From f79395851b044689567e0e6c9504acbcaa742782 Mon Sep 17 00:00:00 2001 From: Benjamin Southall Date: Tue, 26 Feb 2019 10:11:12 +1000 Subject: [PATCH] Twig update to latest Twig 1.x legacy as per vichan --- inc/lib/Twig/Autoloader.php | 18 +- inc/lib/Twig/BaseNodeVisitor.php | 54 ++ inc/lib/Twig/Cache/Filesystem.php | 93 ++ inc/lib/Twig/Cache/Null.php | 40 + inc/lib/Twig/CacheInterface.php | 58 ++ inc/lib/Twig/Compiler.php | 90 +- inc/lib/Twig/CompilerInterface.php | 9 +- inc/lib/Twig/ContainerRuntimeLoader.php | 39 + inc/lib/Twig/Environment.php | 802 ++++++++++++----- inc/lib/Twig/Error.php | 172 +++- inc/lib/Twig/Error/Loader.php | 15 +- inc/lib/Twig/Error/Runtime.php | 6 +- inc/lib/Twig/Error/Syntax.php | 39 +- inc/lib/Twig/ExistsLoaderInterface.php | 9 +- inc/lib/Twig/ExpressionParser.php | 341 +++++--- inc/lib/Twig/Extension.php | 54 +- inc/lib/Twig/Extension/Core.php | 809 ++++++++++++------ inc/lib/Twig/Extension/Debug.php | 22 +- inc/lib/Twig/Extension/Escaper.php | 61 +- inc/lib/Twig/Extension/GlobalsInterface.php | 24 + .../Twig/Extension/InitRuntimeInterface.php | 24 + inc/lib/Twig/Extension/Optimizer.php | 14 +- inc/lib/Twig/Extension/Profiler.php | 49 ++ inc/lib/Twig/Extension/Sandbox.php | 27 +- inc/lib/Twig/Extension/Staging.php | 45 +- inc/lib/Twig/Extension/StringLoader.php | 37 +- inc/lib/Twig/ExtensionInterface.php | 23 +- inc/lib/Twig/FactoryRuntimeLoader.php | 39 + .../Twig/FileExtensionEscapingStrategy.php | 60 ++ inc/lib/Twig/Filter.php | 13 +- inc/lib/Twig/Filter/Function.php | 5 +- inc/lib/Twig/Filter/Method.php | 7 +- inc/lib/Twig/Filter/Node.php | 5 +- inc/lib/Twig/FilterCallableInterface.php | 3 +- inc/lib/Twig/FilterInterface.php | 3 +- inc/lib/Twig/Function.php | 9 +- inc/lib/Twig/Function/Function.php | 7 +- inc/lib/Twig/Function/Method.php | 9 +- inc/lib/Twig/Function/Node.php | 5 +- inc/lib/Twig/FunctionCallableInterface.php | 3 +- inc/lib/Twig/FunctionInterface.php | 5 +- inc/lib/Twig/Lexer.php | 127 +-- inc/lib/Twig/LexerInterface.php | 15 +- inc/lib/Twig/Loader/Array.php | 42 +- inc/lib/Twig/Loader/Chain.php | 65 +- inc/lib/Twig/Loader/Filesystem.php | 158 +++- inc/lib/Twig/Loader/String.php | 33 +- inc/lib/Twig/LoaderInterface.php | 13 +- inc/lib/Twig/Markup.php | 4 +- inc/lib/Twig/Node.php | 124 ++- inc/lib/Twig/Node/AutoEscape.php | 9 +- inc/lib/Twig/Node/Block.php | 11 +- inc/lib/Twig/Node/BlockReference.php | 11 +- inc/lib/Twig/Node/Body.php | 4 +- inc/lib/Twig/Node/CheckSecurity.php | 80 ++ inc/lib/Twig/Node/Do.php | 9 +- inc/lib/Twig/Node/Embed.php | 20 +- inc/lib/Twig/Node/Expression.php | 6 +- inc/lib/Twig/Node/Expression/Array.php | 13 +- inc/lib/Twig/Node/Expression/AssignName.php | 11 +- inc/lib/Twig/Node/Expression/Binary.php | 11 +- inc/lib/Twig/Node/Expression/Binary/Add.php | 6 +- inc/lib/Twig/Node/Expression/Binary/And.php | 6 +- .../Node/Expression/Binary/BitwiseAnd.php | 6 +- .../Twig/Node/Expression/Binary/BitwiseOr.php | 6 +- .../Node/Expression/Binary/BitwiseXor.php | 6 +- .../Twig/Node/Expression/Binary/Concat.php | 6 +- inc/lib/Twig/Node/Expression/Binary/Div.php | 6 +- .../Twig/Node/Expression/Binary/EndsWith.php | 32 + inc/lib/Twig/Node/Expression/Binary/Equal.php | 4 +- .../Twig/Node/Expression/Binary/FloorDiv.php | 13 +- .../Twig/Node/Expression/Binary/Greater.php | 4 +- .../Node/Expression/Binary/GreaterEqual.php | 4 +- inc/lib/Twig/Node/Expression/Binary/In.php | 9 +- inc/lib/Twig/Node/Expression/Binary/Less.php | 4 +- .../Twig/Node/Expression/Binary/LessEqual.php | 4 +- .../Twig/Node/Expression/Binary/Matches.php | 30 + inc/lib/Twig/Node/Expression/Binary/Mod.php | 6 +- inc/lib/Twig/Node/Expression/Binary/Mul.php | 6 +- .../Twig/Node/Expression/Binary/NotEqual.php | 4 +- inc/lib/Twig/Node/Expression/Binary/NotIn.php | 9 +- inc/lib/Twig/Node/Expression/Binary/Or.php | 6 +- inc/lib/Twig/Node/Expression/Binary/Power.php | 13 +- inc/lib/Twig/Node/Expression/Binary/Range.php | 9 +- .../Node/Expression/Binary/StartsWith.php | 32 + inc/lib/Twig/Node/Expression/Binary/Sub.php | 6 +- .../Twig/Node/Expression/BlockReference.php | 84 +- inc/lib/Twig/Node/Expression/Call.php | 209 +++-- inc/lib/Twig/Node/Expression/Conditional.php | 6 +- inc/lib/Twig/Node/Expression/Constant.php | 6 +- .../Node/Expression/ExtensionReference.php | 11 +- inc/lib/Twig/Node/Expression/Filter.php | 9 +- .../Twig/Node/Expression/Filter/Default.php | 12 +- inc/lib/Twig/Node/Expression/Function.php | 16 +- inc/lib/Twig/Node/Expression/GetAttr.php | 49 +- inc/lib/Twig/Node/Expression/MethodCall.php | 4 +- inc/lib/Twig/Node/Expression/Name.php | 28 +- inc/lib/Twig/Node/Expression/NullCoalesce.php | 48 ++ inc/lib/Twig/Node/Expression/Parent.php | 17 +- inc/lib/Twig/Node/Expression/TempName.php | 4 +- inc/lib/Twig/Node/Expression/Test.php | 14 +- .../Twig/Node/Expression/Test/Constant.php | 4 +- inc/lib/Twig/Node/Expression/Test/Defined.php | 17 +- .../Twig/Node/Expression/Test/Divisibleby.php | 6 +- inc/lib/Twig/Node/Expression/Test/Even.php | 4 +- inc/lib/Twig/Node/Expression/Test/Null.php | 4 +- inc/lib/Twig/Node/Expression/Test/Odd.php | 4 +- inc/lib/Twig/Node/Expression/Test/Sameas.php | 4 +- inc/lib/Twig/Node/Expression/Unary.php | 13 +- inc/lib/Twig/Node/Expression/Unary/Neg.php | 6 +- inc/lib/Twig/Node/Expression/Unary/Not.php | 6 +- inc/lib/Twig/Node/Expression/Unary/Pos.php | 6 +- inc/lib/Twig/Node/Flush.php | 9 +- inc/lib/Twig/Node/For.php | 29 +- inc/lib/Twig/Node/ForLoop.php | 9 +- inc/lib/Twig/Node/If.php | 24 +- inc/lib/Twig/Node/Import.php | 19 +- inc/lib/Twig/Node/Include.php | 65 +- inc/lib/Twig/Node/Macro.php | 77 +- inc/lib/Twig/Node/Module.php | 256 +++--- inc/lib/Twig/Node/Print.php | 11 +- inc/lib/Twig/Node/Sandbox.php | 11 +- inc/lib/Twig/Node/SandboxedPrint.php | 20 +- inc/lib/Twig/Node/Set.php | 13 +- inc/lib/Twig/Node/SetTemp.php | 7 +- inc/lib/Twig/Node/Spaceless.php | 9 +- inc/lib/Twig/Node/Text.php | 11 +- inc/lib/Twig/Node/With.php | 64 ++ inc/lib/Twig/NodeCaptureInterface.php | 21 + inc/lib/Twig/NodeInterface.php | 10 +- inc/lib/Twig/NodeOutputInterface.php | 4 +- inc/lib/Twig/NodeTraverser.php | 32 +- inc/lib/Twig/NodeVisitor/Escaper.php | 39 +- inc/lib/Twig/NodeVisitor/Optimizer.php | 101 ++- inc/lib/Twig/NodeVisitor/SafeAnalysis.php | 43 +- inc/lib/Twig/NodeVisitor/Sandbox.php | 52 +- inc/lib/Twig/NodeVisitorInterface.php | 14 +- inc/lib/Twig/Parser.php | 140 +-- inc/lib/Twig/ParserInterface.php | 9 +- inc/lib/Twig/Profiler/Dumper/Base.php | 62 ++ inc/lib/Twig/Profiler/Dumper/Blackfire.php | 72 ++ inc/lib/Twig/Profiler/Dumper/Html.php | 47 + inc/lib/Twig/Profiler/Dumper/Text.php | 35 + inc/lib/Twig/Profiler/Node/EnterProfile.php | 39 + inc/lib/Twig/Profiler/Node/LeaveProfile.php | 33 + .../Twig/Profiler/NodeVisitor/Profiler.php | 67 ++ inc/lib/Twig/Profiler/Profile.php | 170 ++++ inc/lib/Twig/RuntimeLoaderInterface.php | 29 + inc/lib/Twig/Sandbox/SecurityError.php | 4 +- .../Sandbox/SecurityNotAllowedFilterError.php | 33 + .../SecurityNotAllowedFunctionError.php | 33 + .../Sandbox/SecurityNotAllowedMethodError.php | 40 + .../SecurityNotAllowedPropertyError.php | 40 + .../Sandbox/SecurityNotAllowedTagError.php | 33 + inc/lib/Twig/Sandbox/SecurityPolicy.php | 18 +- .../Twig/Sandbox/SecurityPolicyInterface.php | 4 +- inc/lib/Twig/SimpleFilter.php | 41 +- inc/lib/Twig/SimpleFunction.php | 37 +- inc/lib/Twig/SimpleTest.php | 29 +- inc/lib/Twig/Source.php | 53 ++ inc/lib/Twig/SourceContextLoaderInterface.php | 33 + inc/lib/Twig/Template.php | 565 ++++++++---- inc/lib/Twig/TemplateInterface.php | 11 +- inc/lib/Twig/TemplateWrapper.php | 133 +++ inc/lib/Twig/Test.php | 5 +- inc/lib/Twig/Test/Function.php | 5 +- inc/lib/Twig/Test/IntegrationTestCase.php | 153 +++- inc/lib/Twig/Test/Method.php | 7 +- inc/lib/Twig/Test/Node.php | 5 +- inc/lib/Twig/Test/NodeTestCase.php | 37 +- inc/lib/Twig/TestCallableInterface.php | 3 +- inc/lib/Twig/TestInterface.php | 3 +- inc/lib/Twig/Token.php | 89 +- inc/lib/Twig/TokenParser.php | 8 +- inc/lib/Twig/TokenParser/AutoEscape.php | 24 +- inc/lib/Twig/TokenParser/Block.php | 32 +- inc/lib/Twig/TokenParser/Do.php | 18 +- inc/lib/Twig/TokenParser/Embed.php | 33 +- inc/lib/Twig/TokenParser/Extends.php | 28 +- inc/lib/Twig/TokenParser/Filter.php | 20 +- inc/lib/Twig/TokenParser/Flush.php | 18 +- inc/lib/Twig/TokenParser/For.php | 35 +- inc/lib/Twig/TokenParser/From.php | 32 +- inc/lib/Twig/TokenParser/If.php | 22 +- inc/lib/Twig/TokenParser/Import.php | 18 +- inc/lib/Twig/TokenParser/Include.php | 29 +- inc/lib/Twig/TokenParser/Macro.php | 24 +- inc/lib/Twig/TokenParser/Sandbox.php | 27 +- inc/lib/Twig/TokenParser/Set.php | 25 +- inc/lib/Twig/TokenParser/Spaceless.php | 18 +- inc/lib/Twig/TokenParser/Use.php | 36 +- inc/lib/Twig/TokenParser/With.php | 52 ++ inc/lib/Twig/TokenParserBroker.php | 46 +- inc/lib/Twig/TokenParserBrokerInterface.php | 9 +- inc/lib/Twig/TokenParserInterface.php | 14 +- inc/lib/Twig/TokenStream.php | 114 ++- inc/lib/Twig/Util/DeprecationCollector.php | 86 ++ inc/lib/Twig/Util/TemplateDirIterator.php | 28 + 198 files changed, 6124 insertions(+), 2480 deletions(-) create mode 100644 inc/lib/Twig/BaseNodeVisitor.php create mode 100644 inc/lib/Twig/Cache/Filesystem.php create mode 100644 inc/lib/Twig/Cache/Null.php create mode 100644 inc/lib/Twig/CacheInterface.php create mode 100644 inc/lib/Twig/ContainerRuntimeLoader.php create mode 100644 inc/lib/Twig/Extension/GlobalsInterface.php create mode 100644 inc/lib/Twig/Extension/InitRuntimeInterface.php create mode 100644 inc/lib/Twig/Extension/Profiler.php create mode 100644 inc/lib/Twig/FactoryRuntimeLoader.php create mode 100644 inc/lib/Twig/FileExtensionEscapingStrategy.php create mode 100644 inc/lib/Twig/Node/CheckSecurity.php create mode 100644 inc/lib/Twig/Node/Expression/Binary/EndsWith.php create mode 100644 inc/lib/Twig/Node/Expression/Binary/Matches.php create mode 100644 inc/lib/Twig/Node/Expression/Binary/StartsWith.php create mode 100644 inc/lib/Twig/Node/Expression/NullCoalesce.php create mode 100644 inc/lib/Twig/Node/With.php create mode 100644 inc/lib/Twig/NodeCaptureInterface.php create mode 100644 inc/lib/Twig/Profiler/Dumper/Base.php create mode 100644 inc/lib/Twig/Profiler/Dumper/Blackfire.php create mode 100644 inc/lib/Twig/Profiler/Dumper/Html.php create mode 100644 inc/lib/Twig/Profiler/Dumper/Text.php create mode 100644 inc/lib/Twig/Profiler/Node/EnterProfile.php create mode 100644 inc/lib/Twig/Profiler/Node/LeaveProfile.php create mode 100644 inc/lib/Twig/Profiler/NodeVisitor/Profiler.php create mode 100644 inc/lib/Twig/Profiler/Profile.php create mode 100644 inc/lib/Twig/RuntimeLoaderInterface.php create mode 100644 inc/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php create mode 100644 inc/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php create mode 100644 inc/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php create mode 100644 inc/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php create mode 100644 inc/lib/Twig/Sandbox/SecurityNotAllowedTagError.php create mode 100644 inc/lib/Twig/Source.php create mode 100644 inc/lib/Twig/SourceContextLoaderInterface.php create mode 100644 inc/lib/Twig/TemplateWrapper.php create mode 100644 inc/lib/Twig/TokenParser/With.php create mode 100644 inc/lib/Twig/Util/DeprecationCollector.php create mode 100644 inc/lib/Twig/Util/TemplateDirIterator.php diff --git a/inc/lib/Twig/Autoloader.php b/inc/lib/Twig/Autoloader.php index 7007d315..212af544 100644 --- a/inc/lib/Twig/Autoloader.php +++ b/inc/lib/Twig/Autoloader.php @@ -3,37 +3,43 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Autoloader class is deprecated since version 1.21 and will be removed in 2.0. Use Composer instead.', E_USER_DEPRECATED); + /** * Autoloads Twig classes. * * @author Fabien Potencier + * + * @deprecated since 1.21 and will be removed in 2.0. Use Composer instead. 2.0. */ class Twig_Autoloader { /** * Registers Twig_Autoloader as an SPL autoloader. * - * @param Boolean $prepend Whether to prepend the autoloader or not. + * @param bool $prepend whether to prepend the autoloader or not */ public static function register($prepend = false) { - if (version_compare(phpversion(), '5.3.0', '>=')) { - spl_autoload_register(array(new self, 'autoload'), true, $prepend); + @trigger_error('Using Twig_Autoloader is deprecated since version 1.21. Use Composer instead.', E_USER_DEPRECATED); + + if (PHP_VERSION_ID < 50300) { + spl_autoload_register(array(__CLASS__, 'autoload')); } else { - spl_autoload_register(array(new self, 'autoload')); + spl_autoload_register(array(__CLASS__, 'autoload'), true, $prepend); } } /** * Handles autoloading of classes. * - * @param string $class A class name. + * @param string $class a class name */ public static function autoload($class) { diff --git a/inc/lib/Twig/BaseNodeVisitor.php b/inc/lib/Twig/BaseNodeVisitor.php new file mode 100644 index 00000000..d8ef02fb --- /dev/null +++ b/inc/lib/Twig/BaseNodeVisitor.php @@ -0,0 +1,54 @@ + + */ +abstract class Twig_BaseNodeVisitor implements Twig_NodeVisitorInterface +{ + final public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if (!$node instanceof Twig_Node) { + throw new LogicException('Twig_BaseNodeVisitor only supports Twig_Node instances.'); + } + + return $this->doEnterNode($node, $env); + } + + final public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) + { + if (!$node instanceof Twig_Node) { + throw new LogicException('Twig_BaseNodeVisitor only supports Twig_Node instances.'); + } + + return $this->doLeaveNode($node, $env); + } + + /** + * Called before child nodes are visited. + * + * @return Twig_Node The modified node + */ + abstract protected function doEnterNode(Twig_Node $node, Twig_Environment $env); + + /** + * Called after child nodes are visited. + * + * @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); +} + +class_alias('Twig_BaseNodeVisitor', 'Twig\NodeVisitor\AbstractNodeVisitor', false); +class_exists('Twig_Environment'); +class_exists('Twig_Node'); diff --git a/inc/lib/Twig/Cache/Filesystem.php b/inc/lib/Twig/Cache/Filesystem.php new file mode 100644 index 00000000..65976282 --- /dev/null +++ b/inc/lib/Twig/Cache/Filesystem.php @@ -0,0 +1,93 @@ + + */ +class Twig_Cache_Filesystem implements Twig_CacheInterface +{ + const FORCE_BYTECODE_INVALIDATION = 1; + + private $directory; + private $options; + + /** + * @param $directory string The root cache directory + * @param $options int A set of options + */ + public function __construct($directory, $options = 0) + { + $this->directory = rtrim($directory, '\/').'/'; + $this->options = $options; + } + + public function generateKey($name, $className) + { + $hash = hash('sha256', $className); + + return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php'; + } + + public function load($key) + { + if (file_exists($key)) { + @include_once $key; + } + } + + public function write($key, $content) + { + $dir = dirname($key); + if (!is_dir($dir)) { + if (false === @mkdir($dir, 0777, true)) { + if (PHP_VERSION_ID >= 50300) { + clearstatcache(true, $dir); + } + if (!is_dir($dir)) { + throw new RuntimeException(sprintf('Unable to create the cache directory (%s).', $dir)); + } + } + } elseif (!is_writable($dir)) { + throw new RuntimeException(sprintf('Unable to write in the cache directory (%s).', $dir)); + } + + $tmpFile = tempnam($dir, basename($key)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $key)) { + @chmod($key, 0666 & ~umask()); + + if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) { + // Compile cached file into bytecode cache + if (function_exists('opcache_invalidate')) { + opcache_invalidate($key, true); + } elseif (function_exists('apc_compile_file')) { + apc_compile_file($key); + } + } + + return; + } + + throw new RuntimeException(sprintf('Failed to write cache file "%s".', $key)); + } + + public function getTimestamp($key) + { + if (!file_exists($key)) { + return 0; + } + + return (int) @filemtime($key); + } +} + +class_alias('Twig_Cache_Filesystem', 'Twig\Cache\FilesystemCache', false); diff --git a/inc/lib/Twig/Cache/Null.php b/inc/lib/Twig/Cache/Null.php new file mode 100644 index 00000000..69d1d2f9 --- /dev/null +++ b/inc/lib/Twig/Cache/Null.php @@ -0,0 +1,40 @@ + + */ +class Twig_Cache_Null implements Twig_CacheInterface +{ + public function generateKey($name, $className) + { + return ''; + } + + public function write($key, $content) + { + } + + public function load($key) + { + } + + public function getTimestamp($key) + { + return 0; + } +} + +class_alias('Twig_Cache_Null', 'Twig\Cache\NullCache', false); diff --git a/inc/lib/Twig/CacheInterface.php b/inc/lib/Twig/CacheInterface.php new file mode 100644 index 00000000..776808bf --- /dev/null +++ b/inc/lib/Twig/CacheInterface.php @@ -0,0 +1,58 @@ + + */ +interface Twig_CacheInterface +{ + /** + * Generates a cache key for the given template class name. + * + * @param string $name The template name + * @param string $className The template class name + * + * @return string + */ + public function generateKey($name, $className); + + /** + * Writes the compiled template to cache. + * + * @param string $key The cache key + * @param string $content The template representation as a PHP class + */ + public function write($key, $content); + + /** + * Loads a template from the cache. + * + * @param string $key The cache key + */ + public function load($key); + + /** + * Returns the modification timestamp of a key. + * + * @param string $key The cache key + * + * @return int + */ + public function getTimestamp($key); +} + +class_alias('Twig_CacheInterface', 'Twig\Cache\CacheInterface', false); diff --git a/inc/lib/Twig/Compiler.php b/inc/lib/Twig/Compiler.php index b80210b1..803eb896 100644 --- a/inc/lib/Twig/Compiler.php +++ b/inc/lib/Twig/Compiler.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -21,31 +21,31 @@ class Twig_Compiler implements Twig_CompilerInterface protected $source; protected $indentation; protected $env; - protected $debugInfo; + protected $debugInfo = array(); protected $sourceOffset; protected $sourceLine; protected $filename; + private $varNameSalt = 0; - /** - * Constructor. - * - * @param Twig_Environment $env The twig environment instance - */ public function __construct(Twig_Environment $env) { $this->env = $env; - $this->debugInfo = array(); } + /** + * @deprecated since 1.25 (to be removed in 2.0) + */ public function getFilename() { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + return $this->filename; } /** * Returns the environment instance related to this compiler. * - * @return Twig_Environment The environment instance + * @return Twig_Environment */ public function getEnvironment() { @@ -66,21 +66,24 @@ class Twig_Compiler implements Twig_CompilerInterface * Compiles a node. * * @param Twig_NodeInterface $node The node to compile - * @param integer $indentation The current indentation + * @param int $indentation The current indentation * - * @return Twig_Compiler The current compiler instance + * @return $this */ public function compile(Twig_NodeInterface $node, $indentation = 0) { $this->lastLine = null; $this->source = ''; + $this->debugInfo = array(); $this->sourceOffset = 0; // source code starts at 1 (as we then increment it when we encounter new lines) $this->sourceLine = 1; $this->indentation = $indentation; + $this->varNameSalt = 0; if ($node instanceof Twig_Node_Module) { - $this->filename = $node->getAttribute('filename'); + // to be removed in 2.0 + $this->filename = $node->getTemplateName(); } $node->compile($this); @@ -91,7 +94,7 @@ class Twig_Compiler implements Twig_CompilerInterface public function subcompile(Twig_NodeInterface $node, $raw = true) { if (false === $raw) { - $this->addIndentation(); + $this->source .= str_repeat(' ', $this->indentation * 4); } $node->compile($this); @@ -104,7 +107,7 @@ class Twig_Compiler implements Twig_CompilerInterface * * @param string $string The string * - * @return Twig_Compiler The current compiler instance + * @return $this */ public function raw($string) { @@ -116,14 +119,13 @@ class Twig_Compiler implements Twig_CompilerInterface /** * 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; @@ -132,10 +134,14 @@ class Twig_Compiler implements Twig_CompilerInterface /** * 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; @@ -146,7 +152,7 @@ class Twig_Compiler implements Twig_CompilerInterface * * @param string $value The string * - * @return Twig_Compiler The current compiler instance + * @return $this */ public function string($value) { @@ -160,12 +166,12 @@ class Twig_Compiler implements Twig_CompilerInterface * * @param mixed $value The value to convert * - * @return Twig_Compiler The current compiler instance + * @return $this */ public function repr($value) { if (is_int($value) || is_float($value)) { - if (false !== $locale = setlocale(LC_NUMERIC, 0)) { + if (false !== $locale = setlocale(LC_NUMERIC, '0')) { setlocale(LC_NUMERIC, 'C'); } @@ -181,14 +187,14 @@ class Twig_Compiler implements Twig_CompilerInterface } elseif (is_array($value)) { $this->raw('array('); $first = true; - foreach ($value as $key => $value) { + foreach ($value as $key => $v) { if (!$first) { $this->raw(', '); } $first = false; $this->repr($key); $this->raw(' => '); - $this->repr($value); + $this->repr($v); } $this->raw(')'); } else { @@ -201,28 +207,28 @@ class Twig_Compiler implements Twig_CompilerInterface /** * 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("// line {$node->getLine()}\n"); + 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() // but they have different signatures! if (((int) ini_get('mbstring.func_overload')) & 2) { + @trigger_error('Support for having "mbstring.func_overload" different from 0 is deprecated version 1.29 and will be removed in 2.0.', E_USER_DEPRECATED); + // this is much slower than the "right" version $this->sourceLine += mb_substr_count(mb_substr($this->source, $this->sourceOffset), "\n"); } else { $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; @@ -230,15 +236,17 @@ class Twig_Compiler implements Twig_CompilerInterface public function getDebugInfo() { + ksort($this->debugInfo); + return $this->debugInfo; } /** * Indents the generated code. * - * @param integer $step The number of indentation to add + * @param int $step The number of indentation to add * - * @return Twig_Compiler The current compiler instance + * @return $this */ public function indent($step = 1) { @@ -250,19 +258,29 @@ class Twig_Compiler implements Twig_CompilerInterface /** * Outdents the generated code. * - * @param integer $step The number of indentation to remove + * @param int $step The number of indentation to remove + * + * @return $this * - * @return Twig_Compiler The current compiler instance + * @throws LogicException When trying to outdent too much so the indentation would become negative */ public function outdent($step = 1) { // can't outdent by more steps than the current indentation level if ($this->indentation < $step) { - throw new LogicException('Unable to call outdent() as the indentation would become negative'); + throw new LogicException('Unable to call outdent() as the indentation would become negative.'); } $this->indentation -= $step; return $this; } + + public function getVarName() + { + return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->varNameSalt++)); + } } + +class_alias('Twig_Compiler', 'Twig\Compiler', false); +class_exists('Twig_Node'); diff --git a/inc/lib/Twig/CompilerInterface.php b/inc/lib/Twig/CompilerInterface.php index e293ec91..42872c9c 100644 --- a/inc/lib/Twig/CompilerInterface.php +++ b/inc/lib/Twig/CompilerInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,16 +13,15 @@ * Interface implemented by compiler classes. * * @author Fabien Potencier - * @deprecated since 1.12 (to be removed in 2.0) + * + * @deprecated since 1.12 (to be removed in 3.0) */ 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/inc/lib/Twig/ContainerRuntimeLoader.php b/inc/lib/Twig/ContainerRuntimeLoader.php new file mode 100644 index 00000000..814ab58b --- /dev/null +++ b/inc/lib/Twig/ContainerRuntimeLoader.php @@ -0,0 +1,39 @@ + + * @author Robin Chalas + */ +class Twig_ContainerRuntimeLoader implements Twig_RuntimeLoaderInterface +{ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + public function load($class) + { + if ($this->container->has($class)) { + return $this->container->get($class); + } + } +} + +class_alias('Twig_ContainerRuntimeLoader', 'Twig\RuntimeLoader\ContainerRuntimeLoader', false); diff --git a/inc/lib/Twig/Environment.php b/inc/lib/Twig/Environment.php index 09ea4a25..b15f2aff 100644 --- a/inc/lib/Twig/Environment.php +++ b/inc/lib/Twig/Environment.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,7 +16,12 @@ */ class Twig_Environment { - const VERSION = '1.14.0-DEV'; + const VERSION = '1.35.4-DEV'; + const VERSION_ID = 13504; + const MAJOR_VERSION = 1; + const MINOR_VERSION = 35; + const RELEASE_VERSION = 4; + const EXTRA_VERSION = 'DEV'; protected $charset; protected $loader; @@ -34,17 +39,26 @@ class Twig_Environment protected $tests; protected $functions; protected $globals; - protected $runtimeInitialized; - protected $extensionInitialized; + protected $runtimeInitialized = false; + protected $extensionInitialized = false; protected $loadedTemplates; protected $strictVariables; protected $unaryOperators; protected $binaryOperators; protected $templateClassPrefix = '__TwigTemplate_'; - protected $functionCallbacks; - protected $filterCallbacks; + protected $functionCallbacks = array(); + protected $filterCallbacks = array(); protected $staging; - protected $templateClasses; + + private $originalCache; + private $bcWriteCacheFile = false; + private $bcGetCacheFilename = false; + private $lastModifiedExtension = 0; + private $extensionsByClass = array(); + private $runtimeLoaders = array(); + private $runtimes = array(); + private $optionsHash; + private $loading = array(); /** * Constructor. @@ -59,12 +73,13 @@ class Twig_Environment * * base_template_class: The base template class to use for generated * templates (default to Twig_Template). * - * * cache: An absolute path where to store the compiled templates, or - * false to disable compilation cache (default). + * * cache: An absolute path where to store the compiled templates, + * a Twig_Cache_Interface implementation, + * or false to disable compilation cache (default). * - * * auto_reload: Whether to reload the template is the original source changed. + * * auto_reload: Whether to reload the template if the original source changed. * If you don't provide the auto_reload option, it will be - * determined automatically base on the debug value. + * determined automatically based on the debug value. * * * strict_variables: Whether to ignore invalid variables in templates * (default to false). @@ -73,48 +88,63 @@ class Twig_Environment * * false: disable auto-escaping * * true: equivalent to html * * html, js: set the autoescaping to one of the supported strategies - * * 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()) { if (null !== $loader) { $this->setLoader($loader); + } else { + @trigger_error('Not passing a Twig_LoaderInterface as the first constructor argument of Twig_Environment is deprecated since version 1.21.', E_USER_DEPRECATED); } $options = array_merge(array( - 'debug' => false, - 'charset' => 'UTF-8', + 'debug' => false, + 'charset' => 'UTF-8', 'base_template_class' => 'Twig_Template', - 'strict_variables' => false, - 'autoescape' => 'html', - 'cache' => false, - 'auto_reload' => null, - 'optimizations' => -1, + 'strict_variables' => false, + 'autoescape' => 'html', + 'cache' => false, + 'auto_reload' => null, + 'optimizations' => -1, ), $options); - $this->debug = (bool) $options['debug']; - $this->charset = strtoupper($options['charset']); - $this->baseTemplateClass = $options['base_template_class']; - $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; - $this->strictVariables = (bool) $options['strict_variables']; - $this->runtimeInitialized = false; + $this->debug = (bool) $options['debug']; + $this->charset = strtoupper($options['charset']); + $this->baseTemplateClass = $options['base_template_class']; + $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; + $this->strictVariables = (bool) $options['strict_variables']; $this->setCache($options['cache']); - $this->functionCallbacks = array(); - $this->filterCallbacks = array(); - $this->templateClasses = array(); $this->addExtension(new Twig_Extension_Core()); $this->addExtension(new Twig_Extension_Escaper($options['autoescape'])); $this->addExtension(new Twig_Extension_Optimizer($options['optimizations'])); - $this->extensionInitialized = false; $this->staging = new Twig_Extension_Staging(); + + // For BC + if (is_string($this->originalCache)) { + $r = new ReflectionMethod($this, 'writeCacheFile'); + if (__CLASS__ !== $r->getDeclaringClass()->getName()) { + @trigger_error('The Twig_Environment::writeCacheFile method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED); + + $this->bcWriteCacheFile = true; + } + + $r = new ReflectionMethod($this, 'getCacheFilename'); + if (__CLASS__ !== $r->getDeclaringClass()->getName()) { + @trigger_error('The Twig_Environment::getCacheFilename method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED); + + $this->bcGetCacheFilename = true; + } + } } /** @@ -135,6 +165,7 @@ class Twig_Environment public function setBaseTemplateClass($class) { $this->baseTemplateClass = $class; + $this->updateOptionsHash(); } /** @@ -143,6 +174,7 @@ class Twig_Environment public function enableDebug() { $this->debug = true; + $this->updateOptionsHash(); } /** @@ -151,12 +183,13 @@ class Twig_Environment public function disableDebug() { $this->debug = false; + $this->updateOptionsHash(); } /** * Checks if debug mode is enabled. * - * @return Boolean true if debug mode is enabled, false otherwise + * @return bool true if debug mode is enabled, false otherwise */ public function isDebug() { @@ -182,7 +215,7 @@ class Twig_Environment /** * Checks if the auto_reload option is enabled. * - * @return Boolean true if auto_reload is enabled, false otherwise + * @return bool true if auto_reload is enabled, false otherwise */ public function isAutoReload() { @@ -195,6 +228,7 @@ class Twig_Environment public function enableStrictVariables() { $this->strictVariables = true; + $this->updateOptionsHash(); } /** @@ -203,12 +237,13 @@ class Twig_Environment public function disableStrictVariables() { $this->strictVariables = false; + $this->updateOptionsHash(); } /** * Checks if the strict_variables option is enabled. * - * @return Boolean true if strict_variables is enabled, false otherwise + * @return bool true if strict_variables is enabled, false otherwise */ public function isStrictVariables() { @@ -216,24 +251,43 @@ class Twig_Environment } /** - * Gets the cache directory or false if cache is disabled. + * Gets the current cache implementation. + * + * @param bool $original Whether to return the original cache option or the real cache instance * - * @return string|false + * @return Twig_CacheInterface|string|false A Twig_CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache */ - public function getCache() + public function getCache($original = true) { - return $this->cache; + return $original ? $this->originalCache : $this->cache; } - /** - * Sets the cache directory or false if cache is disabled. - * - * @param string|false $cache The absolute path to the compiled templates, - * or false to disable cache - */ + /** + * Sets the current cache implementation. + * + * @param Twig_CacheInterface|string|false $cache A Twig_CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache + */ public function setCache($cache) { - $this->cache = $cache ? $cache : false; + if (is_string($cache)) { + $this->originalCache = $cache; + $this->cache = new Twig_Cache_Filesystem($cache); + } elseif (false === $cache) { + $this->originalCache = $cache; + $this->cache = new Twig_Cache_Null(); + } elseif (null === $cache) { + @trigger_error('Using "null" as the cache strategy is deprecated since version 1.23 and will be removed in Twig 2.0.', E_USER_DEPRECATED); + $this->originalCache = false; + $this->cache = new Twig_Cache_Null(); + } elseif ($cache instanceof Twig_CacheInterface) { + $this->originalCache = $this->cache = $cache; + } else { + throw new LogicException(sprintf('Cache can only be a string, false, or a Twig_CacheInterface implementation.')); + } } /** @@ -241,45 +295,54 @@ class Twig_Environment * * @param string $name The template name * - * @return string The cache file name + * @return string|false The cache file name or false when caching is disabled + * + * @deprecated since 1.22 (to be removed in 2.0) */ public function getCacheFilename($name) { - if (false === $this->cache) { - return false; - } + @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); - $class = substr($this->getTemplateClass($name), strlen($this->templateClassPrefix)); + $key = $this->cache->generateKey($name, $this->getTemplateClass($name)); - return $this->getCache().'/'.substr($class, 0, 2).'/'.substr($class, 2, 2).'/'.substr($class, 4).'.php'; + return !$key ? false : $key; } /** * Gets the template class associated with the given string. * - * @param string $name The name for which to calculate the template class name - * @param integer $index The index if it is an embedded template + * The generated template class is based on the following parameters: + * + * * The cache key for the given template; + * * The currently enabled extensions; + * * Whether the Twig C extension is available or not; + * * PHP version; + * * Twig version; + * * Options with what environment was created. + * + * @param string $name The name for which to calculate the template class name + * @param int|null $index The index if it is an embedded template * * @return string The template class name */ public function getTemplateClass($name, $index = null) { - $suffix = null === $index ? '' : '_'.$index; - $cls = $name.$suffix; - if (isset($this->templateClasses[$cls])) { - return $this->templateClasses[$cls]; - } + $key = $this->getLoader()->getCacheKey($name).$this->optionsHash; - return $this->templateClasses[$cls] = $this->templateClassPrefix.hash('sha256', $this->getLoader()->getCacheKey($name)).$suffix; + return $this->templateClassPrefix.hash('sha256', $key).(null === $index ? '' : '_'.$index); } /** * Gets the template class prefix. * * @return string The template class prefix + * + * @deprecated since 1.22 (to be removed in 2.0) */ public function getTemplateClassPrefix() { + @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + return $this->templateClassPrefix; } @@ -290,6 +353,10 @@ class Twig_Environment * @param array $context An array of parameters to pass to the template * * @return string The rendered template + * + * @throws Twig_Error_Loader When the template cannot be found + * @throws Twig_Error_Syntax When an error occurred during compilation + * @throws Twig_Error_Runtime When an error occurred during rendering */ public function render($name, array $context = array()) { @@ -301,6 +368,10 @@ class Twig_Environment * * @param string $name The template name * @param array $context An array of parameters to pass to the template + * + * @throws Twig_Error_Loader When the template cannot be found + * @throws Twig_Error_Syntax When an error occurred during compilation + * @throws Twig_Error_Runtime When an error occurred during rendering */ public function display($name, array $context = array()) { @@ -308,30 +379,97 @@ class Twig_Environment } /** - * Loads a template by name. + * Loads a template. * - * @param string $name The template name - * @param integer $index The index if it is an embedded template + * @param string|Twig_TemplateWrapper|Twig_Template $name The template name + * + * @throws Twig_Error_Loader When the template cannot be found + * @throws Twig_Error_Runtime When a previously generated cache is corrupted + * @throws Twig_Error_Syntax When an error occurred during compilation + * + * @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 * * @return Twig_TemplateInterface A template instance representing the given template name + * + * @throws Twig_Error_Loader When the template cannot be found + * @throws Twig_Error_Runtime When a previously generated cache is corrupted + * @throws Twig_Error_Syntax When an error occurred during compilation + * + * @internal */ public function loadTemplate($name, $index = null) { - $cls = $this->getTemplateClass($name, $index); + $cls = $mainCls = $this->getTemplateClass($name); + if (null !== $index) { + $cls .= '_'.$index; + } if (isset($this->loadedTemplates[$cls])) { return $this->loadedTemplates[$cls]; } if (!class_exists($cls, false)) { - if (false === $cache = $this->getCacheFilename($name)) { - eval('?>'.$this->compileSource($this->getLoader()->getSource($name), $name)); + if ($this->bcGetCacheFilename) { + $key = $this->getCacheFilename($name); } else { - if (!is_file($cache) || ($this->isAutoReload() && !$this->isTemplateFresh($name, filemtime($cache)))) { - $this->writeCacheFile($cache, $this->compileSource($this->getLoader()->getSource($name), $name)); + $key = $this->cache->generateKey($name, $mainCls); + } + + if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) { + $this->cache->load($key); + } + + if (!class_exists($cls, false)) { + $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); + } + + if (!class_exists($mainCls, 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); } + } - require_once $cache; + if (!class_exists($cls, false)) { + throw new Twig_Error_Runtime(sprintf('Failed to load Twig template "%s", index "%s": cache is corrupted.', $name, $index), -1, $source); } } @@ -339,7 +477,60 @@ class Twig_Environment $this->initRuntime(); } - return $this->loadedTemplates[$cls] = new $cls($this); + if (isset($this->loading[$cls])) { + throw new Twig_Error_Runtime(sprintf('Circular reference detected for Twig template "%s", path: %s.', $name, implode(' -> ', array_merge($this->loading, array($name))))); + } + + $this->loading[$cls] = $name; + + try { + $this->loadedTemplates[$cls] = new $cls($this); + unset($this->loading[$cls]); + } catch (\Exception $e) { + unset($this->loading[$cls]); + + throw $e; + } + + return $this->loadedTemplates[$cls]; + } + + /** + * Creates a template from source. + * + * This method should not be used as a generic way to load templates. + * + * @param string $template The template name + * + * @return Twig_Template A template instance representing the given template name + * + * @throws Twig_Error_Loader When the template cannot be found + * @throws Twig_Error_Syntax When an error occurred during compilation + */ + public function createTemplate($template) + { + $name = sprintf('__string_template__%s', hash('sha256', $template, false)); + + $loader = new Twig_Loader_Chain(array( + new Twig_Loader_Array(array($name => $template)), + $current = $this->getLoader(), + )); + + $this->setLoader($loader); + try { + $template = $this->loadTemplate($name); + } catch (Exception $e) { + $this->setLoader($current); + + throw $e; + } catch (Throwable $e) { + $this->setLoader($current); + + throw $e; + } + $this->setLoader($current); + + return $template; } /** @@ -349,23 +540,38 @@ class Twig_Environment * this method also checks if the enabled extensions have * not changed. * - * @param string $name The template name - * @param timestamp $time The last modification time of the cached template + * @param string $name The template name + * @param int $time The last modification time of the cached template * - * @return Boolean true if the template is fresh, false otherwise + * @return bool true if the template is fresh, false otherwise */ public function isTemplateFresh($name, $time) { - foreach ($this->extensions as $extension) { - $r = new ReflectionObject($extension); - if (filemtime($r->getFileName()) > $time) { - return false; + if (0 === $this->lastModifiedExtension) { + foreach ($this->extensions as $extension) { + $r = new ReflectionObject($extension); + if (file_exists($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModifiedExtension) { + $this->lastModifiedExtension = $extensionTime; + } } } - return $this->getLoader()->isFresh($name, $time); + return $this->lastModifiedExtension <= $time && $this->getLoader()->isFresh($name, $time); } + /** + * Tries to load a template consecutively from an array. + * + * Similar to loadTemplate() but it also accepts instances of Twig_Template and + * Twig_TemplateWrapper, and an array of templates where each is tried to be loaded. + * + * @param string|Twig_Template|Twig_TemplateWrapper|array $names A template or an array of templates to try consecutively + * + * @return Twig_Template|Twig_TemplateWrapper + * + * @throws Twig_Error_Loader When none of the templates can be found + * @throws Twig_Error_Syntax When an error occurred during compilation + */ public function resolveTemplate($names) { if (!is_array($names)) { @@ -377,6 +583,10 @@ class Twig_Environment return $name; } + if ($name instanceof Twig_TemplateWrapper) { + return $name; + } + try { return $this->loadTemplate($name); } catch (Twig_Error_Loader $e) { @@ -392,24 +602,30 @@ class Twig_Environment /** * Clears the internal template cache. + * + * @deprecated since 1.18.3 (to be removed in 2.0) */ public function clearTemplateCache() { + @trigger_error(sprintf('The %s method is deprecated since version 1.18.3 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + $this->loadedTemplates = array(); } /** * Clears the template cache files on the filesystem. + * + * @deprecated since 1.22 (to be removed in 2.0) */ public function clearCacheFiles() { - if (false === $this->cache) { - return; - } + @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); - foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { - if ($file->isFile()) { - @unlink($file->getPathname()); + if (is_string($this->originalCache)) { + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->originalCache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if ($file->isFile()) { + @unlink($file->getPathname()); + } } } } @@ -417,10 +633,14 @@ class Twig_Environment /** * Gets the Lexer instance. * - * @return Twig_LexerInterface A Twig_LexerInterface instance + * @return Twig_LexerInterface + * + * @deprecated since 1.25 (to be removed in 2.0) */ public function getLexer() { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + if (null === $this->lexer) { $this->lexer = new Twig_Lexer($this); } @@ -428,11 +648,6 @@ class Twig_Environment return $this->lexer; } - /** - * Sets the Lexer instance. - * - * @param Twig_LexerInterface A Twig_LexerInterface instance - */ public function setLexer(Twig_LexerInterface $lexer) { $this->lexer = $lexer; @@ -441,23 +656,38 @@ class Twig_Environment /** * 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 * - * @return Twig_TokenStream A Twig_TokenStream instance + * @throws Twig_Error_Syntax When the code is syntactically wrong */ public function tokenize($source, $name = null) { - return $this->getLexer()->tokenize($source, $name); + 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); } /** * Gets the Parser instance. * - * @return Twig_ParserInterface A Twig_ParserInterface instance + * @return Twig_ParserInterface + * + * @deprecated since 1.25 (to be removed in 2.0) */ public function getParser() { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + if (null === $this->parser) { $this->parser = new Twig_Parser($this); } @@ -465,35 +695,38 @@ class Twig_Environment return $this->parser; } - /** - * Sets the Parser instance. - * - * @param Twig_ParserInterface A Twig_ParserInterface instance - */ public function setParser(Twig_ParserInterface $parser) { $this->parser = $parser; } /** - * Parses a token stream. + * Converts a token stream to a node tree. * - * @param Twig_TokenStream $tokens A Twig_TokenStream instance + * @return Twig_Node_Module * - * @return Twig_Node_Module A Node tree + * @throws Twig_Error_Syntax When the token stream is syntactically or semantically wrong */ - public function parse(Twig_TokenStream $tokens) + public function parse(Twig_TokenStream $stream) { - return $this->getParser()->parse($tokens); + if (null === $this->parser) { + $this->parser = new Twig_Parser($this); + } + + return $this->parser->parse($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) */ public function getCompiler() { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + if (null === $this->compiler) { $this->compiler = new Twig_Compiler($this); } @@ -501,62 +734,65 @@ class Twig_Environment return $this->compiler; } - /** - * Sets the Compiler instance. - * - * @param Twig_CompilerInterface $compiler A Twig_CompilerInterface instance - */ public function setCompiler(Twig_CompilerInterface $compiler) { $this->compiler = $compiler; } /** - * Compiles a Node. - * - * @param Twig_NodeInterface $node A Twig_NodeInterface instance + * Compiles a node and returns the PHP code. * * @return string The compiled PHP source code */ public function compile(Twig_NodeInterface $node) { - return $this->getCompiler()->compile($node)->getSource(); + if (null === $this->compiler) { + $this->compiler = new Twig_Compiler($this); + } + + return $this->compiler->compile($node)->getSource(); } /** * 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 + * + * @throws Twig_Error_Syntax When there was an error during tokenizing, parsing or compiling */ 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->setSourceContext($source); throw $e; } catch (Exception $e) { - throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e); + throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $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_')) { + @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() { @@ -589,12 +825,22 @@ class Twig_Environment /** * Initializes the runtime environment. + * + * @deprecated since 1.23 (to be removed in 2.0) */ public function initRuntime() { $this->runtimeInitialized = true; - foreach ($this->getExtensions() as $extension) { + foreach ($this->getExtensions() as $name => $extension) { + if (!$extension instanceof Twig_Extension_InitRuntimeInterface) { + $m = new ReflectionMethod($extension, 'initRuntime'); + + if ('Twig_Extension' !== $m->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Defining the initRuntime() method in the "%s" extension is deprecated since version 1.23. Use the `needs_environment` option to get the Twig_Environment instance in filters, functions, or tests; or explicitly implement Twig_Extension_InitRuntimeInterface if needed (not recommended).', $name), E_USER_DEPRECATED); + } + } + $extension->initRuntime($this); } } @@ -602,43 +848,111 @@ class Twig_Environment /** * Returns true if the given extension is registered. * - * @param string $name The extension name + * @param string $class The extension class name * - * @return Boolean Whether the extension is registered or not + * @return bool Whether the extension is registered or not + */ + public function hasExtension($class) + { + $class = ltrim($class, '\\'); + if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { + // For BC/FC with namespaced aliases + $class = new ReflectionClass($class); + $class = $class->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); + } + + return true; + } + + return isset($this->extensionsByClass[$class]); + } + + /** + * Adds a runtime loader. */ - public function hasExtension($name) + public function addRuntimeLoader(Twig_RuntimeLoaderInterface $loader) { - return isset($this->extensions[$name]); + $this->runtimeLoaders[] = $loader; } /** - * Gets an extension by name. + * Gets an extension by class name. * - * @param string $name The extension name + * @param string $class The extension class name * - * @return Twig_ExtensionInterface A Twig_ExtensionInterface instance + * @return Twig_ExtensionInterface */ - public function getExtension($name) + public function getExtension($class) { - if (!isset($this->extensions[$name])) { - throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $name)); + $class = ltrim($class, '\\'); + if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { + // For BC/FC with namespaced aliases + $class = new ReflectionClass($class); + $class = $class->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); + } + + 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[$name]; + return $this->extensionsByClass[$class]; } /** - * Registers an extension. + * Returns the runtime implementation of a Twig element (filter/function/test). + * + * @param string $class A runtime class name * - * @param Twig_ExtensionInterface $extension A Twig_ExtensionInterface instance + * @return object The runtime implementation + * + * @throws Twig_Error_Runtime When the template cannot be found */ + public function getRuntime($class) + { + if (isset($this->runtimes[$class])) { + return $this->runtimes[$class]; + } + + foreach ($this->runtimeLoaders as $loader) { + if (null !== $runtime = $loader->load($class)) { + return $this->runtimes[$class] = $runtime; + } + } + + throw new Twig_Error_Runtime(sprintf('Unable to load the "%s" runtime.', $class)); + } + public function addExtension(Twig_ExtensionInterface $extension) { if ($this->extensionInitialized) { throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName())); } + $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; + $this->extensionsByClass[$class] = $extension; $this->extensions[$extension->getName()] = $extension; + $this->updateOptionsHash(); } /** @@ -652,11 +966,29 @@ class Twig_Environment */ 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 ($this->extensionInitialized) { throw new LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name)); } - unset($this->extensions[$name]); + $class = ltrim($name, '\\'); + if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { + // For BC/FC with namespaced aliases + $class = new ReflectionClass($class); + $class = $class->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(); } /** @@ -674,18 +1006,13 @@ class Twig_Environment /** * Returns all registered extensions. * - * @return array An array of extensions + * @return Twig_ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on) */ 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) { @@ -698,7 +1025,9 @@ class Twig_Environment /** * Gets the registered Token Parsers. * - * @return Twig_TokenParserBrokerInterface A broker containing token parsers + * @return Twig_TokenParserBrokerInterface + * + * @internal */ public function getTokenParsers() { @@ -714,7 +1043,9 @@ class Twig_Environment * * 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 */ public function getTags() { @@ -728,11 +1059,6 @@ class Twig_Environment return $tags; } - /** - * Registers a Node Visitor. - * - * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance - */ public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) { if ($this->extensionInitialized) { @@ -745,7 +1071,9 @@ class Twig_Environment /** * Gets the registered Node Visitors. * - * @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances + * @return Twig_NodeVisitorInterface[] + * + * @internal */ public function getNodeVisitors() { @@ -760,23 +1088,25 @@ class Twig_Environment * 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) { if (!$name instanceof Twig_SimpleFilter && !($filter instanceof Twig_SimpleFilter || $filter instanceof Twig_FilterInterface)) { - throw new LogicException('A filter must be an instance of Twig_FilterInterface or Twig_SimpleFilter'); + throw new LogicException('A filter must be an instance of Twig_FilterInterface or Twig_SimpleFilter.'); } if ($name instanceof Twig_SimpleFilter) { $filter = $name; $name = $filter->getName(); + } else { + @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleFilter" instead when defining filter "%s".', __METHOD__, $name), E_USER_DEPRECATED); } - + if ($this->extensionInitialized) { throw new LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name)); } - + $this->staging->addFilter($name, $filter); } @@ -789,6 +1119,8 @@ class Twig_Environment * @param string $name The filter name * * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exist + * + * @internal */ public function getFilter($name) { @@ -830,11 +1162,13 @@ class Twig_Environment /** * Gets the registered Filters. * - * Be warned that this method cannot return filters defined with registerUndefinedFunctionCallback. + * 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 + * + * @internal */ public function getFilters() { @@ -854,14 +1188,16 @@ class Twig_Environment public function addTest($name, $test = null) { if (!$name instanceof Twig_SimpleTest && !($test instanceof Twig_SimpleTest || $test instanceof Twig_TestInterface)) { - throw new LogicException('A test must be an instance of Twig_TestInterface or Twig_SimpleTest'); + throw new LogicException('A test must be an instance of Twig_TestInterface or Twig_SimpleTest.'); } if ($name instanceof Twig_SimpleTest) { $test = $name; $name = $test->getName(); + } else { + @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleTest" instead when defining test "%s".', __METHOD__, $name), E_USER_DEPRECATED); } - + if ($this->extensionInitialized) { throw new LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name)); } @@ -872,7 +1208,9 @@ class Twig_Environment /** * Gets the registered Tests. * - * @return Twig_TestInterface[] An array of Twig_TestInterface instances + * @return Twig_TestInterface[] + * + * @internal */ public function getTests() { @@ -889,6 +1227,8 @@ class Twig_Environment * @param string $name The test name * * @return Twig_Test|false A Twig_Test instance or false if the test does not exist + * + * @internal */ public function getTest($name) { @@ -907,23 +1247,25 @@ class Twig_Environment * 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) { if (!$name instanceof Twig_SimpleFunction && !($function instanceof Twig_SimpleFunction || $function instanceof Twig_FunctionInterface)) { - throw new LogicException('A function must be an instance of Twig_FunctionInterface or Twig_SimpleFunction'); + throw new LogicException('A function must be an instance of Twig_FunctionInterface or Twig_SimpleFunction.'); } if ($name instanceof Twig_SimpleFunction) { $function = $name; $name = $function->getName(); + } else { + @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleFunction" instead when defining function "%s".', __METHOD__, $name), E_USER_DEPRECATED); } - + if ($this->extensionInitialized) { throw new LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name)); } - + $this->staging->addFunction($name, $function); } @@ -936,6 +1278,8 @@ class Twig_Environment * @param string $name function name * * @return Twig_Function|false A Twig_Function instance or false if the function does not exist + * + * @internal */ public function getFunction($name) { @@ -979,9 +1323,11 @@ class Twig_Environment * * 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 + * + * @internal */ public function getFunctions() { @@ -1008,11 +1354,11 @@ class Twig_Environment $this->globals = $this->initGlobals(); } - /* This condition must be uncommented in Twig 2.0 if (!array_key_exists($name, $this->globals)) { - throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); + // The deprecation notice must be turned into the following exception in Twig 2.0 + @trigger_error(sprintf('Registering global variable "%s" at runtime or when the extensions have already been initialized is deprecated since version 1.21.', $name), E_USER_DEPRECATED); + //throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); } - */ } if ($this->extensionInitialized || $this->runtimeInitialized) { @@ -1027,6 +1373,8 @@ class Twig_Environment * Gets the registered Globals. * * @return array An array of globals + * + * @internal */ public function getGlobals() { @@ -1065,6 +1413,8 @@ class Twig_Environment * Gets the registered unary Operators. * * @return array An array of unary operators + * + * @internal */ public function getUnaryOperators() { @@ -1079,6 +1429,8 @@ class Twig_Environment * Gets the registered binary Operators. * * @return array An array of binary operators + * + * @internal */ public function getBinaryOperators() { @@ -1089,24 +1441,31 @@ class Twig_Environment return $this->binaryOperators; } + /** + * @deprecated since 1.23 (to be removed in 2.0) + */ public function computeAlternatives($name, $items) { - $alternatives = array(); - foreach ($items as $item) { - $lev = levenshtein($name, $item); - if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { - $alternatives[$item] = $lev; - } - } - asort($alternatives); + @trigger_error(sprintf('The %s method is deprecated since version 1.23 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); - return array_keys($alternatives); + return Twig_Error_Syntax::computeAlternatives($name, $items); } + /** + * @internal + */ protected function initGlobals() { $globals = array(); - foreach ($this->extensions as $extension) { + foreach ($this->extensions as $name => $extension) { + if (!$extension instanceof Twig_Extension_GlobalsInterface) { + $m = new ReflectionMethod($extension, 'getGlobals'); + + if ('Twig_Extension' !== $m->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Defining the getGlobals() method in the "%s" extension without explicitly implementing Twig_Extension_GlobalsInterface is deprecated since version 1.23.', $name), E_USER_DEPRECATED); + } + } + $extGlob = $extension->getGlobals(); if (!is_array($extGlob)) { throw new UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', get_class($extension))); @@ -1120,14 +1479,16 @@ class Twig_Environment return call_user_func_array('array_merge', $globals); } + /** + * @internal + */ protected function initExtensions() { if ($this->extensionInitialized) { return; } - $this->extensionInitialized = true; - $this->parsers = new Twig_TokenParserBroker(); + $this->parsers = new Twig_TokenParserBroker(array(), array(), false); $this->filters = array(); $this->functions = array(); $this->tests = array(); @@ -1139,17 +1500,21 @@ class Twig_Environment $this->initExtension($extension); } $this->initExtension($this->staging); + // Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception + $this->extensionInitialized = true; } + /** + * @internal + */ protected function initExtension(Twig_ExtensionInterface $extension) { // filters foreach ($extension->getFilters() as $name => $filter) { - if ($name instanceof Twig_SimpleFilter) { - $filter = $name; - $name = $filter->getName(); - } elseif ($filter instanceof Twig_SimpleFilter) { + if ($filter instanceof Twig_SimpleFilter) { $name = $filter->getName(); + } else { + @trigger_error(sprintf('Using an instance of "%s" for filter "%s" is deprecated since version 1.21. Use Twig_SimpleFilter instead.', get_class($filter), $name), E_USER_DEPRECATED); } $this->filters[$name] = $filter; @@ -1157,11 +1522,10 @@ class Twig_Environment // functions foreach ($extension->getFunctions() as $name => $function) { - if ($name instanceof Twig_SimpleFunction) { - $function = $name; - $name = $function->getName(); - } elseif ($function instanceof Twig_SimpleFunction) { + if ($function instanceof Twig_SimpleFunction) { $name = $function->getName(); + } else { + @trigger_error(sprintf('Using an instance of "%s" for function "%s" is deprecated since version 1.21. Use Twig_SimpleFunction instead.', get_class($function), $name), E_USER_DEPRECATED); } $this->functions[$name] = $function; @@ -1169,11 +1533,10 @@ class Twig_Environment // tests foreach ($extension->getTests() as $name => $test) { - if ($name instanceof Twig_SimpleTest) { - $test = $name; - $name = $test->getName(); - } elseif ($test instanceof Twig_SimpleTest) { + if ($test instanceof Twig_SimpleTest) { $name = $test->getName(); + } else { + @trigger_error(sprintf('Using an instance of "%s" for test "%s" is deprecated since version 1.21. Use Twig_SimpleTest instead.', get_class($test), $name), E_USER_DEPRECATED); } $this->tests[$name] = $test; @@ -1184,9 +1547,11 @@ class Twig_Environment if ($parser instanceof Twig_TokenParserInterface) { $this->parsers->addTokenParser($parser); } elseif ($parser instanceof Twig_TokenParserBrokerInterface) { + @trigger_error('Registering a Twig_TokenParserBrokerInterface instance is deprecated since version 1.21.', E_USER_DEPRECATED); + $this->parsers->addTokenParserBroker($parser); } else { - throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances'); + throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances.'); } } @@ -1197,8 +1562,12 @@ class Twig_Environment // operators if ($operators = $extension->getOperators()) { + if (!is_array($operators)) { + throw new InvalidArgumentException(sprintf('"%s::getOperators()" must return an array with operators, got "%s".', get_class($extension), is_object($operators) ? get_class($operators) : gettype($operators).(is_resource($operators) ? '' : '#'.$operators))); + } + if (2 !== count($operators)) { - throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension))); + throw new InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', get_class($extension), count($operators))); } $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); @@ -1206,27 +1575,30 @@ class Twig_Environment } } + /** + * @deprecated since 1.22 (to be removed in 2.0) + */ protected function writeCacheFile($file, $content) { - $dir = dirname($file); - if (!is_dir($dir)) { - if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) { - throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir)); - } - } elseif (!is_writable($dir)) { - throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir)); - } - - $tmpFile = tempnam(dirname($file), basename($file)); - if (false !== @file_put_contents($tmpFile, $content)) { - // rename does not work on Win32 before 5.2.6 - if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) { - @chmod($file, 0666 & ~umask()); - - return; - } - } + $this->cache->write($file, $content); + } - throw new RuntimeException(sprintf('Failed to write cache file "%s".', $file)); + private function updateOptionsHash() + { + $hashParts = array_merge( + array_keys($this->extensions), + array( + (int) function_exists('twig_template_get_attributes'), + PHP_MAJOR_VERSION, + PHP_MINOR_VERSION, + self::VERSION, + (int) $this->debug, + $this->baseTemplateClass, + (int) $this->strictVariables, + ) + ); + $this->optionsHash = implode(':', $hashParts); } } + +class_alias('Twig_Environment', 'Twig\Environment', false); diff --git a/inc/lib/Twig/Error.php b/inc/lib/Twig/Error.php index 61a4cfa0..787e0d09 100644 --- a/inc/lib/Twig/Error.php +++ b/inc/lib/Twig/Error.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -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,30 +34,44 @@ class Twig_Error extends Exception { protected $lineno; + // to be renamed to name in 2.0 protected $filename; protected $rawMessage; protected $previous; + private $sourcePath; + private $sourceCode; + /** * 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 integer $lineno The template line where the error occurred - * @param string $filename The template file name where the error occurred - * @param Exception $previous The previous exception + * @param string $message The error message + * @param int $lineno The template line where the error occurred + * @param Twig_Source|string|null $source The source context 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, $source = null, Exception $previous = null) { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { + if (null === $source) { + $name = null; + } elseif (!$source instanceof Twig_Source) { + // for compat with the Twig C ext., passing the template name as string is accepted + $name = $source; + } else { + $name = $source->getName(); + $this->sourceCode = $source->getCode(); + $this->sourcePath = $source->getPath(); + } + if (PHP_VERSION_ID < 50300) { $this->previous = $previous; parent::__construct(''); } else { @@ -65,9 +79,9 @@ class Twig_Error extends Exception } $this->lineno = $lineno; - $this->filename = $filename; + $this->filename = $name; - if (-1 === $this->lineno || null === $this->filename) { + if (-1 === $lineno || null === $name || null === $this->sourcePath) { $this->guessTemplateInfo(); } @@ -87,23 +101,62 @@ class Twig_Error extends Exception } /** - * Gets the filename where the error occurred. + * Gets the logical name where the error occurred. + * + * @return string The name * - * @return string The filename + * @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead. */ public function getTemplateFile() { + @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->filename; } /** - * Sets the filename where the error occurred. + * Sets the logical name where the error occurred. + * + * @param string $name The name * - * @param string $filename The filename + * @deprecated since 1.27 (to be removed in 2.0). Use setSourceContext() instead. */ - public function setTemplateFile($filename) + public function setTemplateFile($name) { - $this->filename = $filename; + @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + $this->filename = $name; + + $this->updateRepr(); + } + + /** + * Gets the logical name where the error occurred. + * + * @return string The name + * + * @deprecated since 1.29 (to be removed in 2.0). Use getSourceContext() instead. + */ + public function getTemplateName() + { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->filename; + } + + /** + * Sets the logical name where the error occurred. + * + * @param string $name The name + * + * @deprecated since 1.29 (to be removed in 2.0). Use setSourceContext() instead. + */ + public function setTemplateName($name) + { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + $this->filename = $name; + $this->sourceCode = $this->sourcePath = null; $this->updateRepr(); } @@ -111,7 +164,7 @@ class Twig_Error extends Exception /** * Gets the template line where the error occurred. * - * @return integer The template line + * @return int The template line */ public function getTemplateLine() { @@ -121,7 +174,7 @@ class Twig_Error extends Exception /** * Sets the template line where the error occurred. * - * @param integer $lineno The template line + * @param int $lineno The template line */ public function setTemplateLine($lineno) { @@ -130,6 +183,32 @@ class Twig_Error extends Exception $this->updateRepr(); } + /** + * Gets the source context of the Twig template where the error occurred. + * + * @return Twig_Source|null + */ + public function getSourceContext() + { + return $this->filename ? new Twig_Source($this->sourceCode, $this->filename, $this->sourcePath) : null; + } + + /** + * Sets the source context of the Twig template where the error occurred. + */ + public function setSourceContext(Twig_Source $source = null) + { + if (null === $source) { + $this->sourceCode = $this->filename = $this->sourcePath = null; + } else { + $this->sourceCode = $source->getCode(); + $this->filename = $source->getName(); + $this->sourcePath = $source->getPath(); + } + + $this->updateRepr(); + } + public function guess() { $this->guessTemplateInfo(); @@ -155,23 +234,45 @@ class Twig_Error extends Exception throw new BadMethodCallException(sprintf('Method "Twig_Error::%s()" does not exist.', $method)); } + public function appendMessage($rawMessage) + { + $this->rawMessage .= $rawMessage; + $this->updateRepr(); + } + + /** + * @internal + */ protected function updateRepr() { $this->message = $this->rawMessage; + if ($this->sourcePath && $this->lineno > 0) { + $this->file = $this->sourcePath; + $this->line = $this->lineno; + + return; + } + $dot = false; if ('.' === substr($this->message, -1)) { $this->message = substr($this->message, 0, -1); $dot = true; } + $questionMark = false; + if ('?' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $questionMark = true; + } + 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) { @@ -181,14 +282,21 @@ class Twig_Error extends Exception if ($dot) { $this->message .= '.'; } + + if ($questionMark) { + $this->message .= '?'; + } } + /** + * @internal + */ protected function guessTemplateInfo() { $template = null; $templateClass = null; - if (version_compare(phpversion(), '5.3.6', '>=')) { + if (PHP_VERSION_ID >= 50306) { $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT); } else { $backtrace = debug_backtrace(); @@ -205,11 +313,18 @@ class Twig_Error extends Exception } } - // update template filename + // update template name if (null !== $template && null === $this->filename) { $this->filename = $template->getTemplateName(); } + // update template path if any + if (null !== $template && null === $this->sourcePath) { + $src = $template->getSourceContext(); + $this->sourceCode = $src->getCode(); + $this->sourcePath = $src->getPath(); + } + if (null === $template || $this->lineno > -1) { return; } @@ -224,6 +339,8 @@ class Twig_Error extends Exception while ($e = array_pop($exceptions)) { $traces = $e->getTrace(); + array_unshift($traces, array('file' => $e->getFile(), 'line' => $e->getLine())); + while ($trace = array_shift($traces)) { if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) { continue; @@ -241,3 +358,6 @@ class Twig_Error extends Exception } } } + +class_alias('Twig_Error', 'Twig\Error\Error', false); +class_exists('Twig_Source'); diff --git a/inc/lib/Twig/Error/Loader.php b/inc/lib/Twig/Error/Loader.php index 68efb574..df566dd7 100644 --- a/inc/lib/Twig/Error/Loader.php +++ b/inc/lib/Twig/Error/Loader.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -24,8 +24,17 @@ */ class Twig_Error_Loader extends Twig_Error { - public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null) + public function __construct($message, $lineno = -1, $source = null, Exception $previous = null) { - parent::__construct($message, false, false, $previous); + if (PHP_VERSION_ID < 50300) { + $this->previous = $previous; + Exception::__construct(''); + } else { + Exception::__construct('', 0, $previous); + } + $this->appendMessage($message); + $this->setTemplateLine(false); } } + +class_alias('Twig_Error_Loader', 'Twig\Error\LoaderError', false); diff --git a/inc/lib/Twig/Error/Runtime.php b/inc/lib/Twig/Error/Runtime.php index 8b6ceddb..3b24ad3a 100644 --- a/inc/lib/Twig/Error/Runtime.php +++ b/inc/lib/Twig/Error/Runtime.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -18,3 +18,5 @@ class Twig_Error_Runtime extends Twig_Error { } + +class_alias('Twig_Error_Runtime', 'Twig\Error\RuntimeError', false); diff --git a/inc/lib/Twig/Error/Syntax.php b/inc/lib/Twig/Error/Syntax.php index 0f5c5792..9d09f217 100644 --- a/inc/lib/Twig/Error/Syntax.php +++ b/inc/lib/Twig/Error/Syntax.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,4 +17,39 @@ */ class Twig_Error_Syntax extends Twig_Error { + /** + * Tweaks the error message to include suggestions. + * + * @param string $name The original name of the item that does not exist + * @param array $items An array of possible items + */ + public function addSuggestions($name, array $items) + { + if (!$alternatives = self::computeAlternatives($name, $items)) { + return; + } + + $this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', $alternatives))); + } + + /** + * @internal + * + * To be merged with the addSuggestions() method in 2.0. + */ + public static function computeAlternatives($name, $items) + { + $alternatives = array(); + foreach ($items as $item) { + $lev = levenshtein($name, $item); + if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = $lev; + } + } + asort($alternatives); + + return array_keys($alternatives); + } } + +class_alias('Twig_Error_Syntax', 'Twig\Error\SyntaxError', false); diff --git a/inc/lib/Twig/ExistsLoaderInterface.php b/inc/lib/Twig/ExistsLoaderInterface.php index ce434765..968cb21a 100644 --- a/inc/lib/Twig/ExistsLoaderInterface.php +++ b/inc/lib/Twig/ExistsLoaderInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,7 +13,8 @@ * Adds an exists() method for loaders. * * @author Florin Patan - * @deprecated since 1.12 (to be removed in 2.0) + * + * @deprecated since 1.12 (to be removed in 3.0) */ interface Twig_ExistsLoaderInterface { @@ -22,7 +23,9 @@ interface Twig_ExistsLoaderInterface * * @param string $name The name of the template to check if we can load * - * @return boolean If the template source code is handled by this loader or not + * @return bool If the template source code is handled by this loader or not */ public function exists($name); } + +class_alias('Twig_ExistsLoaderInterface', 'Twig\Loader\ExistsLoaderInterface', false); diff --git a/inc/lib/Twig/ExpressionParser.php b/inc/lib/Twig/ExpressionParser.php index 9deab09c..fe4a9b4a 100644 --- a/inc/lib/Twig/ExpressionParser.php +++ b/inc/lib/Twig/ExpressionParser.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,10 +15,12 @@ * * This parser implements a "Precedence climbing" algorithm. * - * @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm - * @see http://en.wikipedia.org/wiki/Operator-precedence_parser + * @see https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm + * @see https://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, $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 @@ class Twig_ExpressionParser $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']); @@ -86,18 +104,15 @@ class Twig_ExpressionParser protected function parseConditionalExpression($expr) { - while ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '?')) { - $this->parser->getStream()->next(); - if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) { + while ($this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, '?')) { + if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) { $expr2 = $this->parseExpression(); - if ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) { - $this->parser->getStream()->next(); + if ($this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) { $expr3 = $this->parseExpression(); } else { $expr3 = new Twig_Node_Expression_Constant('', $this->parser->getCurrentToken()->getLine()); } } else { - $this->parser->getStream()->next(); $expr2 = $expr; $expr3 = $this->parseExpression(); } @@ -161,13 +176,39 @@ class Twig_ExpressionParser $node = $this->parseStringExpression(); break; + case Twig_Token::OPERATOR_TYPE: + if (preg_match(Twig_Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) { + // in this context, string operators are variable names + $this->parser->getStream()->next(); + $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine()); + break; + } elseif (isset($this->unaryOperators[$token->getValue()])) { + $class = $this->unaryOperators[$token->getValue()]['class']; + + $ref = new ReflectionClass($class); + $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->getStream()->getSourceContext()); + } + + $this->parser->getStream()->next(); + $expr = $this->parsePrimaryExpression(); + + $node = new $class($expr, $token->getLine()); + break; + } + + // no break default: if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) { $node = $this->parseArrayExpression(); } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) { $node = $this->parseHashExpression(); + } elseif ($token->test(Twig_Token::OPERATOR_TYPE, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) { + throw new Twig_Error_Syntax(sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); } else { - throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $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()); } } @@ -182,12 +223,10 @@ class Twig_ExpressionParser // a string cannot be followed by another string in a single expression $nextCanBeString = true; while (true) { - if ($stream->test(Twig_Token::STRING_TYPE) && $nextCanBeString) { - $token = $stream->next(); + if ($nextCanBeString && $token = $stream->nextIf(Twig_Token::STRING_TYPE)) { $nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); $nextCanBeString = false; - } elseif ($stream->test(Twig_Token::INTERPOLATION_START_TYPE)) { - $stream->next(); + } elseif ($stream->nextIf(Twig_Token::INTERPOLATION_START_TYPE)) { $nodes[] = $this->parseExpression(); $stream->expect(Twig_Token::INTERPOLATION_END_TYPE); $nextCanBeString = true; @@ -198,7 +237,7 @@ class Twig_ExpressionParser $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; @@ -253,15 +292,14 @@ class Twig_ExpressionParser // * a string -- 'a' // * a name, which is equivalent to a string -- a // * an expression, which must be enclosed in parentheses -- (1 + 2) - if ($stream->test(Twig_Token::STRING_TYPE) || $stream->test(Twig_Token::NAME_TYPE) || $stream->test(Twig_Token::NUMBER_TYPE)) { - $token = $stream->next(); + if (($token = $stream->nextIf(Twig_Token::STRING_TYPE)) || ($token = $stream->nextIf(Twig_Token::NAME_TYPE)) || $token = $stream->nextIf(Twig_Token::NUMBER_TYPE)) { $key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); } elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { $key = $this->parseExpression(); } 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->getLine()), $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()); } $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); @@ -278,7 +316,7 @@ class Twig_ExpressionParser { while (true) { $token = $this->parser->getCurrentToken(); - if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) { + if (Twig_Token::PUNCTUATION_TYPE == $token->getType()) { if ('.' == $token->getValue() || '[' == $token->getValue()) { $node = $this->parseSubscriptExpression($node); } elseif ('|' == $token->getValue()) { @@ -298,41 +336,46 @@ class Twig_ExpressionParser { switch ($name) { case 'parent': - $args = $this->parseArguments(); + $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()); } 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()); } 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()); + } + + 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()); } - return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_Template::ANY_CALL, $line); + return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : null, Twig_Template::ANY_CALL, $line); default: - $args = $this->parseArguments(true); - if (null !== $alias = $this->parser->getImportedSymbol('macro', $name)) { - return new Twig_Node_Expression_MacroCall($alias['node'], $alias['name'], $this->createArrayFromArguments($args), $line); - } - - try { - $class = $this->getFunctionNodeClass($name, $line); - } catch (Twig_Error_Syntax $e) { - if (!$this->parser->hasMacro($name)) { - throw $e; + if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) { + $arguments = new Twig_Node_Expression_Array(array(), $line); + foreach ($this->parseArguments() as $n) { + $arguments->addElement($n); } - return new Twig_Node_Expression_MacroCall(new Twig_Node_Expression_Name('_self', $line), $name, $this->createArrayFromArguments($args), $line); + $node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line); + $node->setAttribute('safe', true); + + return $node; } + $args = $this->parseArguments(true); + $class = $this->getFunctionNodeClass($name, $line); + return new $class($name, $args, $line); } } @@ -344,33 +387,42 @@ class Twig_ExpressionParser $lineno = $token->getLine(); $arguments = new Twig_Node_Expression_Array(array(), $lineno); $type = Twig_Template::ANY_CALL; - if ($token->getValue() == '.') { + if ('.' == $token->getValue()) { $token = $stream->next(); if ( - $token->getType() == Twig_Token::NAME_TYPE + Twig_Token::NAME_TYPE == $token->getType() || - $token->getType() == Twig_Token::NUMBER_TYPE + Twig_Token::NUMBER_TYPE == $token->getType() || - ($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue())) + (Twig_Token::OPERATOR_TYPE == $token->getType() && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue())) ) { $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno); + + if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { + $type = Twig_Template::METHOD_CALL; + foreach ($this->parseArguments() as $n) { + $arguments->addElement($n); + } + } } 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()); } 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()); } - $arguments = $this->createArrayFromArguments($this->parseArguments(true)); + $name = $arg->getAttribute('value'); - return new Twig_Node_Expression_MacroCall($node, $arg->getAttribute('value'), $arguments, $lineno); - } + 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(), $stream->getSourceContext()); + } + + $node = new Twig_Node_Expression_MethodCall($node, 'get'.$name, $arguments, $lineno); + $node->setAttribute('safe', true); - if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { - $type = Twig_Template::METHOD_CALL; - $arguments = $this->createArrayFromArguments($this->parseArguments()); + return $node; } } else { $type = Twig_Template::ARRAY_CALL; @@ -384,9 +436,8 @@ class Twig_ExpressionParser $arg = $this->parseExpression(); } - if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) { + if ($stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) { $slice = true; - $stream->next(); } if ($slice) { @@ -447,10 +498,12 @@ class Twig_ExpressionParser /** * Parses arguments. * - * @param Boolean $namedArguments Whether to allow named arguments or not - * @param Boolean $definition Whether we are parsing arguments for a function definition + * @param bool $namedArguments Whether to allow named arguments or not + * @param bool $definition Whether we are parsing arguments for a function definition * * @return Twig_Node + * + * @throws Twig_Error_Syntax */ public function parseArguments($namedArguments = false, $definition = false) { @@ -471,10 +524,9 @@ class Twig_ExpressionParser } $name = null; - if ($namedArguments && $stream->test(Twig_Token::OPERATOR_TYPE, '=')) { - $token = $stream->next(); + if ($namedArguments && $token = $stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) { if (!$value instanceof Twig_Node_Expression_Name) { - throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given', get_class($value)), $token->getLine(), $this->parser->getFilename()); + throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given.', get_class($value)), $token->getLine(), $stream->getSourceContext()); } $name = $value->getAttribute('name'); @@ -482,26 +534,25 @@ class Twig_ExpressionParser $value = $this->parsePrimaryExpression(); if (!$this->checkConstantExpression($value)) { - throw new Twig_Error_Syntax('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()); } } else { $value = $this->parseExpression(); } } - if ($definition && null === $name) { - $name = $value->getAttribute('name'); - $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine()); - } - - if (null === $name) { - $args[] = $value; - } else { - if ($definition && isset($args[$name])) { - throw new Twig_Error_Syntax(sprintf('Arguments cannot contain the same argument name more than once ("%s" is defined twice).', $name), $token->getLine(), $this->parser->getFilename()); + if ($definition) { + if (null === $name) { + $name = $value->getAttribute('name'); + $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine()); } - $args[$name] = $value; + } else { + if (null === $name) { + $args[] = $value; + } else { + $args[$name] = $value; + } } } $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); @@ -511,18 +562,19 @@ class Twig_ExpressionParser 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'); - if (in_array($token->getValue(), array('true', 'false', 'none'))) { - throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine(), $this->parser->getFilename()); + $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(), $stream->getSourceContext()); } - $targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine()); + $targets[] = new Twig_Node_Expression_AssignName($value, $token->getLine()); - if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) { + if (!$stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) { break; } - $this->parser->getStream()->next(); } return new Twig_Node($targets); @@ -533,26 +585,104 @@ class Twig_ExpressionParser $targets = array(); while (true) { $targets[] = $this->parseExpression(); - if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) { + if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) { break; } - $this->parser->getStream()->next(); } return new Twig_Node($targets); } + private function parseNotTestExpression(Twig_NodeInterface $node) + { + 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); + } + + 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()); + $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()); + } + $src = $stream->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->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) { - $env = $this->parser->getEnvironment(); + if (false === $function = $this->env->getFunction($name)) { + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getFunctions())); - if (false === $function = $env->getFunction($name)) { - $message = sprintf('The function "%s" does not exist', $name); - if ($alternatives = $env->computeAlternatives($name, array_keys($env->getFunctions()))) { - $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives)); + throw $e; + } + + if ($function instanceof Twig_SimpleFunction && $function->isDeprecated()) { + $message = sprintf('Twig Function "%s" is deprecated', $function->getName()); + if (!is_bool($function->getDeprecatedVersion())) { + $message .= sprintf(' since version %s', $function->getDeprecatedVersion()); + } + if ($function->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $function->getAlternative()); } + $src = $this->parser->getStream()->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line); - throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename()); + @trigger_error($message, E_USER_DEPRECATED); } if ($function instanceof Twig_SimpleFunction) { @@ -564,15 +694,25 @@ class Twig_ExpressionParser protected function getFilterNodeClass($name, $line) { - $env = $this->parser->getEnvironment(); + if (false === $filter = $this->env->getFilter($name)) { + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getFilters())); - if (false === $filter = $env->getFilter($name)) { - $message = sprintf('The filter "%s" does not exist', $name); - if ($alternatives = $env->computeAlternatives($name, array_keys($env->getFilters()))) { - $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives)); + throw $e; + } + + if ($filter instanceof Twig_SimpleFilter && $filter->isDeprecated()) { + $message = sprintf('Twig Filter "%s" is deprecated', $filter->getName()); + if (!is_bool($filter->getDeprecatedVersion())) { + $message .= sprintf(' since version %s', $filter->getDeprecatedVersion()); + } + if ($filter->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $filter->getAlternative()); } + $src = $this->parser->getStream()->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line); - throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename()); + @trigger_error($message, E_USER_DEPRECATED); } if ($filter instanceof Twig_SimpleFilter) { @@ -585,7 +725,9 @@ class Twig_ExpressionParser // checks that the node only contains "constant" elements protected function checkConstantExpression(Twig_NodeInterface $node) { - if (!($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array)) { + if (!($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array + || $node instanceof Twig_Node_Expression_Unary_Neg || $node instanceof Twig_Node_Expression_Unary_Pos + )) { return false; } @@ -597,15 +739,6 @@ class Twig_ExpressionParser return true; } - - private function createArrayFromArguments(Twig_Node $arguments, $line = null) - { - $line = null === $line ? $arguments->getLine() : $line; - $array = new Twig_Node_Expression_Array(array(), $line); - foreach ($arguments as $key => $value) { - $array->addElement($value, new Twig_Node_Expression_Constant($key, $value->getLine())); - } - - return $array; - } } + +class_alias('Twig_ExpressionParser', 'Twig\ExpressionParser', false); diff --git a/inc/lib/Twig/Extension.php b/inc/lib/Twig/Extension.php index 931fc033..38084495 100644 --- a/inc/lib/Twig/Extension.php +++ b/inc/lib/Twig/Extension.php @@ -3,91 +3,67 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + abstract class Twig_Extension implements Twig_ExtensionInterface { /** - * Initializes the runtime environment. - * - * This is where you can load some file that contains filter functions for instance. - * - * @param Twig_Environment $environment The current Twig_Environment instance + * @deprecated since 1.23 (to be removed in 2.0), implement Twig_Extension_InitRuntimeInterface instead */ public function initRuntime(Twig_Environment $environment) { } - /** - * Returns the token parser instances to add to the existing list. - * - * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances - */ public function getTokenParsers() { return array(); } - /** - * Returns the node visitor instances to add to the existing list. - * - * @return array An array of Twig_NodeVisitorInterface instances - */ public function getNodeVisitors() { return array(); } - /** - * Returns a list of filters to add to the existing list. - * - * @return array An array of filters - */ public function getFilters() { return array(); } - /** - * Returns a list of tests to add to the existing list. - * - * @return array An array of tests - */ public function getTests() { return array(); } - /** - * Returns a list of functions to add to the existing list. - * - * @return array An array of functions - */ public function getFunctions() { return array(); } - /** - * Returns a list of operators to add to the existing list. - * - * @return array An array of operators - */ public function getOperators() { return array(); } /** - * Returns a list of global variables to add to the existing list. - * - * @return array An array of global variables + * @deprecated since 1.23 (to be removed in 2.0), implement Twig_Extension_GlobalsInterface instead */ public function getGlobals() { return array(); } + + /** + * @deprecated since 1.26 (to be removed in 2.0), not used anymore internally + */ + public function getName() + { + return get_class($this); + } } + +class_alias('Twig_Extension', 'Twig\Extension\AbstractExtension', false); +class_exists('Twig_Environment'); diff --git a/inc/lib/Twig/Extension/Core.php b/inc/lib/Twig/Extension/Core.php index 60fe1936..6fa78e13 100644 --- a/inc/lib/Twig/Extension/Core.php +++ b/inc/lib/Twig/Extension/Core.php @@ -7,16 +7,42 @@ if (!defined('ENT_SUBSTITUTE')) { /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + +/** + * @final + */ class Twig_Extension_Core extends Twig_Extension { protected $dateFormats = array('F j, Y H:i', '%d days'); protected $numberFormat = array(0, '.', ','); protected $timezone = null; + protected $escapers = array(); + + /** + * Defines a new escaper to be used via the escape filter. + * + * @param string $strategy The strategy name that should be used as a strategy in the escape call + * @param callable $callable A valid PHP callable + */ + public function setEscaper($strategy, $callable) + { + $this->escapers[$strategy] = $callable; + } + + /** + * Gets all defined escapers. + * + * @return array An array of escapers + */ + public function getEscapers() + { + return $this->escapers; + } /** * Sets the default format to be used by the date filter. @@ -72,9 +98,9 @@ class Twig_Extension_Core extends Twig_Extension /** * Sets the default format to be used by the number_format filter. * - * @param integer $decimal The number of decimal places to use. - * @param string $decimalPoint The character(s) to use for the decimal point. - * @param string $thousandSep The character(s) to use for the thousands separator. + * @param int $decimal the number of decimal places to use + * @param string $decimalPoint the character(s) to use for the decimal point + * @param string $thousandSep the character(s) to use for the thousands separator */ public function setNumberFormat($decimal, $decimalPoint, $thousandSep) { @@ -91,11 +117,6 @@ class Twig_Extension_Core extends Twig_Extension return $this->numberFormat; } - /** - * Returns the token parser instance to add to the existing list. - * - * @return array An array of Twig_TokenParser instances - */ public function getTokenParsers() { return array( @@ -114,14 +135,10 @@ class Twig_Extension_Core extends Twig_Extension new Twig_TokenParser_Flush(), new Twig_TokenParser_Do(), new Twig_TokenParser_Embed(), + new Twig_TokenParser_With(), ); } - /** - * Returns a list of filters to add to the existing list. - * - * @return array An array of filters - */ public function getFilters() { $filters = array( @@ -129,9 +146,10 @@ class Twig_Extension_Core extends Twig_Extension new Twig_SimpleFilter('date', 'twig_date_format_filter', array('needs_environment' => true)), new Twig_SimpleFilter('date_modify', 'twig_date_modify_filter', array('needs_environment' => true)), new Twig_SimpleFilter('format', 'sprintf'), - new Twig_SimpleFilter('replace', 'strtr'), + new Twig_SimpleFilter('replace', 'twig_replace_filter'), new Twig_SimpleFilter('number_format', 'twig_number_format_filter', array('needs_environment' => true)), new Twig_SimpleFilter('abs', 'abs'), + new Twig_SimpleFilter('round', 'twig_round'), // encoding new Twig_SimpleFilter('url_encode', 'twig_urlencode_filter'), @@ -144,12 +162,12 @@ class Twig_Extension_Core extends Twig_Extension new Twig_SimpleFilter('upper', 'strtoupper'), new Twig_SimpleFilter('lower', 'strtolower'), new Twig_SimpleFilter('striptags', 'strip_tags'), - new Twig_SimpleFilter('trim', 'trim'), + new Twig_SimpleFilter('trim', 'twig_trim_filter'), new Twig_SimpleFilter('nl2br', 'nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))), // array helpers new Twig_SimpleFilter('join', 'twig_join_filter'), - new Twig_SimpleFilter('split', 'twig_split_filter'), + new Twig_SimpleFilter('split', 'twig_split_filter', array('needs_environment' => true)), new Twig_SimpleFilter('sort', 'twig_sort_filter'), new Twig_SimpleFilter('merge', 'twig_array_merge'), new Twig_SimpleFilter('batch', 'twig_array_batch'), @@ -178,130 +196,80 @@ class Twig_Extension_Core extends Twig_Extension return $filters; } - /** - * Returns a list of global functions to add to the existing list. - * - * @return array An array of global functions - */ public function getFunctions() { return array( + new Twig_SimpleFunction('max', 'max'), + new Twig_SimpleFunction('min', 'min'), new Twig_SimpleFunction('range', 'range'), new Twig_SimpleFunction('constant', 'twig_constant'), new Twig_SimpleFunction('cycle', 'twig_cycle'), new Twig_SimpleFunction('random', 'twig_random', array('needs_environment' => true)), new Twig_SimpleFunction('date', 'twig_date_converter', array('needs_environment' => true)), new Twig_SimpleFunction('include', 'twig_include', array('needs_environment' => true, 'needs_context' => true, 'is_safe' => array('all'))), + new Twig_SimpleFunction('source', 'twig_source', array('needs_environment' => true, 'is_safe' => array('all'))), ); } - /** - * Returns a list of tests to add to the existing list. - * - * @return array An array of tests - */ public function getTests() { return array( new Twig_SimpleTest('even', null, array('node_class' => 'Twig_Node_Expression_Test_Even')), new Twig_SimpleTest('odd', null, array('node_class' => 'Twig_Node_Expression_Test_Odd')), new Twig_SimpleTest('defined', null, array('node_class' => 'Twig_Node_Expression_Test_Defined')), - new Twig_SimpleTest('sameas', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas')), + new Twig_SimpleTest('sameas', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas', 'deprecated' => '1.21', 'alternative' => 'same as')), + new Twig_SimpleTest('same as', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas')), new Twig_SimpleTest('none', null, array('node_class' => 'Twig_Node_Expression_Test_Null')), new Twig_SimpleTest('null', null, array('node_class' => 'Twig_Node_Expression_Test_Null')), - new Twig_SimpleTest('divisibleby', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby')), + new Twig_SimpleTest('divisibleby', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby', 'deprecated' => '1.21', 'alternative' => 'divisible by')), + new Twig_SimpleTest('divisible by', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby')), new Twig_SimpleTest('constant', null, array('node_class' => 'Twig_Node_Expression_Test_Constant')), new Twig_SimpleTest('empty', 'twig_test_empty'), new Twig_SimpleTest('iterable', 'twig_test_iterable'), ); } - /** - * Returns a list of operators to add to the existing list. - * - * @return array An array of operators - */ public function getOperators() { return array( array( 'not' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'), - '-' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Neg'), - '+' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'), + '-' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Neg'), + '+' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'), ), array( - 'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'b-or' => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'b-xor' => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'b-and' => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'b-or' => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'b-xor' => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'b-and' => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '..' => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - '**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT), + 'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'matches' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Matches', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'starts with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_StartsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'ends with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_EndsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '..' => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'is' => array('precedence' => 100, '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, $node) - { - return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine()); - } - - public function parseTestExpression(Twig_Parser $parser, $node) - { - $stream = $parser->getStream(); - $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); - $arguments = null; - if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { - $arguments = $parser->getExpressionParser()->parseArguments(true); - } - - $class = $this->getTestNodeClass($parser, $name, $node->getLine()); - - return new $class($node, $name, $arguments, $parser->getCurrentToken()->getLine()); - } - - protected function getTestNodeClass(Twig_Parser $parser, $name, $line) - { - $env = $parser->getEnvironment(); - $testMap = $env->getTests(); - if (!isset($testMap[$name])) { - $message = sprintf('The test "%s" does not exist', $name); - if ($alternatives = $env->computeAlternatives($name, array_keys($env->getTests()))) { - $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives)); - } - - throw new Twig_Error_Syntax($message, $line, $parser->getFilename()); - } - - if ($testMap[$name] instanceof Twig_SimpleTest) { - return $testMap[$name]->getNodeClass(); - } - - return $testMap[$name] instanceof Twig_Test_Node ? $testMap[$name]->getClass() : 'Twig_Node_Expression_Test'; - } - - /** - * Returns the name of the extension. - * - * @return string The extension name - */ public function getName() { return 'core'; @@ -311,8 +279,8 @@ class Twig_Extension_Core extends Twig_Extension /** * Cycles over a value. * - * @param ArrayAccess|array $values An array or an ArrayAccess instance - * @param integer $position The cycle position + * @param ArrayAccess|array $values + * @param int $position The cycle position * * @return string The next value in the cycle */ @@ -329,12 +297,12 @@ function twig_cycle($values, $position) * Returns a random value depending on the supplied parameter type: * - a random item from a Traversable or array * - a random character from a string - * - a random integer between 0 and the integer parameter + * - a random integer between 0 and the integer parameter. * - * @param Twig_Environment $env A Twig_Environment instance - * @param Traversable|array|integer|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). + * @throws Twig_Error_Runtime when $values is an empty array (does not apply to an empty string which is returned as is) * * @return mixed A random value from the given sequence */ @@ -348,14 +316,14 @@ function twig_random(Twig_Environment $env, $values = null) return $values < 0 ? mt_rand($values, 0) : mt_rand(0, $values); } - if (is_object($values) && $values instanceof Traversable) { + if ($values instanceof Traversable) { $values = iterator_to_array($values); } elseif (is_string($values)) { if ('' === $values) { return ''; } if (null !== $charset = $env->getCharset()) { - if ('UTF-8' != $charset) { + if ('UTF-8' !== $charset) { $values = twig_convert_encoding($values, 'UTF-8', $charset); } @@ -363,7 +331,7 @@ function twig_random(Twig_Environment $env, $values = null) // split at all positions, but not after the start and not before the end $values = preg_split('/(? $value) { $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8'); } @@ -391,17 +359,17 @@ function twig_random(Twig_Environment $env, $values = null) * {{ post.published_at|date("m/d/Y") }} * * - * @param Twig_Environment $env A Twig_Environment instance - * @param DateTime|DateInterval|string $date A date - * @param string $format A format - * @param DateTimeZone|string $timezone A timezone + * @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 * * @return string The formatted date */ function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $timezone = null) { if (null === $format) { - $formats = $env->getExtension('core')->getDateFormat(); + $formats = $env->getExtension('Twig_Extension_Core')->getDateFormat(); $format = $date instanceof DateInterval ? $formats[1] : $formats[0]; } @@ -413,24 +381,27 @@ function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $ } /** - * Returns a new date object modified + * Returns a new date object modified. * *
  *   {{ post.published_at|date_modify("-1day")|date("m/d/Y") }}
  * 
* - * @param Twig_Environment $env A Twig_Environment instance - * @param DateTime|string $date A date - * @param string $modifier A modifier string + * @param Twig_Environment $env + * @param DateTime|string $date A date + * @param string $modifier A modifier string * * @return DateTime A new date object */ function twig_date_modify_filter(Twig_Environment $env, $date, $modifier) { $date = twig_date_converter($env, $date, false); - $date->modify($modifier); + $resultDate = $date->modify($modifier); - return $date; + // This is a hack to ensure PHP 5.2 support and support for DateTimeImmutable + // DateTime::modify does not return the modified DateTime object < 5.3.0 + // and DateTimeImmutable does not modify $date. + return null === $resultDate ? $date : $resultDate; } /** @@ -442,45 +413,101 @@ function twig_date_modify_filter(Twig_Environment $env, $date, $modifier) * {% endif %} * * - * @param Twig_Environment $env A Twig_Environment instance - * @param DateTime|string $date A date - * @param DateTimeZone|string $timezone A timezone + * @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 * * @return DateTime A DateTime instance */ function twig_date_converter(Twig_Environment $env, $date = null, $timezone = null) { // determine the timezone - if (!$timezone) { - $defaultTimezone = $env->getExtension('core')->getTimezone(); - } elseif (!$timezone instanceof DateTimeZone) { - $defaultTimezone = new DateTimeZone($timezone); - } else { - $defaultTimezone = $timezone; + if (false !== $timezone) { + if (null === $timezone) { + $timezone = $env->getExtension('Twig_Extension_Core')->getTimezone(); + } elseif (!$timezone instanceof DateTimeZone) { + $timezone = new DateTimeZone($timezone); + } } - if ($date instanceof DateTime) { + // immutable dates + if ($date instanceof DateTimeImmutable) { + return false !== $timezone ? $date->setTimezone($timezone) : $date; + } + + if ($date instanceof DateTime || $date instanceof DateTimeInterface) { $date = clone $date; if (false !== $timezone) { - $date->setTimezone($defaultTimezone); + $date->setTimezone($timezone); } return $date; } + if (null === $date || 'now' === $date) { + return new DateTime($date, false !== $timezone ? $timezone : $env->getExtension('Twig_Extension_Core')->getTimezone()); + } + $asString = (string) $date; if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) { - $date = '@'.$date; + $date = new DateTime('@'.$date); + } else { + $date = new DateTime($date, $env->getExtension('Twig_Extension_Core')->getTimezone()); } - $date = new DateTime($date, $defaultTimezone); if (false !== $timezone) { - $date->setTimezone($defaultTimezone); + $date->setTimezone($timezone); } return $date; } +/** + * Replaces strings within a string. + * + * @param string $str String to replace in + * @param array|Traversable $from Replace values + * @param string|null $to Replace to, deprecated (@see https://secure.php.net/manual/en/function.strtr.php) + * + * @return string + */ +function twig_replace_filter($str, $from, $to = null) +{ + if ($from instanceof Traversable) { + $from = iterator_to_array($from); + } elseif (is_string($from) && is_string($to)) { + @trigger_error('Using "replace" with character by character replacement is deprecated since version 1.22 and will be removed in Twig 2.0', E_USER_DEPRECATED); + + return strtr($str, $from, $to); + } elseif (!is_array($from)) { + throw new Twig_Error_Runtime(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".', is_object($from) ? get_class($from) : gettype($from))); + } + + return strtr($str, $from); +} + +/** + * Rounds a number. + * + * @param int|float $value The value to round + * @param int|float $precision The rounding precision + * @param string $method The method to use for rounding + * + * @return int|float The rounded number + */ +function twig_round($value, $precision = 0, $method = 'common') +{ + if ('common' == $method) { + return round($value, $precision); + } + + if ('ceil' != $method && 'floor' != $method) { + throw new Twig_Error_Runtime('The round filter only supports the "common", "ceil", and "floor" methods.'); + } + + return $method($value * pow(10, $precision)) / pow(10, $precision); +} + /** * Number format filter. * @@ -488,17 +515,17 @@ function twig_date_converter(Twig_Environment $env, $date = null, $timezone = nu * be used. Supplying any of the parameters will override the defaults set in the * environment object. * - * @param Twig_Environment $env A Twig_Environment instance - * @param mixed $number A float/int/string of the number to format - * @param integer $decimal The number of decimal points to display. - * @param string $decimalPoint The character(s) to use for the decimal point. - * @param string $thousandSep The character(s) to use for the thousands separator. + * @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 + * @param string $thousandSep the character(s) to use for the thousands separator * * @return string The formatted number */ function twig_number_format_filter(Twig_Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null) { - $defaults = $env->getExtension('core')->getNumberFormat(); + $defaults = $env->getExtension('Twig_Extension_Core')->getNumberFormat(); if (null === $decimal) { $decimal = $defaults[0]; } @@ -515,32 +542,31 @@ function twig_number_format_filter(Twig_Environment $env, $number, $decimal = nu } /** - * URL encodes a string as a path segment or an array as a query string. + * URL encodes (RFC 3986) a string as a path segment or an array as a query string. * * @param string|array $url A URL or an array of query parameters - * @param bool $raw true to use rawurlencode() instead of urlencode * * @return string The URL encoded value */ -function twig_urlencode_filter($url, $raw = false) +function twig_urlencode_filter($url) { if (is_array($url)) { - return http_build_query($url, '', '&'); - } + if (defined('PHP_QUERY_RFC3986')) { + return http_build_query($url, '', '&', PHP_QUERY_RFC3986); + } - if ($raw) { - return rawurlencode($url); + return http_build_query($url, '', '&'); } - return urlencode($url); + return rawurlencode($url); } -if (version_compare(PHP_VERSION, '5.3.0', '<')) { +if (PHP_VERSION_ID < 50300) { /** * JSON encodes a variable. * - * @param mixed $value The value to encode. - * @param integer $options Not used on PHP 5.2.x + * @param mixed $value the value to encode + * @param int $options Not used on PHP 5.2.x * * @return mixed The JSON encoded value */ @@ -558,8 +584,8 @@ if (version_compare(PHP_VERSION, '5.3.0', '<')) { /** * JSON encodes a variable. * - * @param mixed $value The value to encode. - * @param integer $options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT + * @param mixed $value the value to encode + * @param int $options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT * * @return mixed The JSON encoded value */ @@ -593,15 +619,23 @@ function _twig_markup2string(&$value) * {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #} * * - * @param array $arr1 An array - * @param array $arr2 An array + * @param array|Traversable $arr1 An array + * @param array|Traversable $arr2 An array * * @return array The merged array */ function twig_array_merge($arr1, $arr2) { - if (!is_array($arr1) || !is_array($arr2)) { - throw new Twig_Error_Runtime('The merge filter only works with arrays or hashes.'); + if ($arr1 instanceof Traversable) { + $arr1 = iterator_to_array($arr1); + } elseif (!is_array($arr1)) { + throw new Twig_Error_Runtime(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as first argument.', gettype($arr1))); + } + + if ($arr2 instanceof Traversable) { + $arr2 = iterator_to_array($arr2); + } elseif (!is_array($arr2)) { + throw new Twig_Error_Runtime(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.', gettype($arr2))); } return array_merge($arr1, $arr2); @@ -610,18 +644,30 @@ 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 integer $start Start of the slice - * @param integer $length Size of the slice - * @param Boolean $preserveKeys Whether to preserve key or not (when the input is an array) + * @param int $start Start of the slice + * @param int $length Size of the slice + * @param bool $preserveKeys Whether to preserve key or not (when the input is an array) * * @return mixed The sliced variable */ function twig_slice(Twig_Environment $env, $item, $start, $length = null, $preserveKeys = false) { - if (is_object($item) && $item instanceof Traversable) { - $item = iterator_to_array($item, false); + if ($item instanceof Traversable) { + while ($item instanceof IteratorAggregate) { + $item = $item->getIterator(); + } + + if ($start >= 0 && $length >= 0 && $item instanceof Iterator) { + try { + return iterator_to_array(new LimitIterator($item, $start, null === $length ? -1 : $length), $preserveKeys); + } catch (OutOfBoundsException $exception) { + return array(); + } + } + + $item = iterator_to_array($item, $preserveKeys); } if (is_array($item)) { @@ -631,16 +677,16 @@ function twig_slice(Twig_Environment $env, $item, $start, $length = null, $prese $item = (string) $item; if (function_exists('mb_get_info') && null !== $charset = $env->getCharset()) { - return mb_substr($item, $start, null === $length ? mb_strlen($item, $charset) - $start : $length, $charset); + return (string) mb_substr($item, $start, null === $length ? mb_strlen($item, $charset) - $start : $length, $charset); } - return null === $length ? substr($item, $start) : substr($item, $start, $length); + return (string) (null === $length ? substr($item, $start) : substr($item, $start, $length)); } /** * Returns the first element of the item. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param mixed $item A variable * * @return mixed The first element of the item @@ -649,13 +695,13 @@ function twig_first(Twig_Environment $env, $item) { $elements = twig_slice($env, $item, 0, 1, false); - return is_string($elements) ? $elements[0] : current($elements); + return is_string($elements) ? $elements : current($elements); } /** * 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 @@ -664,7 +710,7 @@ function twig_last(Twig_Environment $env, $item) { $elements = twig_slice($env, $item, -1, 1, false); - return is_string($elements) ? $elements[0] : current($elements); + return is_string($elements) ? $elements : current($elements); } /** @@ -687,7 +733,7 @@ function twig_last(Twig_Environment $env, $item) */ function twig_join_filter($value, $glue = '') { - if (is_object($value) && $value instanceof Traversable) { + if ($value instanceof Traversable) { $value = iterator_to_array($value, false); } @@ -711,24 +757,46 @@ function twig_join_filter($value, $glue = '') * {# returns [aa, bb, cc] #} * * - * @param string $value A string - * @param string $delimiter The delimiter - * @param integer $limit The limit + * @param Twig_Environment $env + * @param string $value A string + * @param string $delimiter The delimiter + * @param int $limit The limit * * @return array The split string as an array */ -function twig_split_filter($value, $delimiter, $limit = null) +function twig_split_filter(Twig_Environment $env, $value, $delimiter, $limit = null) { - if (empty($delimiter)) { + if (!empty($delimiter)) { + return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); + } + + if (!function_exists('mb_get_info') || null === $charset = $env->getCharset()) { return str_split($value, null === $limit ? 1 : $limit); } - return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); + if ($limit <= 1) { + return preg_split('/(?getIterator(); + } + + if ($array instanceof Iterator) { + $keys = array(); + $array->rewind(); + while ($array->valid()) { + $keys[] = $array->key(); + $array->next(); + } + + return $keys; + } + + $keys = array(); + foreach ($array as $key => $item) { + $keys[] = $key; + } + + return $keys; } if (!is_array($array)) { @@ -769,15 +857,15 @@ 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 Boolean $preserveKeys Whether to preserve key or not + * @param bool $preserveKeys Whether to preserve key or not * * @return mixed The reversed input */ function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false) { - if (is_object($item) && $item instanceof Traversable) { + if ($item instanceof Traversable) { return array_reverse(iterator_to_array($item), $preserveKeys); } @@ -788,7 +876,7 @@ function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false if (null !== $charset = $env->getCharset()) { $string = (string) $item; - if ('UTF-8' != $charset) { + if ('UTF-8' !== $charset) { $item = twig_convert_encoding($string, 'UTF-8', $charset); } @@ -796,7 +884,7 @@ function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false $string = implode('', array_reverse($matches[0])); - if ('UTF-8' != $charset) { + if ('UTF-8' !== $charset) { $string = twig_convert_encoding($string, $charset, 'UTF-8'); } @@ -809,41 +897,88 @@ function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false /** * Sorts an array. * - * @param array $array An array + * @param array|Traversable $array + * + * @return array */ function twig_sort_filter($array) { + if ($array instanceof Traversable) { + $array = iterator_to_array($array); + } elseif (!is_array($array)) { + throw new Twig_Error_Runtime(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', gettype($array))); + } + asort($array); return $array; } -/* used internally */ +/** + * @internal + */ function twig_in_filter($value, $compare) { if (is_array($compare)) { - return in_array($value, $compare, is_object($value)); - } elseif (is_string($compare)) { - if (!strlen($value)) { - return empty($compare); + return in_array($value, $compare, is_object($value) || is_resource($value)); + } elseif (is_string($compare) && (is_string($value) || is_int($value) || is_float($value))) { + return '' === $value || false !== strpos($compare, (string) $value); + } elseif ($compare instanceof Traversable) { + if (is_object($value) || is_resource($value)) { + foreach ($compare as $item) { + if ($item === $value) { + return true; + } + } + } else { + foreach ($compare as $item) { + if ($item == $value) { + return true; + } + } } - return false !== strpos($compare, (string) $value); - } elseif (is_object($compare) && $compare instanceof Traversable) { - return in_array($value, iterator_to_array($compare, false), is_object($value)); + return false; } return false; } +/** + * Returns a trimmed string. + * + * @return string + * + * @throws Twig_Error_Runtime When an invalid trimming side is used (not a string or not 'left', 'right', or 'both') + */ +function twig_trim_filter($string, $characterMask = null, $side = 'both') +{ + if (null === $characterMask) { + $characterMask = " \t\n\r\0\x0B"; + } + + switch ($side) { + case 'both': + return trim($string, $characterMask); + case 'left': + return ltrim($string, $characterMask); + case 'right': + return rtrim($string, $characterMask); + default: + throw new Twig_Error_Runtime('Trimming side must be "left", "right" or "both".'); + } +} + /** * Escapes a string. * - * @param Twig_Environment $env A Twig_Environment instance - * @param string $string The value to be escaped + * @param Twig_Environment $env + * @param mixed $string The value to be escaped * @param string $strategy The escaping strategy * @param string $charset The charset - * @param Boolean $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) + * @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) + * + * @return string */ function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false) { @@ -854,7 +989,7 @@ function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', if (!is_string($string)) { if (is_object($string) && method_exists($string, '__toString')) { $string = (string) $string; - } else { + } elseif (in_array($strategy, array('html', 'js', 'css', 'html_attr', 'url'))) { return $string; } } @@ -865,7 +1000,7 @@ function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', switch ($strategy) { case 'html': - // see http://php.net/htmlspecialchars + // see https://secure.php.net/htmlspecialchars // Using a static variable to avoid initializing the array // each time the function is called. Moving the declaration on the @@ -905,72 +1040,84 @@ function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', case 'js': // escape all non-alphanumeric characters - // into their \xHH or \uHHHH representations - if ('UTF-8' != $charset) { + // into their \x or \uHHHH representations + if ('UTF-8' !== $charset) { $string = twig_convert_encoding($string, 'UTF-8', $charset); } - if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) { + 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.'); } $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', '_twig_escape_js_callback', $string); - if ('UTF-8' != $charset) { + if ('UTF-8' !== $charset) { $string = twig_convert_encoding($string, $charset, 'UTF-8'); } return $string; case 'css': - if ('UTF-8' != $charset) { + if ('UTF-8' !== $charset) { $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.'); } $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', '_twig_escape_css_callback', $string); - if ('UTF-8' != $charset) { + if ('UTF-8' !== $charset) { $string = twig_convert_encoding($string, $charset, 'UTF-8'); } return $string; case 'html_attr': - if ('UTF-8' != $charset) { + if ('UTF-8' !== $charset) { $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.'); } $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', '_twig_escape_html_attr_callback', $string); - if ('UTF-8' != $charset) { + if ('UTF-8' !== $charset) { $string = twig_convert_encoding($string, $charset, 'UTF-8'); } return $string; case 'url': - // hackish test to avoid version_compare that is much slower, this works unless PHP releases a 5.10.* - // at that point however PHP 5.2.* support can be removed - if (PHP_VERSION < '5.3.0') { + if (PHP_VERSION_ID < 50300) { return str_replace('%7E', '~', rawurlencode($string)); } return rawurlencode($string); default: - throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: html, js, url, css, and html_attr).', $strategy)); + static $escapers; + + if (null === $escapers) { + $escapers = $env->getExtension('Twig_Extension_Core')->getEscapers(); + } + + if (isset($escapers[$strategy])) { + return call_user_func($escapers[$strategy], $env, $string, $charset); + } + + $validStrategies = implode(', ', array_merge(array('html', 'js', 'url', 'css', 'html_attr'), array_keys($escapers))); + + throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies)); } } -/* used internally */ +/** + * @internal + */ function twig_escape_filter_is_safe(Twig_Node $filterArgs) { foreach ($filterArgs as $arg) { @@ -1005,15 +1152,34 @@ function _twig_escape_js_callback($matches) { $char = $matches[0]; - // \xHH - if (!isset($char[1])) { - return '\\x'.strtoupper(substr('00'.bin2hex($char), -2)); + /* + * A few characters have short escape sequences in JSON and JavaScript. + * Escape sequences supported only by JavaScript, not JSON, are ommitted. + * \" is also supported but omitted, because the resulting string is not HTML safe. + */ + static $shortMap = array( + '\\' => '\\\\', + '/' => '\\/', + "\x08" => '\b', + "\x0C" => '\f', + "\x0A" => '\n', + "\x0D" => '\r', + "\x09" => '\t', + ); + + if (isset($shortMap[$char])) { + return $shortMap[$char]; } // \uHHHH $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8'); + $char = strtoupper(bin2hex($char)); - return '\\u'.strtoupper(substr('0000'.bin2hex($char), -4)); + if (4 >= strlen($char)) { + return sprintf('\u%04s', $char); + } + + return sprintf('\u%04s\u%04s', substr($char, 0, -4), substr($char, -4)); } function _twig_escape_css_callback($matches) @@ -1039,8 +1205,8 @@ function _twig_escape_css_callback($matches) /** * This function is adapted from code coming from Zend Framework. * - * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) - * @license http://framework.zend.com/license/new-bsd New BSD License + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com) + * @license https://framework.zend.com/license/new-bsd New BSD License */ function _twig_escape_html_attr_callback($matches) { @@ -1060,19 +1226,19 @@ function _twig_escape_html_attr_callback($matches) $chr = $matches[0]; $ord = ord($chr); - /** + /* * The following replaces characters undefined in HTML with the * hex entity for the Unicode replacement character. */ - if (($ord <= 0x1f && $chr != "\t" && $chr != "\n" && $chr != "\r") || ($ord >= 0x7f && $ord <= 0x9f)) { + if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) { return '�'; } - /** + /* * Check if the current character to escape has a name entity we should * replace it with while grabbing the hex value of the character. */ - if (strlen($chr) == 1) { + if (1 == strlen($chr)) { $hex = strtoupper(substr('00'.bin2hex($chr), -2)); } else { $chr = twig_convert_encoding($chr, 'UTF-16BE', 'UTF-8'); @@ -1084,11 +1250,10 @@ function _twig_escape_html_attr_callback($matches) return sprintf('&%s;', $entityMap[$int]); } - /** + /* * Per OWASP recommendations, we'll use hex entities for any other * characters where a named entity does not exist. */ - return sprintf('&#x%s;', $hex); } @@ -1097,27 +1262,51 @@ if (function_exists('mb_get_info')) { /** * Returns the length of a variable. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param mixed $thing A variable * - * @return integer The length of the value + * @return int The length of the value */ function twig_length_filter(Twig_Environment $env, $thing) { - return is_scalar($thing) ? mb_strlen($thing, $env->getCharset()) : count($thing); + if (null === $thing) { + return 0; + } + + if (is_scalar($thing)) { + return mb_strlen($thing, $env->getCharset()); + } + + if ($thing instanceof \SimpleXMLElement) { + return count($thing); + } + + if (is_object($thing) && method_exists($thing, '__toString') && !$thing instanceof \Countable) { + return mb_strlen((string) $thing, $env->getCharset()); + } + + if ($thing instanceof \Countable || is_array($thing)) { + return count($thing); + } + + if ($thing instanceof \IteratorAggregate) { + return iterator_count($thing); + } + + return 1; } /** * 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 */ function twig_upper_filter(Twig_Environment $env, $string) { - if (null !== ($charset = $env->getCharset())) { + if (null !== $charset = $env->getCharset()) { return mb_strtoupper($string, $charset); } @@ -1127,14 +1316,14 @@ if (function_exists('mb_get_info')) { /** * 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 */ function twig_lower_filter(Twig_Environment $env, $string) { - if (null !== ($charset = $env->getCharset())) { + if (null !== $charset = $env->getCharset()) { return mb_strtolower($string, $charset); } @@ -1144,14 +1333,14 @@ if (function_exists('mb_get_info')) { /** * 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 */ function twig_title_string_filter(Twig_Environment $env, $string) { - if (null !== ($charset = $env->getCharset())) { + if (null !== $charset = $env->getCharset()) { return mb_convert_case($string, MB_CASE_TITLE, $charset); } @@ -1161,16 +1350,15 @@ if (function_exists('mb_get_info')) { /** * 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 */ function twig_capitalize_string_filter(Twig_Environment $env, $string) { - if (null !== ($charset = $env->getCharset())) { - return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset). - mb_strtolower(mb_substr($string, 1, mb_strlen($string, $charset), $charset), $charset); + if (null !== $charset = $env->getCharset()) { + return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).mb_strtolower(mb_substr($string, 1, mb_strlen($string, $charset), $charset), $charset); } return ucfirst(strtolower($string)); @@ -1181,20 +1369,44 @@ else { /** * Returns the length of a variable. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param mixed $thing A variable * - * @return integer The length of the value + * @return int The length of the value */ function twig_length_filter(Twig_Environment $env, $thing) { - return is_scalar($thing) ? strlen($thing) : count($thing); + if (null === $thing) { + return 0; + } + + if (is_scalar($thing)) { + return strlen($thing); + } + + if ($thing instanceof \SimpleXMLElement) { + return count($thing); + } + + if (is_object($thing) && method_exists($thing, '__toString') && !$thing instanceof \Countable) { + return strlen((string) $thing); + } + + if ($thing instanceof \Countable || is_array($thing)) { + return count($thing); + } + + if ($thing instanceof \IteratorAggregate) { + return iterator_count($thing); + } + + return 1; } /** * 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 @@ -1207,7 +1419,7 @@ else { /** * 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 @@ -1218,7 +1430,9 @@ else { } } -/* used internally */ +/** + * @internal + */ function twig_ensure_traversable($seq) { if ($seq instanceof Traversable || is_array($seq)) { @@ -1240,7 +1454,7 @@ function twig_ensure_traversable($seq) * * @param mixed $value A variable * - * @return Boolean true if the value is empty, false otherwise + * @return bool true if the value is empty, false otherwise */ function twig_test_empty($value) { @@ -1248,6 +1462,10 @@ function twig_test_empty($value) return 0 == count($value); } + if (is_object($value) && method_exists($value, '__toString')) { + return '' === (string) $value; + } + return '' === $value || false === $value || null === $value || array() === $value; } @@ -1256,14 +1474,14 @@ function twig_test_empty($value) * *
  * {# evaluates to true if the foo variable is an array or a traversable object #}
- * {% if foo is traversable %}
+ * {% if foo is iterable %}
  *     {# ... #}
  * {% endif %}
  * 
* * @param mixed $value A variable * - * @return Boolean true if the value is traversable + * @return bool true if the value is traversable */ function twig_test_iterable($value) { @@ -1273,38 +1491,86 @@ function twig_test_iterable($value) /** * Renders a template. * - * @param string $template The template to render - * @param array $variables The variables to pass to the template - * @param Boolean $with_context Whether to pass the current context variables or not - * @param Boolean $ignore_missing Whether to ignore missing templates or not - * @param Boolean $sandboxed Whether to sandbox the template or not + * @param Twig_Environment $env + * @param array $context + * @param string|array $template The template to render or an array of templates to try consecutively + * @param array $variables The variables to pass to the template + * @param bool $withContext + * @param bool $ignoreMissing Whether to ignore missing templates or not + * @param bool $sandboxed Whether to sandbox the template or not * * @return string The rendered template */ function twig_include(Twig_Environment $env, $context, $template, $variables = array(), $withContext = true, $ignoreMissing = false, $sandboxed = false) { + $alreadySandboxed = false; + $sandbox = null; if ($withContext) { $variables = array_merge($context, $variables); } - if ($isSandboxed = $sandboxed && $env->hasExtension('sandbox')) { - $sandbox = $env->getExtension('sandbox'); + if ($isSandboxed = $sandboxed && $env->hasExtension('Twig_Extension_Sandbox')) { + $sandbox = $env->getExtension('Twig_Extension_Sandbox'); if (!$alreadySandboxed = $sandbox->isSandboxed()) { $sandbox->enableSandbox(); } } + $result = null; try { - return $env->resolveTemplate($template)->render($variables); + $result = $env->resolveTemplate($template)->render($variables); } catch (Twig_Error_Loader $e) { if (!$ignoreMissing) { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + throw $e; } + } catch (Throwable $e) { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + + throw $e; + } catch (Exception $e) { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + + throw $e; } if ($isSandboxed && !$alreadySandboxed) { $sandbox->disableSandbox(); } + + return $result; +} + +/** + * Returns a template content without rendering it. + * + * @param Twig_Environment $env + * @param string $name The template name + * @param bool $ignoreMissing Whether to ignore missing templates or not + * + * @return string The template source + */ +function twig_source(Twig_Environment $env, $name, $ignoreMissing = false) +{ + $loader = $env->getLoader(); + try { + if (!$loader instanceof Twig_SourceContextLoaderInterface) { + return $loader->getSource($name); + } else { + return $loader->getSourceContext($name)->getCode(); + } + } catch (Twig_Error_Loader $e) { + if (!$ignoreMissing) { + throw $e; + } + } } /** @@ -1324,18 +1590,35 @@ 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. * - * @param array $items An array of items - * @param integer $size The size of the batch - * @param mixed $fill A value used to fill missing items + * @param array $items An array of items + * @param int $size The size of the batch + * @param mixed $fill A value used to fill missing items * * @return array */ function twig_array_batch($items, $size, $fill = null) { - if (is_object($items) && $items instanceof Traversable) { + if ($items instanceof Traversable) { $items = iterator_to_array($items, false); } @@ -1343,7 +1626,7 @@ function twig_array_batch($items, $size, $fill = null) $result = array_chunk($items, $size, true); - if (null !== $fill) { + if (null !== $fill && !empty($result)) { $last = count($result) - 1; if ($fillCount = $size - count($result[$last])) { $result[$last] = array_merge( @@ -1355,3 +1638,5 @@ function twig_array_batch($items, $size, $fill = null) return $result; } + +class_alias('Twig_Extension_Core', 'Twig\Extension\CoreExtension', false); diff --git a/inc/lib/Twig/Extension/Debug.php b/inc/lib/Twig/Extension/Debug.php index e3a85bfe..d0cd1962 100644 --- a/inc/lib/Twig/Extension/Debug.php +++ b/inc/lib/Twig/Extension/Debug.php @@ -3,18 +3,17 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + +/** + * @final + */ class Twig_Extension_Debug extends Twig_Extension { - /** - * Returns a list of global functions to add to the existing list. - * - * @return array An array of global functions - */ public function getFunctions() { // dump is safe if var_dump is overridden by xdebug @@ -24,7 +23,7 @@ class Twig_Extension_Debug extends Twig_Extension // false means that it was not set (and the default is on) or it explicitly enabled // xdebug.overload_var_dump produces HTML only when html_errors is also enabled && (false === ini_get('html_errors') || ini_get('html_errors')) - || 'cli' === php_sapi_name() + || 'cli' === PHP_SAPI ; return array( @@ -32,11 +31,6 @@ class Twig_Extension_Debug extends Twig_Extension ); } - /** - * Returns the name of the extension. - * - * @return string The extension name - */ public function getName() { return 'debug'; @@ -62,10 +56,12 @@ function twig_var_dump(Twig_Environment $env, $context) var_dump($vars); } else { - for ($i = 2; $i < $count; $i++) { + for ($i = 2; $i < $count; ++$i) { var_dump(func_get_arg($i)); } } return ob_get_clean(); } + +class_alias('Twig_Extension_Debug', 'Twig\Extension\DebugExtension', false); diff --git a/inc/lib/Twig/Extension/Escaper.php b/inc/lib/Twig/Extension/Escaper.php index c9a7f68e..46c2d84b 100644 --- a/inc/lib/Twig/Extension/Escaper.php +++ b/inc/lib/Twig/Extension/Escaper.php @@ -3,45 +3,39 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + +/** + * @final + */ class Twig_Extension_Escaper extends Twig_Extension { protected $defaultStrategy; + /** + * @param string|false|callable $defaultStrategy An escaping strategy + * + * @see setDefaultStrategy() + */ public function __construct($defaultStrategy = 'html') { $this->setDefaultStrategy($defaultStrategy); } - /** - * Returns the token parser instances to add to the existing list. - * - * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances - */ public function getTokenParsers() { return array(new Twig_TokenParser_AutoEscape()); } - /** - * Returns the node visitor instances to add to the existing list. - * - * @return array An array of Twig_NodeVisitorInterface instances - */ public function getNodeVisitors() { return array(new Twig_NodeVisitor_Escaper()); } - /** - * Returns a list of filters to add to the existing list. - * - * @return array An array of filters - */ public function getFilters() { return array( @@ -53,43 +47,50 @@ class Twig_Extension_Escaper extends Twig_Extension * 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 mixed $defaultStrategy An escaping strategy + * @param string|false|callable $defaultStrategy An escaping strategy */ public function setDefaultStrategy($defaultStrategy) { // for BC if (true === $defaultStrategy) { + @trigger_error('Using "true" as the default strategy is deprecated since version 1.21. Use "html" instead.', E_USER_DEPRECATED); + $defaultStrategy = 'html'; } + 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'); + } + $this->defaultStrategy = $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 The default strategy to use for the template + * @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) && is_callable($this->defaultStrategy)) { - return call_user_func($this->defaultStrategy, $filename); + if (!is_string($this->defaultStrategy) && false !== $this->defaultStrategy) { + return call_user_func($this->defaultStrategy, $name); } return $this->defaultStrategy; } - /** - * Returns the name of the extension. - * - * @return string The extension name - */ public function getName() { return 'escaper'; @@ -100,8 +101,12 @@ class Twig_Extension_Escaper extends Twig_Extension * Marks a variable as being safe. * * @param string $string A PHP variable + * + * @return string */ function twig_raw_filter($string) { return $string; } + +class_alias('Twig_Extension_Escaper', 'Twig\Extension\EscaperExtension', false); diff --git a/inc/lib/Twig/Extension/GlobalsInterface.php b/inc/lib/Twig/Extension/GlobalsInterface.php new file mode 100644 index 00000000..922cd2c9 --- /dev/null +++ b/inc/lib/Twig/Extension/GlobalsInterface.php @@ -0,0 +1,24 @@ + + */ +interface Twig_Extension_GlobalsInterface +{ +} + +class_alias('Twig_Extension_GlobalsInterface', 'Twig\Extension\GlobalsInterface', false); diff --git a/inc/lib/Twig/Extension/InitRuntimeInterface.php b/inc/lib/Twig/Extension/InitRuntimeInterface.php new file mode 100644 index 00000000..1549862f --- /dev/null +++ b/inc/lib/Twig/Extension/InitRuntimeInterface.php @@ -0,0 +1,24 @@ + + */ +interface Twig_Extension_InitRuntimeInterface +{ +} + +class_alias('Twig_Extension_InitRuntimeInterface', 'Twig\Extension\InitRuntimeInterface', false); diff --git a/inc/lib/Twig/Extension/Optimizer.php b/inc/lib/Twig/Extension/Optimizer.php index 013fcb62..6c62e3ef 100644 --- a/inc/lib/Twig/Extension/Optimizer.php +++ b/inc/lib/Twig/Extension/Optimizer.php @@ -3,11 +3,15 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + +/** + * @final + */ class Twig_Extension_Optimizer extends Twig_Extension { protected $optimizers; @@ -17,19 +21,15 @@ class Twig_Extension_Optimizer extends Twig_Extension $this->optimizers = $optimizers; } - /** - * {@inheritdoc} - */ public function getNodeVisitors() { return array(new Twig_NodeVisitor_Optimizer($this->optimizers)); } - /** - * {@inheritdoc} - */ public function getName() { return 'optimizer'; } } + +class_alias('Twig_Extension_Optimizer', 'Twig\Extension\OptimizerExtension', false); diff --git a/inc/lib/Twig/Extension/Profiler.php b/inc/lib/Twig/Extension/Profiler.php new file mode 100644 index 00000000..fcfc002b --- /dev/null +++ b/inc/lib/Twig/Extension/Profiler.php @@ -0,0 +1,49 @@ +actives[] = $profile; + } + + public function enter(Twig_Profiler_Profile $profile) + { + $this->actives[0]->addProfile($profile); + array_unshift($this->actives, $profile); + } + + public function leave(Twig_Profiler_Profile $profile) + { + $profile->leave(); + array_shift($this->actives); + + if (1 === count($this->actives)) { + $this->actives[0]->leave(); + } + } + + public function getNodeVisitors() + { + return array(new Twig_Profiler_NodeVisitor_Profiler(get_class($this))); + } + + public function getName() + { + return 'profiler'; + } +} + +class_alias('Twig_Extension_Profiler', 'Twig\Extension\ProfilerExtension', false); +class_exists('Twig_Profiler_Profile'); diff --git a/inc/lib/Twig/Extension/Sandbox.php b/inc/lib/Twig/Extension/Sandbox.php index bf76c11a..5cb80a71 100644 --- a/inc/lib/Twig/Extension/Sandbox.php +++ b/inc/lib/Twig/Extension/Sandbox.php @@ -3,11 +3,15 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + +/** + * @final + */ class Twig_Extension_Sandbox extends Twig_Extension { protected $sandboxedGlobally; @@ -16,25 +20,15 @@ class Twig_Extension_Sandbox extends Twig_Extension public function __construct(Twig_Sandbox_SecurityPolicyInterface $policy, $sandboxed = false) { - $this->policy = $policy; + $this->policy = $policy; $this->sandboxedGlobally = $sandboxed; } - /** - * Returns the token parser instances to add to the existing list. - * - * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances - */ public function getTokenParsers() { return array(new Twig_TokenParser_Sandbox()); } - /** - * Returns the node visitor instances to add to the existing list. - * - * @return array An array of Twig_NodeVisitorInterface instances - */ public function getNodeVisitors() { return array(new Twig_NodeVisitor_Sandbox()); @@ -93,20 +87,17 @@ class Twig_Extension_Sandbox extends Twig_Extension public function ensureToStringAllowed($obj) { - if (is_object($obj)) { + if ($this->isSandboxed() && is_object($obj)) { $this->policy->checkMethodAllowed($obj, '__toString'); } return $obj; } - /** - * Returns the name of the extension. - * - * @return string The extension name - */ public function getName() { return 'sandbox'; } } + +class_alias('Twig_Extension_Sandbox', 'Twig\Extension\SandboxExtension', false); diff --git a/inc/lib/Twig/Extension/Staging.php b/inc/lib/Twig/Extension/Staging.php index 8ab0f459..d3a0f9c9 100644 --- a/inc/lib/Twig/Extension/Staging.php +++ b/inc/lib/Twig/Extension/Staging.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2012 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,6 +15,8 @@ * This class is used by Twig_Environment as a staging area and must not be used directly. * * @author Fabien Potencier + * + * @internal */ class Twig_Extension_Staging extends Twig_Extension { @@ -27,12 +29,13 @@ class Twig_Extension_Staging extends Twig_Extension public function addFunction($name, $function) { + if (isset($this->functions[$name])) { + @trigger_error(sprintf('Overriding function "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED); + } + $this->functions[$name] = $function; } - /** - * {@inheritdoc} - */ public function getFunctions() { return $this->functions; @@ -40,12 +43,13 @@ class Twig_Extension_Staging extends Twig_Extension public function addFilter($name, $filter) { + if (isset($this->filters[$name])) { + @trigger_error(sprintf('Overriding filter "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED); + } + $this->filters[$name] = $filter; } - /** - * {@inheritdoc} - */ public function getFilters() { return $this->filters; @@ -56,9 +60,6 @@ class Twig_Extension_Staging extends Twig_Extension $this->visitors[] = $visitor; } - /** - * {@inheritdoc} - */ public function getNodeVisitors() { return $this->visitors; @@ -66,12 +67,13 @@ class Twig_Extension_Staging extends Twig_Extension public function addTokenParser(Twig_TokenParserInterface $parser) { - $this->tokenParsers[] = $parser; + if (isset($this->tokenParsers[$parser->getTag()])) { + @trigger_error(sprintf('Overriding tag "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $parser->getTag()), E_USER_DEPRECATED); + } + + $this->tokenParsers[$parser->getTag()] = $parser; } - /** - * {@inheritdoc} - */ public function getTokenParsers() { return $this->tokenParsers; @@ -82,9 +84,6 @@ class Twig_Extension_Staging extends Twig_Extension $this->globals[$name] = $value; } - /** - * {@inheritdoc} - */ public function getGlobals() { return $this->globals; @@ -92,22 +91,22 @@ class Twig_Extension_Staging extends Twig_Extension public function addTest($name, $test) { + if (isset($this->tests[$name])) { + @trigger_error(sprintf('Overriding test "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED); + } + $this->tests[$name] = $test; } - /** - * {@inheritdoc} - */ public function getTests() { return $this->tests; } - /** - * {@inheritdoc} - */ public function getName() { return 'staging'; } } + +class_alias('Twig_Extension_Staging', 'Twig\Extension\StagingExtension', false); diff --git a/inc/lib/Twig/Extension/StringLoader.php b/inc/lib/Twig/Extension/StringLoader.php index 5e1a60d0..2ce3c992 100644 --- a/inc/lib/Twig/Extension/StringLoader.php +++ b/inc/lib/Twig/Extension/StringLoader.php @@ -3,16 +3,17 @@ /* * This file is part of Twig. * - * (c) 2012 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + +/** + * @final + */ class Twig_Extension_StringLoader extends Twig_Extension { - /** - * {@inheritdoc} - */ public function getFunctions() { return array( @@ -20,9 +21,6 @@ class Twig_Extension_StringLoader extends Twig_Extension ); } - /** - * {@inheritdoc} - */ public function getName() { return 'string_loader'; @@ -37,28 +35,13 @@ class Twig_Extension_StringLoader extends Twig_Extension * * * @param Twig_Environment $env A Twig_Environment instance - * @param string $template A template as a string + * @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) { - $name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), true), false)); - - $loader = new Twig_Loader_Chain(array( - new Twig_Loader_Array(array($name => $template)), - $current = $env->getLoader(), - )); - - $env->setLoader($loader); - try { - $template = $env->loadTemplate($name); - } catch (Exception $e) { - $env->setLoader($current); - - throw $e; - } - $env->setLoader($current); - - return $template; + return $env->createTemplate((string) $template); } + +class_alias('Twig_Extension_StringLoader', 'Twig\Extension\StringLoaderExtension', false); diff --git a/inc/lib/Twig/ExtensionInterface.php b/inc/lib/Twig/ExtensionInterface.php index f189e9d9..946df500 100644 --- a/inc/lib/Twig/ExtensionInterface.php +++ b/inc/lib/Twig/ExtensionInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -21,49 +21,49 @@ 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); /** * Returns the token parser instances to add to the existing list. * - * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances + * @return Twig_TokenParserInterface[] */ public function getTokenParsers(); /** * Returns the node visitor instances to add to the existing list. * - * @return array An array of Twig_NodeVisitorInterface instances + * @return Twig_NodeVisitorInterface[] */ public function getNodeVisitors(); /** * Returns a list of filters to add to the existing list. * - * @return array An array of filters + * @return Twig_SimpleFilter[] */ public function getFilters(); /** * Returns a list of tests to add to the existing list. * - * @return array An array of tests + * @return Twig_SimpleTest[] */ public function getTests(); /** * Returns a list of functions to add to the existing list. * - * @return array An array of functions + * @return Twig_SimpleFunction[] */ public function getFunctions(); /** * Returns a list of operators to add to the existing list. * - * @return array An array of operators + * @return array First array of unary operators, second array of binary operators */ public function getOperators(); @@ -71,6 +71,8 @@ interface Twig_ExtensionInterface * Returns a list of global variables to add to the existing list. * * @return array An array of global variables + * + * @deprecated since 1.23 (to be removed in 2.0), implement Twig_Extension_GlobalsInterface instead */ public function getGlobals(); @@ -78,6 +80,11 @@ interface Twig_ExtensionInterface * Returns the name of the extension. * * @return string The extension name + * + * @deprecated since 1.26 (to be removed in 2.0), not used anymore internally */ public function getName(); } + +class_alias('Twig_ExtensionInterface', 'Twig\Extension\ExtensionInterface', false); +class_exists('Twig_Environment'); diff --git a/inc/lib/Twig/FactoryRuntimeLoader.php b/inc/lib/Twig/FactoryRuntimeLoader.php new file mode 100644 index 00000000..2cdaded1 --- /dev/null +++ b/inc/lib/Twig/FactoryRuntimeLoader.php @@ -0,0 +1,39 @@ + + */ +class Twig_FactoryRuntimeLoader implements Twig_RuntimeLoaderInterface +{ + private $map; + + /** + * @param array $map An array where keys are class names and values factory callables + */ + public function __construct($map = array()) + { + $this->map = $map; + } + + public function load($class) + { + if (isset($this->map[$class])) { + $runtimeFactory = $this->map[$class]; + + return $runtimeFactory(); + } + } +} + +class_alias('Twig_FactoryRuntimeLoader', 'Twig\RuntimeLoader\FactoryRuntimeLoader', false); diff --git a/inc/lib/Twig/FileExtensionEscapingStrategy.php b/inc/lib/Twig/FileExtensionEscapingStrategy.php new file mode 100644 index 00000000..8f8cd2ee --- /dev/null +++ b/inc/lib/Twig/FileExtensionEscapingStrategy.php @@ -0,0 +1,60 @@ + + */ +class Twig_FileExtensionEscapingStrategy +{ + /** + * Guesses the best autoescaping strategy based on the 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($name) + { + if (in_array(substr($name, -1), array('/', '\\'))) { + return 'html'; // return html for directories + } + + if ('.twig' === substr($name, -5)) { + $name = substr($name, 0, -5); + } + + $extension = pathinfo($name, PATHINFO_EXTENSION); + + switch ($extension) { + case 'js': + return 'js'; + + case 'css': + return 'css'; + + case 'txt': + return false; + + default: + return 'html'; + } + } +} + +class_alias('Twig_FileExtensionEscapingStrategy', 'Twig\FileExtensionEscapingStrategy', false); diff --git a/inc/lib/Twig/Filter.php b/inc/lib/Twig/Filter.php index 5cfbb662..893d75d1 100644 --- a/inc/lib/Twig/Filter.php +++ b/inc/lib/Twig/Filter.php @@ -3,18 +3,21 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Filter class is deprecated since version 1.12 and will be removed in 2.0. Use Twig_SimpleFilter instead.', E_USER_DEPRECATED); + /** * Represents a template filter. * * Use Twig_SimpleFilter instead. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableInterface @@ -26,10 +29,10 @@ abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableI { $this->options = array_merge(array( 'needs_environment' => false, - 'needs_context' => false, - 'pre_escape' => null, - 'preserves_safety' => null, - 'callable' => null, + 'needs_context' => false, + 'pre_escape' => null, + 'preserves_safety' => null, + 'callable' => null, ), $options); } diff --git a/inc/lib/Twig/Filter/Function.php b/inc/lib/Twig/Filter/Function.php index ad374a55..71b16554 100644 --- a/inc/lib/Twig/Filter/Function.php +++ b/inc/lib/Twig/Filter/Function.php @@ -3,18 +3,21 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Filter_Function class is deprecated since version 1.12 and will be removed in 2.0. Use Twig_SimpleFilter instead.', E_USER_DEPRECATED); + /** * Represents a function template filter. * * Use Twig_SimpleFilter instead. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ class Twig_Filter_Function extends Twig_Filter diff --git a/inc/lib/Twig/Filter/Method.php b/inc/lib/Twig/Filter/Method.php index 63c8c3be..1b75676c 100644 --- a/inc/lib/Twig/Filter/Method.php +++ b/inc/lib/Twig/Filter/Method.php @@ -3,18 +3,21 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Filter_Method class is deprecated since version 1.12 and will be removed in 2.0. Use Twig_SimpleFilter instead.', E_USER_DEPRECATED); + /** * Represents a method template filter. * * Use Twig_SimpleFilter instead. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ class Twig_Filter_Method extends Twig_Filter @@ -34,6 +37,6 @@ class Twig_Filter_Method extends Twig_Filter public function compile() { - return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method); + return sprintf('$this->env->getExtension(\'%s\')->%s', get_class($this->extension), $this->method); } } diff --git a/inc/lib/Twig/Filter/Node.php b/inc/lib/Twig/Filter/Node.php index 8744c5e0..3e6b12ef 100644 --- a/inc/lib/Twig/Filter/Node.php +++ b/inc/lib/Twig/Filter/Node.php @@ -3,18 +3,21 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Filter_Node class is deprecated since version 1.12 and will be removed in 2.0. Use Twig_SimpleFilter instead.', E_USER_DEPRECATED); + /** * Represents a template filter as a node. * * Use Twig_SimpleFilter instead. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ class Twig_Filter_Node extends Twig_Filter diff --git a/inc/lib/Twig/FilterCallableInterface.php b/inc/lib/Twig/FilterCallableInterface.php index 145534df..21b028c4 100644 --- a/inc/lib/Twig/FilterCallableInterface.php +++ b/inc/lib/Twig/FilterCallableInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2012 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,6 +15,7 @@ * Use Twig_SimpleFilter instead. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ interface Twig_FilterCallableInterface diff --git a/inc/lib/Twig/FilterInterface.php b/inc/lib/Twig/FilterInterface.php index 5319ecc9..9d7e9ab6 100644 --- a/inc/lib/Twig/FilterInterface.php +++ b/inc/lib/Twig/FilterInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,6 +15,7 @@ * Use Twig_SimpleFilter instead. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ interface Twig_FilterInterface diff --git a/inc/lib/Twig/Function.php b/inc/lib/Twig/Function.php index b5ffb2b0..9dc16e90 100644 --- a/inc/lib/Twig/Function.php +++ b/inc/lib/Twig/Function.php @@ -3,18 +3,21 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Function class is deprecated since version 1.12 and will be removed in 2.0. Use Twig_SimpleFunction instead.', E_USER_DEPRECATED); + /** * Represents a template function. * * Use Twig_SimpleFunction instead. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ abstract class Twig_Function implements Twig_FunctionInterface, Twig_FunctionCallableInterface @@ -26,8 +29,8 @@ abstract class Twig_Function implements Twig_FunctionInterface, Twig_FunctionCal { $this->options = array_merge(array( 'needs_environment' => false, - 'needs_context' => false, - 'callable' => null, + 'needs_context' => false, + 'callable' => null, ), $options); } diff --git a/inc/lib/Twig/Function/Function.php b/inc/lib/Twig/Function/Function.php index d1e1b96a..97c0eb77 100644 --- a/inc/lib/Twig/Function/Function.php +++ b/inc/lib/Twig/Function/Function.php @@ -3,19 +3,22 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2010 Arnaud Le Blanc + * (c) Fabien Potencier + * (c) Arnaud Le Blanc * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Function_Function class is deprecated since version 1.12 and will be removed in 2.0. Use Twig_SimpleFunction instead.', E_USER_DEPRECATED); + /** * Represents a function template function. * * Use Twig_SimpleFunction instead. * * @author Arnaud Le Blanc + * * @deprecated since 1.12 (to be removed in 2.0) */ class Twig_Function_Function extends Twig_Function diff --git a/inc/lib/Twig/Function/Method.php b/inc/lib/Twig/Function/Method.php index 67039a95..4299e118 100644 --- a/inc/lib/Twig/Function/Method.php +++ b/inc/lib/Twig/Function/Method.php @@ -3,19 +3,22 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2010 Arnaud Le Blanc + * (c) Fabien Potencier + * (c) Arnaud Le Blanc * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Function_Method class is deprecated since version 1.12 and will be removed in 2.0. Use Twig_SimpleFunction instead.', E_USER_DEPRECATED); + /** * Represents a method template function. * * Use Twig_SimpleFunction instead. * * @author Arnaud Le Blanc + * * @deprecated since 1.12 (to be removed in 2.0) */ class Twig_Function_Method extends Twig_Function @@ -35,6 +38,6 @@ class Twig_Function_Method extends Twig_Function public function compile() { - return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method); + return sprintf('$this->env->getExtension(\'%s\')->%s', get_class($this->extension), $this->method); } } diff --git a/inc/lib/Twig/Function/Node.php b/inc/lib/Twig/Function/Node.php index 06a0d0db..0adc5d93 100644 --- a/inc/lib/Twig/Function/Node.php +++ b/inc/lib/Twig/Function/Node.php @@ -3,18 +3,21 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Function_Node class is deprecated since version 1.12 and will be removed in 2.0. Use Twig_SimpleFunction instead.', E_USER_DEPRECATED); + /** * Represents a template function as a node. * * Use Twig_SimpleFunction instead. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ class Twig_Function_Node extends Twig_Function diff --git a/inc/lib/Twig/FunctionCallableInterface.php b/inc/lib/Twig/FunctionCallableInterface.php index 0aab4f5e..d23d6917 100644 --- a/inc/lib/Twig/FunctionCallableInterface.php +++ b/inc/lib/Twig/FunctionCallableInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2012 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,6 +15,7 @@ * Use Twig_SimpleFunction instead. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ interface Twig_FunctionCallableInterface diff --git a/inc/lib/Twig/FunctionInterface.php b/inc/lib/Twig/FunctionInterface.php index 67f4f89c..00d4f95c 100644 --- a/inc/lib/Twig/FunctionInterface.php +++ b/inc/lib/Twig/FunctionInterface.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier - * (c) 2010 Arnaud Le Blanc + * (c) Fabien Potencier + * (c) Arnaud Le Blanc * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,6 +16,7 @@ * Use Twig_SimpleFunction instead. * * @author Arnaud Le Blanc + * * @deprecated since 1.12 (to be removed in 2.0) */ interface Twig_FunctionInterface diff --git a/inc/lib/Twig/Lexer.php b/inc/lib/Twig/Lexer.php index 000b038e..41211eb2 100644 --- a/inc/lib/Twig/Lexer.php +++ b/inc/lib/Twig/Lexer.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -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; @@ -33,62 +34,69 @@ class Twig_Lexer implements Twig_LexerInterface protected $positions; protected $currentVarBlockLine; - const STATE_DATA = 0; - const STATE_BLOCK = 1; - const STATE_VAR = 2; - const STATE_STRING = 3; - const STATE_INTERPOLATION = 4; + private $source; - const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; - const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A'; - const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; + const STATE_DATA = 0; + const STATE_BLOCK = 1; + const STATE_VAR = 2; + const STATE_STRING = 3; + const STATE_INTERPOLATION = 4; + + const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; + const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A'; + const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; const REGEX_DQ_STRING_DELIM = '/"/A'; - const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; - const PUNCTUATION = '()[]{}?:.,|'; + const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; + const PUNCTUATION = '()[]{}?:.,|'; public function __construct(Twig_Environment $env, array $options = array()) { $this->env = $env; $this->options = array_merge(array( - 'tag_comment' => array('{#', '#}'), - 'tag_block' => array('{%', '%}'), - 'tag_variable' => array('{{', '}}'), + 'tag_comment' => array('{#', '#}'), + 'tag_block' => array('{%', '%}'), + 'tag_variable' => array('{{', '}}'), 'whitespace_trim' => '-', - 'interpolation' => array('#{', '}'), + 'interpolation' => array('#{', '}'), ), $options); $this->regexes = array( - 'lex_var' => '/\s*'.preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_variable'][1], '/').'/A', - 'lex_block' => '/\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')\n?/A', - 'lex_raw_data' => '/('.preg_quote($this->options['tag_block'][0].$this->options['whitespace_trim'], '/').'|'.preg_quote($this->options['tag_block'][0], '/').')\s*(?:end%s)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/s', - 'operator' => $this->getOperatorRegex(), - 'lex_comment' => '/(?:'.preg_quote($this->options['whitespace_trim'], '/').preg_quote($this->options['tag_comment'][1], '/').'\s*|'.preg_quote($this->options['tag_comment'][1], '/').')\n?/s', - 'lex_block_raw' => '/\s*(raw|verbatim)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/As', - 'lex_block_line' => '/\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '/').'/As', - 'lex_tokens_start' => '/('.preg_quote($this->options['tag_variable'][0], '/').'|'.preg_quote($this->options['tag_block'][0], '/').'|'.preg_quote($this->options['tag_comment'][0], '/').')('.preg_quote($this->options['whitespace_trim'], '/').')?/s', + 'lex_var' => '/\s*'.preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_variable'][1], '/').'/A', + 'lex_block' => '/\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')\n?/A', + 'lex_raw_data' => '/('.preg_quote($this->options['tag_block'][0].$this->options['whitespace_trim'], '/').'|'.preg_quote($this->options['tag_block'][0], '/').')\s*(?:end%s)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/s', + 'operator' => $this->getOperatorRegex(), + 'lex_comment' => '/(?:'.preg_quote($this->options['whitespace_trim'], '/').preg_quote($this->options['tag_comment'][1], '/').'\s*|'.preg_quote($this->options['tag_comment'][1], '/').')\n?/s', + 'lex_block_raw' => '/\s*(raw|verbatim)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/As', + 'lex_block_line' => '/\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '/').'/As', + 'lex_tokens_start' => '/('.preg_quote($this->options['tag_variable'][0], '/').'|'.preg_quote($this->options['tag_block'][0], '/').'|'.preg_quote($this->options['tag_comment'][0], '/').')('.preg_quote($this->options['whitespace_trim'], '/').')?/s', 'interpolation_start' => '/'.preg_quote($this->options['interpolation'][0], '/').'\s*/A', - 'interpolation_end' => '/\s*'.preg_quote($this->options['interpolation'][1], '/').'/A', + 'interpolation_end' => '/\s*'.preg_quote($this->options['interpolation'][1], '/').'/A', ); } - /** - * Tokenizes a source code. - * - * @param string $code The source code - * @param string $filename A unique identifier for the source code - * - * @return Twig_TokenStream A token stream instance - */ - public function tokenize($code, $filename = null) + 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); + $this->source = new Twig_Source($code, $name); + } else { + $this->source = $code; + } + + if (((int) ini_get('mbstring.func_overload')) & 2) { + @trigger_error('Support for having "mbstring.func_overload" different from 0 is deprecated version 1.29 and will be removed in 2.0.', E_USER_DEPRECATED); + } + if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { $mbEncoding = mb_internal_encoding(); mb_internal_encoding('ASCII'); + } else { + $mbEncoding = null; } - $this->code = str_replace(array("\r\n", "\r"), "\n", $code); - $this->filename = $filename; + $this->code = str_replace(array("\r\n", "\r"), "\n", $this->source->getCode()); + $this->filename = $this->source->getName(); $this->cursor = 0; $this->lineno = 1; $this->end = strlen($this->code); @@ -132,14 +140,14 @@ class Twig_Lexer implements Twig_LexerInterface if (!empty($this->brackets)) { list($expect, $lineno) = array_pop($this->brackets); - throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename); + throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); } - if (isset($mbEncoding)) { + if ($mbEncoding) { mb_internal_encoding($mbEncoding); } - return new Twig_TokenStream($this->tokens, $this->filename); + return new Twig_TokenStream($this->tokens, $this->source); } protected function lexData() @@ -227,13 +235,13 @@ class Twig_Lexer implements Twig_LexerInterface $this->moveCursor($match[0]); if ($this->cursor >= $this->end) { - throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $this->state === self::STATE_BLOCK ? 'block' : 'variable'), $this->currentVarBlockLine, $this->filename); + throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', self::STATE_BLOCK === $this->state ? 'block' : 'variable'), $this->currentVarBlockLine, $this->source); } } // operators if (preg_match($this->regexes['operator'], $this->code, $match, null, $this->cursor)) { - $this->pushToken(Twig_Token::OPERATOR_TYPE, $match[0]); + $this->pushToken(Twig_Token::OPERATOR_TYPE, preg_replace('/\s+/', ' ', $match[0])); $this->moveCursor($match[0]); } // names @@ -259,12 +267,12 @@ class Twig_Lexer implements Twig_LexerInterface // closing bracket elseif (false !== strpos(')]}', $this->code[$this->cursor])) { if (empty($this->brackets)) { - throw new Twig_Error_Syntax(sprintf('Unexpected "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename); + throw new Twig_Error_Syntax(sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); } list($expect, $lineno) = array_pop($this->brackets); if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) { - throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename); + throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); } } @@ -284,14 +292,18 @@ class Twig_Lexer implements Twig_LexerInterface } // unlexable else { - throw new Twig_Error_Syntax(sprintf('Unexpected character "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename); + throw new Twig_Error_Syntax(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); } } protected function lexRawData($tag) { + if ('raw' === $tag) { + @trigger_error(sprintf('Twig Tag "raw" is deprecated since version 1.21. Use "verbatim" instead in %s at line %d.', $this->filename, $this->lineno), E_USER_DEPRECATED); + } + if (!preg_match(str_replace('%s', $tag, $this->regexes['lex_raw_data']), $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { - throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "%s" block', $tag), $this->lineno, $this->filename); + throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "%s" block.', $tag), $this->lineno, $this->source); } $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); @@ -307,7 +319,7 @@ class Twig_Lexer implements Twig_LexerInterface protected function lexComment() { if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { - throw new Twig_Error_Syntax('Unclosed comment', $this->lineno, $this->filename); + throw new Twig_Error_Syntax('Unclosed comment.', $this->lineno, $this->source); } $this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]); @@ -320,20 +332,20 @@ class Twig_Lexer implements Twig_LexerInterface $this->pushToken(Twig_Token::INTERPOLATION_START_TYPE); $this->moveCursor($match[0]); $this->pushState(self::STATE_INTERPOLATION); - } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, null, $this->cursor) && strlen($match[0]) > 0) { $this->pushToken(Twig_Token::STRING_TYPE, stripcslashes($match[0])); $this->moveCursor($match[0]); - } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) { - list($expect, $lineno) = array_pop($this->brackets); - if ($this->code[$this->cursor] != '"') { - throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename); + if ('"' != $this->code[$this->cursor]) { + throw new Twig_Error_Syntax(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); } $this->popState(); ++$this->cursor; + } else { + // unlexable + throw new Twig_Error_Syntax(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); } } @@ -382,10 +394,15 @@ class Twig_Lexer implements Twig_LexerInterface // an operator that ends with a character must be followed by // a whitespace or a parenthesis if (ctype_alpha($operator[$length - 1])) { - $regex[] = preg_quote($operator, '/').'(?=[\s()])'; + $r = preg_quote($operator, '/').'(?=[\s()])'; } else { - $regex[] = preg_quote($operator, '/'); + $r = preg_quote($operator, '/'); } + + // an operator with a space can be any amount of whitespaces + $r = preg_replace('/\s+/', '\s+', $r); + + $regex[] = $r; } return '/'.implode('|', $regex).'/A'; @@ -400,9 +417,11 @@ class Twig_Lexer implements Twig_LexerInterface protected function popState() { if (0 === count($this->states)) { - throw new Exception('Cannot pop state without a previous state'); + throw new Exception('Cannot pop state without a previous state.'); } $this->state = array_pop($this->states); } } + +class_alias('Twig_Lexer', 'Twig\Lexer', false); diff --git a/inc/lib/Twig/LexerInterface.php b/inc/lib/Twig/LexerInterface.php index 4b83f81b..c10bbfec 100644 --- a/inc/lib/Twig/LexerInterface.php +++ b/inc/lib/Twig/LexerInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,17 +13,20 @@ * Interface implemented by lexer classes. * * @author Fabien Potencier - * @deprecated since 1.12 (to be removed in 2.0) + * + * @deprecated since 1.12 (to be removed in 3.0) */ 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 * - * @return Twig_TokenStream A token stream instance + * @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/inc/lib/Twig/Loader/Array.php b/inc/lib/Twig/Loader/Array.php index ac561048..0aac7690 100644 --- a/inc/lib/Twig/Loader/Array.php +++ b/inc/lib/Twig/Loader/Array.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,20 +17,20 @@ * source code of the template). If you don't want to see your cache grows out of * control, you need to take care of clearing the old cache file by yourself. * + * This loader should only be used for unit testing. + * + * @final + * * @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) - * - * @see Twig_Loader */ - public function __construct(array $templates) + public function __construct(array $templates = array()) { $this->templates = $templates; } @@ -46,11 +46,10 @@ class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterf $this->templates[(string) $name] = $template; } - /** - * {@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); + $name = (string) $name; if (!isset($this->templates[$name])) { throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); @@ -59,17 +58,21 @@ class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterf 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); + } + public function exists($name) { return isset($this->templates[(string) $name]); } - /** - * {@inheritdoc} - */ public function getCacheKey($name) { $name = (string) $name; @@ -77,12 +80,9 @@ class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterf throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); } - return $this->templates[$name]; + return $name.':'.$this->templates[$name]; } - /** - * {@inheritdoc} - */ public function isFresh($name, $time) { $name = (string) $name; @@ -93,3 +93,5 @@ class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterf return true; } } + +class_alias('Twig_Loader_Array', 'Twig\Loader\ArrayLoader', false); diff --git a/inc/lib/Twig/Loader/Chain.php b/inc/lib/Twig/Loader/Chain.php index 7919eda6..59a33796 100644 --- a/inc/lib/Twig/Loader/Chain.php +++ b/inc/lib/Twig/Loader/Chain.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,17 +12,17 @@ /** * Loads templates from other loaders. * + * @final + * * @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,22 +31,16 @@ class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterf } } - /** - * Adds a loader instance. - * - * @param Twig_LoaderInterface $loader A Loader instance - */ public function addLoader(Twig_LoaderInterface $loader) { $this->loaders[] = $loader; $this->hasSourceCache = array(); } - /** - * {@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); + $exceptions = array(); foreach ($this->loaders as $loader) { if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) { @@ -60,12 +54,31 @@ class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterf } } - throw new Twig_Error_Loader(sprintf('Template "%s" is not defined (%s).', $name, implode(', ', $exceptions))); + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } + + 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} - */ public function exists($name) { $name = (string) $name; @@ -84,7 +97,11 @@ class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterf } 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) { @@ -94,9 +111,6 @@ class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterf return $this->hasSourceCache[$name] = false; } - /** - * {@inheritdoc} - */ public function getCacheKey($name) { $exceptions = array(); @@ -112,12 +126,9 @@ class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterf } } - throw new Twig_Error_Loader(sprintf('Template "%s" is not defined (%s).', $name, implode(' ', $exceptions))); + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); } - /** - * {@inheritdoc} - */ public function isFresh($name, $time) { $exceptions = array(); @@ -133,6 +144,8 @@ class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterf } } - throw new Twig_Error_Loader(sprintf('Template "%s" is not defined (%s).', $name, implode(' ', $exceptions))); + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); } } + +class_alias('Twig_Loader_Chain', 'Twig\Loader\ChainLoader', false); diff --git a/inc/lib/Twig/Loader/Filesystem.php b/inc/lib/Twig/Loader/Filesystem.php index 23bac47d..4e8be0d5 100644 --- a/inc/lib/Twig/Loader/Filesystem.php +++ b/inc/lib/Twig/Loader/Filesystem.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -14,21 +14,28 @@ * * @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__'; protected $paths = array(); 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); } @@ -80,17 +87,18 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI * 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 */ public function addPath($path, $namespace = self::MAIN_NAMESPACE) { // invalidate the cache - $this->cache = array(); + $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, '/\\'); @@ -100,17 +108,18 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI * 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 */ public function prependPath($path, $namespace = self::MAIN_NAMESPACE) { // invalidate the cache - $this->cache = array(); + $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, '/\\'); @@ -122,84 +131,126 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI } } - /** - * {@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 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); + } + 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; } - /** - * {@inheritdoc} - */ public function exists($name) { - $name = (string) $name; + $name = $this->normalizeName($name); + if (isset($this->cache[$name])) { return true; } try { - $this->findTemplate($name); - - return true; + 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; } } - /** - * {@inheritdoc} - */ public function isFresh($name, $time) { - return filemtime($this->findTemplate($name)) <= $time; + return filemtime($this->findTemplate($name)) < $time; } protected function findTemplate($name) { - $name = (string) $name; - - // normalize name - $name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/')); + $throw = func_num_args() > 1 ? func_get_arg(1) : true; + $name = $this->normalizeName($name); if (isset($this->cache[$name])) { return $this->cache[$name]; } - $this->validateName($name); - - $namespace = self::MAIN_NAMESPACE; - $shortname = $name; - if (isset($name[0]) && '@' == $name[0]) { - if (false === $pos = strpos($name, '/')) { - throw new Twig_Error_Loader(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); + if (isset($this->errorCache[$name])) { + if (!$throw) { + return false; } - $namespace = substr($name, 1, $pos - 1); - $shortname = substr($name, $pos + 1); + throw new Twig_Error_Loader($this->errorCache[$name]); } + $this->validateName($name); + + list($namespace, $shortname) = $this->parseName($name); + if (!isset($this->paths[$namespace])) { - throw new Twig_Error_Loader(sprintf('There are no registered paths for namespace "%s".', $namespace)); + $this->errorCache[$name] = sprintf('There are no registered paths for namespace "%s".', $namespace); + + if (!$throw) { + return false; + } + + throw new Twig_Error_Loader($this->errorCache[$name]); } foreach ($this->paths[$namespace] as $path) { + if (!$this->isAbsolutePath($path)) { + $path = $this->rootPath.'/'.$path; + } + if (is_file($path.'/'.$shortname)) { + if (false !== $realpath = realpath($path.'/'.$shortname)) { + return $this->cache[$name] = $realpath; + } + return $this->cache[$name] = $path.'/'.$shortname; } } - throw new Twig_Error_Loader(sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace]))); + $this->errorCache[$name] = sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])); + + if (!$throw) { + return false; + } + + throw new Twig_Error_Loader($this->errorCache[$name]); + } + + protected function parseName($name, $default = self::MAIN_NAMESPACE) + { + if (isset($name[0]) && '@' == $name[0]) { + if (false === $pos = strpos($name, '/')) { + throw new Twig_Error_Loader(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); + } + + $namespace = substr($name, 1, $pos - 1); + $shortname = substr($name, $pos + 1); + + return array($namespace, $shortname); + } + + return array($default, $name); + } + + protected function normalizeName($name) + { + return preg_replace('#/{2,}#', '/', str_replace('\\', '/', (string) $name)); } protected function validateName($name) @@ -223,4 +274,17 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI } } } + + private function isAbsolutePath($file) + { + 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) + ; + } } + +class_alias('Twig_Loader_Filesystem', 'Twig\Loader\FilesystemLoader', false); diff --git a/inc/lib/Twig/Loader/String.php b/inc/lib/Twig/Loader/String.php index 8ad9856c..950bd35b 100644 --- a/inc/lib/Twig/Loader/String.php +++ b/inc/lib/Twig/Loader/String.php @@ -3,55 +3,54 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Loader_String class is deprecated since version 1.18.1 and will be removed in 2.0. Use Twig_Loader_Array instead or Twig_Environment::createTemplate().', E_USER_DEPRECATED); + /** * Loads a template from a string. * - * This loader should only be used for unit testing as it has many limitations - * (for instance, the include or extends tag does not make any sense for a string - * loader). + * This loader should NEVER be used. It only exists for Twig internal purposes. * * When using this loader with a cache mechanism, you should know that a new cache * key is generated each time a template content "changes" (the cache key being the * source code of the template). If you don't want to see your cache grows out of * control, you need to take care of clearing the old cache file by yourself. * + * @deprecated since 1.18.1 (to be removed in 2.0) + * + * @internal + * * @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); + } + public function exists($name) { return true; } - /** - * {@inheritdoc} - */ public function getCacheKey($name) { return $name; } - /** - * {@inheritdoc} - */ public function isFresh($name, $time) { return true; diff --git a/inc/lib/Twig/LoaderInterface.php b/inc/lib/Twig/LoaderInterface.php index 927786d1..459a70ab 100644 --- a/inc/lib/Twig/LoaderInterface.php +++ b/inc/lib/Twig/LoaderInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -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); @@ -41,12 +43,15 @@ interface Twig_LoaderInterface /** * Returns true if the template is still fresh. * - * @param string $name The template name - * @param timestamp $time The last modification time of the cached template + * @param string $name The template name + * @param int $time Timestamp of the last modification time of the + * cached template * - * @return Boolean true if the template is fresh, false otherwise + * @return bool true if the template is fresh, false otherwise * * @throws Twig_Error_Loader When $name is not found */ public function isFresh($name, $time); } + +class_alias('Twig_LoaderInterface', 'Twig\Loader\LoaderInterface', false); diff --git a/inc/lib/Twig/Markup.php b/inc/lib/Twig/Markup.php index 69871fcb..8591d1f9 100644 --- a/inc/lib/Twig/Markup.php +++ b/inc/lib/Twig/Markup.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -35,3 +35,5 @@ class Twig_Markup implements Countable return function_exists('mb_get_info') ? mb_strlen($this->content, $this->charset) : strlen($this->content); } } + +class_alias('Twig_Markup', 'Twig\Markup', false); diff --git a/inc/lib/Twig/Node.php b/inc/lib/Twig/Node.php index 931b4635..89ada144 100644 --- a/inc/lib/Twig/Node.php +++ b/inc/lib/Twig/Node.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -22,19 +22,26 @@ class Twig_Node implements Twig_NodeInterface protected $lineno; protected $tag; + private $name; + /** * Constructor. * * The nodes are automatically made available as properties ($this->node). * The attributes are automatically made available as array items ($this['name']). * - * @param array $nodes An array of named nodes - * @param array $attributes An array of attributes (should not be nodes) - * @param integer $lineno The line number - * @param string $tag The tag name associated with the Node + * @param array $nodes An array of named nodes + * @param array $attributes An array of attributes (should not be nodes) + * @param int $lineno The line number + * @param string $tag The tag name associated with the Node */ public function __construct(array $nodes = array(), array $attributes = array(), $lineno = 0, $tag = null) { + foreach ($nodes as $name => $node) { + if (!$node instanceof Twig_NodeInterface) { + @trigger_error(sprintf('Using "%s" for the value of node "%s" of "%s" is deprecated since version 1.25 and will be removed in 2.0.', is_object($node) ? get_class($node) : null === $node ? 'null' : gettype($node), $name, get_class($this)), E_USER_DEPRECATED); + } + } $this->nodes = $nodes; $this->attributes = $attributes; $this->lineno = $lineno; @@ -69,8 +76,13 @@ class Twig_Node implements Twig_NodeInterface return implode("\n", $repr); } + /** + * @deprecated since 1.16.1 (to be removed in 2.0) + */ public function toXml($asDom = false) { + @trigger_error(sprintf('%s is deprecated since version 1.16.1 and will be removed in 2.0.', __METHOD__), E_USER_DEPRECATED); + $dom = new DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; $dom->appendChild($xml = $dom->createElement('twig')); @@ -96,7 +108,7 @@ class Twig_Node implements Twig_NodeInterface $node->appendChild($child); } - return $asDom ? $dom : $dom->saveXml(); + return $asDom ? $dom : $dom->saveXML(); } public function compile(Twig_Compiler $compiler) @@ -106,8 +118,18 @@ class Twig_Node implements Twig_NodeInterface } } + 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; } @@ -117,11 +139,7 @@ class Twig_Node implements Twig_NodeInterface } /** - * Returns true if the attribute is defined. - * - * @param string The attribute name - * - * @return Boolean true if the attribute is defined, false otherwise + * @return bool */ public function hasAttribute($name) { @@ -129,11 +147,7 @@ class Twig_Node implements Twig_NodeInterface } /** - * Gets an attribute. - * - * @param string The attribute name - * - * @return mixed The attribute value + * @return mixed */ public function getAttribute($name) { @@ -145,32 +159,21 @@ class Twig_Node implements Twig_NodeInterface } /** - * Sets an attribute. - * - * @param string The attribute name - * @param mixed The attribute value + * @param string $name + * @param mixed $value */ public function setAttribute($name, $value) { $this->attributes[$name] = $value; } - /** - * Removes an attribute. - * - * @param string The attribute name - */ public function removeAttribute($name) { unset($this->attributes[$name]); } /** - * Returns true if the node with the given identifier exists. - * - * @param string The node name - * - * @return Boolean true if the node with the given name exists, false otherwise + * @return bool */ public function hasNode($name) { @@ -178,11 +181,7 @@ class Twig_Node implements Twig_NodeInterface } /** - * Gets a node by name. - * - * @param string The node name - * - * @return Twig_Node A Twig_Node instance + * @return Twig_Node */ public function getNode($name) { @@ -193,22 +192,15 @@ class Twig_Node implements Twig_NodeInterface return $this->nodes[$name]; } - /** - * Sets a node. - * - * @param string The node name - * @param Twig_Node A Twig_Node instance - */ public function setNode($name, $node = null) { + if (!$node instanceof Twig_NodeInterface) { + @trigger_error(sprintf('Using "%s" for the value of node "%s" of "%s" is deprecated since version 1.25 and will be removed in 2.0.', is_object($node) ? get_class($node) : null === $node ? 'null' : gettype($node), $name, get_class($this)), E_USER_DEPRECATED); + } + $this->nodes[$name] = $node; } - /** - * Removes a node by name. - * - * @param string The node name - */ public function removeNode($name) { unset($this->nodes[$name]); @@ -223,4 +215,42 @@ class Twig_Node implements Twig_NodeInterface { return new ArrayIterator($this->nodes); } + + public function setTemplateName($name) + { + $this->name = $name; + foreach ($this->nodes as $node) { + if (null !== $node) { + $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() + { + @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; + } } + +class_alias('Twig_Node', 'Twig\Node\Node', false); +class_exists('Twig_Compiler'); diff --git a/inc/lib/Twig/Node/AutoEscape.php b/inc/lib/Twig/Node/AutoEscape.php index 8f190e0b..17e4e381 100644 --- a/inc/lib/Twig/Node/AutoEscape.php +++ b/inc/lib/Twig/Node/AutoEscape.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -27,13 +27,10 @@ class Twig_Node_AutoEscape extends Twig_Node parent::__construct(array('body' => $body), array('value' => $value), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler->subcompile($this->getNode('body')); } } + +class_alias('Twig_Node_AutoEscape', 'Twig\Node\AutoEscapeNode', false); diff --git a/inc/lib/Twig/Node/Block.php b/inc/lib/Twig/Node/Block.php index 50eb67ed..91752ad2 100644 --- a/inc/lib/Twig/Node/Block.php +++ b/inc/lib/Twig/Node/Block.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -22,11 +22,6 @@ class Twig_Node_Block extends Twig_Node parent::__construct(array('body' => $body), array('name' => $name), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler @@ -42,3 +37,5 @@ class Twig_Node_Block extends Twig_Node ; } } + +class_alias('Twig_Node_Block', 'Twig\Node\BlockNode', false); diff --git a/inc/lib/Twig/Node/BlockReference.php b/inc/lib/Twig/Node/BlockReference.php index 013e369e..92a9f398 100644 --- a/inc/lib/Twig/Node/BlockReference.php +++ b/inc/lib/Twig/Node/BlockReference.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -22,11 +22,6 @@ class Twig_Node_BlockReference extends Twig_Node implements Twig_NodeOutputInter parent::__construct(array(), array('name' => $name), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler @@ -35,3 +30,5 @@ class Twig_Node_BlockReference extends Twig_Node implements Twig_NodeOutputInter ; } } + +class_alias('Twig_Node_BlockReference', 'Twig\Node\BlockReferenceNode', false); diff --git a/inc/lib/Twig/Node/Body.php b/inc/lib/Twig/Node/Body.php index 3ffb1342..07dfef8b 100644 --- a/inc/lib/Twig/Node/Body.php +++ b/inc/lib/Twig/Node/Body.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,3 +17,5 @@ class Twig_Node_Body extends Twig_Node { } + +class_alias('Twig_Node_Body', 'Twig\Node\BodyNode', false); diff --git a/inc/lib/Twig/Node/CheckSecurity.php b/inc/lib/Twig/Node/CheckSecurity.php new file mode 100644 index 00000000..7258acb6 --- /dev/null +++ b/inc/lib/Twig/Node/CheckSecurity.php @@ -0,0 +1,80 @@ + + */ +class Twig_Node_CheckSecurity extends Twig_Node +{ + protected $usedFilters; + protected $usedTags; + protected $usedFunctions; + + public function __construct(array $usedFilters, array $usedTags, array $usedFunctions) + { + $this->usedFilters = $usedFilters; + $this->usedTags = $usedTags; + $this->usedFunctions = $usedFunctions; + + parent::__construct(); + } + + public function compile(Twig_Compiler $compiler) + { + $tags = $filters = $functions = array(); + foreach (array('tags', 'filters', 'functions') as $type) { + foreach ($this->{'used'.ucfirst($type)} as $name => $node) { + if ($node instanceof Twig_Node) { + ${$type}[$name] = $node->getTemplateLine(); + } else { + ${$type}[$node] = null; + } + } + } + + $compiler + ->write('$tags = ')->repr(array_filter($tags))->raw(";\n") + ->write('$filters = ')->repr(array_filter($filters))->raw(";\n") + ->write('$functions = ')->repr(array_filter($functions))->raw(";\n\n") + ->write("try {\n") + ->indent() + ->write("\$this->env->getExtension('Twig_Extension_Sandbox')->checkSecurity(\n") + ->indent() + ->write(!$tags ? "array(),\n" : "array('".implode("', '", array_keys($tags))."'),\n") + ->write(!$filters ? "array(),\n" : "array('".implode("', '", array_keys($filters))."'),\n") + ->write(!$functions ? "array()\n" : "array('".implode("', '", array_keys($functions))."')\n") + ->outdent() + ->write(");\n") + ->outdent() + ->write("} catch (Twig_Sandbox_SecurityError \$e) {\n") + ->indent() + ->write("\$e->setSourceContext(\$this->getSourceContext());\n\n") + ->write("if (\$e instanceof Twig_Sandbox_SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n") + ->outdent() + ->write("} elseif (\$e instanceof Twig_Sandbox_SecurityNotAllowedFilterError && isset(\$filters[\$e->getFilterName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$filters[\$e->getFilterName()]);\n") + ->outdent() + ->write("} elseif (\$e instanceof Twig_Sandbox_SecurityNotAllowedFunctionError && isset(\$functions[\$e->getFunctionName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$functions[\$e->getFunctionName()]);\n") + ->outdent() + ->write("}\n\n") + ->write("throw \$e;\n") + ->outdent() + ->write("}\n\n") + ; + } +} + +class_alias('Twig_Node_CheckSecurity', 'Twig\Node\CheckSecurityNode', false); diff --git a/inc/lib/Twig/Node/Do.php b/inc/lib/Twig/Node/Do.php index c528066b..cdd7e77a 100644 --- a/inc/lib/Twig/Node/Do.php +++ b/inc/lib/Twig/Node/Do.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -21,11 +21,6 @@ class Twig_Node_Do extends Twig_Node parent::__construct(array('expr' => $expr), array(), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler @@ -36,3 +31,5 @@ class Twig_Node_Do extends Twig_Node ; } } + +class_alias('Twig_Node_Do', 'Twig\Node\DoNode', false); diff --git a/inc/lib/Twig/Node/Embed.php b/inc/lib/Twig/Node/Embed.php index 4c9456dc..3785d3a9 100644 --- a/inc/lib/Twig/Node/Embed.php +++ b/inc/lib/Twig/Node/Embed.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2012 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,22 +17,30 @@ 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); } protected function addGetTemplate(Twig_Compiler $compiler) { $compiler - ->write("\$this->env->loadTemplate(") - ->string($this->getAttribute('filename')) + ->write('$this->loadTemplate(') + ->string($this->getAttribute('name')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) ->raw(', ') ->string($this->getAttribute('index')) - ->raw(")") + ->raw(')') ; } } + +class_alias('Twig_Node_Embed', 'Twig\Node\EmbedNode', false); diff --git a/inc/lib/Twig/Node/Expression.php b/inc/lib/Twig/Node/Expression.php index a7382e7d..a99c4e63 100644 --- a/inc/lib/Twig/Node/Expression.php +++ b/inc/lib/Twig/Node/Expression.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -18,3 +18,5 @@ abstract class Twig_Node_Expression extends Twig_Node { } + +class_alias('Twig_Node_Expression', 'Twig\Node\Expression\AbstractExpression', false); diff --git a/inc/lib/Twig/Node/Expression/Array.php b/inc/lib/Twig/Node/Expression/Array.php index 1da785fe..0e77bb08 100644 --- a/inc/lib/Twig/Node/Expression/Array.php +++ b/inc/lib/Twig/Node/Expression/Array.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -43,7 +43,7 @@ class Twig_Node_Expression_Array extends Twig_Node_Expression 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,17 +54,12 @@ class Twig_Node_Expression_Array extends Twig_Node_Expression 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); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler->raw('array('); @@ -84,3 +79,5 @@ class Twig_Node_Expression_Array extends Twig_Node_Expression $compiler->raw(')'); } } + +class_alias('Twig_Node_Expression_Array', 'Twig\Node\Expression\ArrayExpression', false); diff --git a/inc/lib/Twig/Node/Expression/AssignName.php b/inc/lib/Twig/Node/Expression/AssignName.php index 2ddea78c..2e6b4c7c 100644 --- a/inc/lib/Twig/Node/Expression/AssignName.php +++ b/inc/lib/Twig/Node/Expression/AssignName.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,11 +12,6 @@ class Twig_Node_Expression_AssignName extends Twig_Node_Expression_Name { - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler @@ -26,3 +21,5 @@ class Twig_Node_Expression_AssignName extends Twig_Node_Expression_Name ; } } + +class_alias('Twig_Node_Expression_AssignName', 'Twig\Node\Expression\AssignNameExpression', false); diff --git a/inc/lib/Twig/Node/Expression/Binary.php b/inc/lib/Twig/Node/Expression/Binary.php index 9dd5de2c..2b545d98 100644 --- a/inc/lib/Twig/Node/Expression/Binary.php +++ b/inc/lib/Twig/Node/Expression/Binary.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,11 +16,6 @@ abstract class Twig_Node_Expression_Binary extends Twig_Node_Expression parent::__construct(array('left' => $left, 'right' => $right), array(), $lineno); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler @@ -38,3 +33,5 @@ abstract class Twig_Node_Expression_Binary extends Twig_Node_Expression abstract public function operator(Twig_Compiler $compiler); } + +class_alias('Twig_Node_Expression_Binary', 'Twig\Node\Expression\Binary\AbstractBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/Add.php b/inc/lib/Twig/Node/Expression/Binary/Add.php index 0ef8e117..5a09d836 100644 --- a/inc/lib/Twig/Node/Expression/Binary/Add.php +++ b/inc/lib/Twig/Node/Expression/Binary/Add.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,3 +16,5 @@ class Twig_Node_Expression_Binary_Add extends Twig_Node_Expression_Binary return $compiler->raw('+'); } } + +class_alias('Twig_Node_Expression_Binary_Add', 'Twig\Node\Expression\Binary\AddBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/And.php b/inc/lib/Twig/Node/Expression/Binary/And.php index d5752ebb..9ffddce6 100644 --- a/inc/lib/Twig/Node/Expression/Binary/And.php +++ b/inc/lib/Twig/Node/Expression/Binary/And.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,3 +16,5 @@ class Twig_Node_Expression_Binary_And extends Twig_Node_Expression_Binary return $compiler->raw('&&'); } } + +class_alias('Twig_Node_Expression_Binary_And', 'Twig\Node\Expression\Binary\AndBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/BitwiseAnd.php b/inc/lib/Twig/Node/Expression/Binary/BitwiseAnd.php index 9a46d845..e46e9ebf 100644 --- a/inc/lib/Twig/Node/Expression/Binary/BitwiseAnd.php +++ b/inc/lib/Twig/Node/Expression/Binary/BitwiseAnd.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,3 +16,5 @@ class Twig_Node_Expression_Binary_BitwiseAnd extends Twig_Node_Expression_Binary return $compiler->raw('&'); } } + +class_alias('Twig_Node_Expression_Binary_BitwiseAnd', 'Twig\Node\Expression\Binary\BitwiseAndBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/BitwiseOr.php b/inc/lib/Twig/Node/Expression/Binary/BitwiseOr.php index 058a20bf..5d7f1b7c 100644 --- a/inc/lib/Twig/Node/Expression/Binary/BitwiseOr.php +++ b/inc/lib/Twig/Node/Expression/Binary/BitwiseOr.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,3 +16,5 @@ class Twig_Node_Expression_Binary_BitwiseOr extends Twig_Node_Expression_Binary return $compiler->raw('|'); } } + +class_alias('Twig_Node_Expression_Binary_BitwiseOr', 'Twig\Node\Expression\Binary\BitwiseOrBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/BitwiseXor.php b/inc/lib/Twig/Node/Expression/Binary/BitwiseXor.php index f4da73d4..82edf516 100644 --- a/inc/lib/Twig/Node/Expression/Binary/BitwiseXor.php +++ b/inc/lib/Twig/Node/Expression/Binary/BitwiseXor.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,3 +16,5 @@ class Twig_Node_Expression_Binary_BitwiseXor extends Twig_Node_Expression_Binary return $compiler->raw('^'); } } + +class_alias('Twig_Node_Expression_Binary_BitwiseXor', 'Twig\Node\Expression\Binary\BitwiseXorBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/Concat.php b/inc/lib/Twig/Node/Expression/Binary/Concat.php index f9a64627..91abca60 100644 --- a/inc/lib/Twig/Node/Expression/Binary/Concat.php +++ b/inc/lib/Twig/Node/Expression/Binary/Concat.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,3 +16,5 @@ class Twig_Node_Expression_Binary_Concat extends Twig_Node_Expression_Binary return $compiler->raw('.'); } } + +class_alias('Twig_Node_Expression_Binary_Concat', 'Twig\Node\Expression\Binary\ConcatBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/Div.php b/inc/lib/Twig/Node/Expression/Binary/Div.php index e0797a61..38ffa30c 100644 --- a/inc/lib/Twig/Node/Expression/Binary/Div.php +++ b/inc/lib/Twig/Node/Expression/Binary/Div.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,3 +16,5 @@ class Twig_Node_Expression_Binary_Div extends Twig_Node_Expression_Binary return $compiler->raw('/'); } } + +class_alias('Twig_Node_Expression_Binary_Div', 'Twig\Node\Expression\Binary\DivBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/EndsWith.php b/inc/lib/Twig/Node/Expression/Binary/EndsWith.php new file mode 100644 index 00000000..85c52937 --- /dev/null +++ b/inc/lib/Twig/Node/Expression/Binary/EndsWith.php @@ -0,0 +1,32 @@ +getVarName(); + $right = $compiler->getVarName(); + $compiler + ->raw(sprintf('(is_string($%s = ', $left)) + ->subcompile($this->getNode('left')) + ->raw(sprintf(') && is_string($%s = ', $right)) + ->subcompile($this->getNode('right')) + ->raw(sprintf(') && (\'\' === $%2$s || $%2$s === substr($%1$s, -strlen($%2$s))))', $left, $right)) + ; + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw(''); + } +} + +class_alias('Twig_Node_Expression_Binary_EndsWith', 'Twig\Node\Expression\Binary\EndsWithBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/Equal.php b/inc/lib/Twig/Node/Expression/Binary/Equal.php index 7b1236d0..a6a6946b 100644 --- a/inc/lib/Twig/Node/Expression/Binary/Equal.php +++ b/inc/lib/Twig/Node/Expression/Binary/Equal.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,3 +15,5 @@ class Twig_Node_Expression_Binary_Equal extends Twig_Node_Expression_Binary return $compiler->raw('=='); } } + +class_alias('Twig_Node_Expression_Binary_Equal', 'Twig\Node\Expression\Binary\EqualBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/FloorDiv.php b/inc/lib/Twig/Node/Expression/Binary/FloorDiv.php index 7fbd0556..7393bcb8 100644 --- a/inc/lib/Twig/Node/Expression/Binary/FloorDiv.php +++ b/inc/lib/Twig/Node/Expression/Binary/FloorDiv.php @@ -3,23 +3,18 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ class Twig_Node_Expression_Binary_FloorDiv extends Twig_Node_Expression_Binary { - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { - $compiler->raw('intval(floor('); + $compiler->raw('(int) floor('); parent::compile($compiler); - $compiler->raw('))'); + $compiler->raw(')'); } public function operator(Twig_Compiler $compiler) @@ -27,3 +22,5 @@ class Twig_Node_Expression_Binary_FloorDiv extends Twig_Node_Expression_Binary return $compiler->raw('/'); } } + +class_alias('Twig_Node_Expression_Binary_FloorDiv', 'Twig\Node\Expression\Binary\FloorDivBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/Greater.php b/inc/lib/Twig/Node/Expression/Binary/Greater.php index a110bd92..832f9797 100644 --- a/inc/lib/Twig/Node/Expression/Binary/Greater.php +++ b/inc/lib/Twig/Node/Expression/Binary/Greater.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,3 +15,5 @@ class Twig_Node_Expression_Binary_Greater extends Twig_Node_Expression_Binary return $compiler->raw('>'); } } + +class_alias('Twig_Node_Expression_Binary_Greater', 'Twig\Node\Expression\Binary\GreaterBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/GreaterEqual.php b/inc/lib/Twig/Node/Expression/Binary/GreaterEqual.php index 3754fed2..c5f76245 100644 --- a/inc/lib/Twig/Node/Expression/Binary/GreaterEqual.php +++ b/inc/lib/Twig/Node/Expression/Binary/GreaterEqual.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,3 +15,5 @@ class Twig_Node_Expression_Binary_GreaterEqual extends Twig_Node_Expression_Bina return $compiler->raw('>='); } } + +class_alias('Twig_Node_Expression_Binary_GreaterEqual', 'Twig\Node\Expression\Binary\GreaterEqualBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/In.php b/inc/lib/Twig/Node/Expression/Binary/In.php index 788f9377..af112448 100644 --- a/inc/lib/Twig/Node/Expression/Binary/In.php +++ b/inc/lib/Twig/Node/Expression/Binary/In.php @@ -3,18 +3,13 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ class Twig_Node_Expression_Binary_In extends Twig_Node_Expression_Binary { - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler @@ -31,3 +26,5 @@ class Twig_Node_Expression_Binary_In extends Twig_Node_Expression_Binary return $compiler->raw('in'); } } + +class_alias('Twig_Node_Expression_Binary_In', 'Twig\Node\Expression\Binary\InBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/Less.php b/inc/lib/Twig/Node/Expression/Binary/Less.php index 45fd3004..ab8fc1f9 100644 --- a/inc/lib/Twig/Node/Expression/Binary/Less.php +++ b/inc/lib/Twig/Node/Expression/Binary/Less.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,3 +15,5 @@ class Twig_Node_Expression_Binary_Less extends Twig_Node_Expression_Binary return $compiler->raw('<'); } } + +class_alias('Twig_Node_Expression_Binary_Less', 'Twig\Node\Expression\Binary\LessBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/LessEqual.php b/inc/lib/Twig/Node/Expression/Binary/LessEqual.php index e38e257c..71a279e9 100644 --- a/inc/lib/Twig/Node/Expression/Binary/LessEqual.php +++ b/inc/lib/Twig/Node/Expression/Binary/LessEqual.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,3 +15,5 @@ class Twig_Node_Expression_Binary_LessEqual extends Twig_Node_Expression_Binary return $compiler->raw('<='); } } + +class_alias('Twig_Node_Expression_Binary_LessEqual', 'Twig\Node\Expression\Binary\LessEqualBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/Matches.php b/inc/lib/Twig/Node/Expression/Binary/Matches.php new file mode 100644 index 00000000..5cb85584 --- /dev/null +++ b/inc/lib/Twig/Node/Expression/Binary/Matches.php @@ -0,0 +1,30 @@ +raw('preg_match(') + ->subcompile($this->getNode('right')) + ->raw(', ') + ->subcompile($this->getNode('left')) + ->raw(')') + ; + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw(''); + } +} + +class_alias('Twig_Node_Expression_Binary_Matches', 'Twig\Node\Expression\Binary\MatchesBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/Mod.php b/inc/lib/Twig/Node/Expression/Binary/Mod.php index 9924114f..28109633 100644 --- a/inc/lib/Twig/Node/Expression/Binary/Mod.php +++ b/inc/lib/Twig/Node/Expression/Binary/Mod.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,3 +16,5 @@ class Twig_Node_Expression_Binary_Mod extends Twig_Node_Expression_Binary return $compiler->raw('%'); } } + +class_alias('Twig_Node_Expression_Binary_Mod', 'Twig\Node\Expression\Binary\ModBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/Mul.php b/inc/lib/Twig/Node/Expression/Binary/Mul.php index c91529ca..790c6a22 100644 --- a/inc/lib/Twig/Node/Expression/Binary/Mul.php +++ b/inc/lib/Twig/Node/Expression/Binary/Mul.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,3 +16,5 @@ class Twig_Node_Expression_Binary_Mul extends Twig_Node_Expression_Binary return $compiler->raw('*'); } } + +class_alias('Twig_Node_Expression_Binary_Mul', 'Twig\Node\Expression\Binary\MulBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/NotEqual.php b/inc/lib/Twig/Node/Expression/Binary/NotEqual.php index 26867ba2..bb45c9ed 100644 --- a/inc/lib/Twig/Node/Expression/Binary/NotEqual.php +++ b/inc/lib/Twig/Node/Expression/Binary/NotEqual.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,3 +15,5 @@ class Twig_Node_Expression_Binary_NotEqual extends Twig_Node_Expression_Binary return $compiler->raw('!='); } } + +class_alias('Twig_Node_Expression_Binary_NotEqual', 'Twig\Node\Expression\Binary\NotEqualBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/NotIn.php b/inc/lib/Twig/Node/Expression/Binary/NotIn.php index f347b7b6..9dedf92f 100644 --- a/inc/lib/Twig/Node/Expression/Binary/NotIn.php +++ b/inc/lib/Twig/Node/Expression/Binary/NotIn.php @@ -3,18 +3,13 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ class Twig_Node_Expression_Binary_NotIn extends Twig_Node_Expression_Binary { - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler @@ -31,3 +26,5 @@ class Twig_Node_Expression_Binary_NotIn extends Twig_Node_Expression_Binary return $compiler->raw('not in'); } } + +class_alias('Twig_Node_Expression_Binary_NotIn', 'Twig\Node\Expression\Binary\NotInBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/Or.php b/inc/lib/Twig/Node/Expression/Binary/Or.php index adba49c6..dc9eece1 100644 --- a/inc/lib/Twig/Node/Expression/Binary/Or.php +++ b/inc/lib/Twig/Node/Expression/Binary/Or.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,3 +16,5 @@ class Twig_Node_Expression_Binary_Or extends Twig_Node_Expression_Binary return $compiler->raw('||'); } } + +class_alias('Twig_Node_Expression_Binary_Or', 'Twig\Node\Expression\Binary\OrBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/Power.php b/inc/lib/Twig/Node/Expression/Binary/Power.php index b2c59040..d24777bd 100644 --- a/inc/lib/Twig/Node/Expression/Binary/Power.php +++ b/inc/lib/Twig/Node/Expression/Binary/Power.php @@ -3,20 +3,19 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ class Twig_Node_Expression_Binary_Power extends Twig_Node_Expression_Binary { - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { + if (PHP_VERSION_ID >= 50600) { + return parent::compile($compiler); + } + $compiler ->raw('pow(') ->subcompile($this->getNode('left')) @@ -31,3 +30,5 @@ class Twig_Node_Expression_Binary_Power extends Twig_Node_Expression_Binary return $compiler->raw('**'); } } + +class_alias('Twig_Node_Expression_Binary_Power', 'Twig\Node\Expression\Binary\PowerBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/Range.php b/inc/lib/Twig/Node/Expression/Binary/Range.php index bea4f2a6..187f6765 100644 --- a/inc/lib/Twig/Node/Expression/Binary/Range.php +++ b/inc/lib/Twig/Node/Expression/Binary/Range.php @@ -3,18 +3,13 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ class Twig_Node_Expression_Binary_Range extends Twig_Node_Expression_Binary { - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler @@ -31,3 +26,5 @@ class Twig_Node_Expression_Binary_Range extends Twig_Node_Expression_Binary return $compiler->raw('..'); } } + +class_alias('Twig_Node_Expression_Binary_Range', 'Twig\Node\Expression\Binary\RangeBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/StartsWith.php b/inc/lib/Twig/Node/Expression/Binary/StartsWith.php new file mode 100644 index 00000000..7e43b8de --- /dev/null +++ b/inc/lib/Twig/Node/Expression/Binary/StartsWith.php @@ -0,0 +1,32 @@ +getVarName(); + $right = $compiler->getVarName(); + $compiler + ->raw(sprintf('(is_string($%s = ', $left)) + ->subcompile($this->getNode('left')) + ->raw(sprintf(') && is_string($%s = ', $right)) + ->subcompile($this->getNode('right')) + ->raw(sprintf(') && (\'\' === $%2$s || 0 === strpos($%1$s, $%2$s)))', $left, $right)) + ; + } + + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw(''); + } +} + +class_alias('Twig_Node_Expression_Binary_StartsWith', 'Twig\Node\Expression\Binary\StartsWithBinary', false); diff --git a/inc/lib/Twig/Node/Expression/Binary/Sub.php b/inc/lib/Twig/Node/Expression/Binary/Sub.php index d4463991..cff8ed07 100644 --- a/inc/lib/Twig/Node/Expression/Binary/Sub.php +++ b/inc/lib/Twig/Node/Expression/Binary/Sub.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,3 +16,5 @@ class Twig_Node_Expression_Binary_Sub extends Twig_Node_Expression_Binary return $compiler->raw('-'); } } + +class_alias('Twig_Node_Expression_Binary_Sub', 'Twig\Node\Expression\Binary\SubBinary', false); diff --git a/inc/lib/Twig/Node/Expression/BlockReference.php b/inc/lib/Twig/Node/Expression/BlockReference.php index 647196eb..37a3983d 100644 --- a/inc/lib/Twig/Node/Expression/BlockReference.php +++ b/inc/lib/Twig/Node/Expression/BlockReference.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,35 +17,77 @@ */ 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); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { - if ($this->getAttribute('as_string')) { - $compiler->raw('(string) '); + if ($this->getAttribute('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(')'); } } + +class_alias('Twig_Node_Expression_BlockReference', 'Twig\Node\Expression\BlockReferenceExpression', false); diff --git a/inc/lib/Twig/Node/Expression/Call.php b/inc/lib/Twig/Node/Expression/Call.php index dba9b0e6..d962b6a5 100644 --- a/inc/lib/Twig/Node/Expression/Call.php +++ b/inc/lib/Twig/Node/Expression/Call.php @@ -3,27 +3,36 @@ /* * This file is part of Twig. * - * (c) 2012 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression { + private $reflector; + protected function compileCallable(Twig_Compiler $compiler) { - $callable = $this->getAttribute('callable'); - $closingParenthesis = false; - if ($callable) { - if (is_string($callable)) { + if ($this->hasAttribute('callable') && $callable = $this->getAttribute('callable')) { + if (is_string($callable) && false === strpos($callable, '::')) { $compiler->raw($callable); - } elseif (is_array($callable) && $callable[0] instanceof Twig_ExtensionInterface) { - $compiler->raw(sprintf('$this->env->getExtension(\'%s\')->%s', $callable[0]->getName(), $callable[1])); } else { - $type = ucfirst($this->getAttribute('type')); - $compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), array', $type, $this->getAttribute('name'))); - $closingParenthesis = true; + list($r, $callable) = $this->reflectCallable($callable); + if ($r instanceof ReflectionMethod && is_string($callable[0])) { + if ($r->isStatic()) { + $compiler->raw(sprintf('%s::%s', $callable[0], $callable[1])); + } else { + $compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1])); + } + } elseif ($r instanceof ReflectionMethod && $callable[0] instanceof Twig_ExtensionInterface) { + $compiler->raw(sprintf('$this->env->getExtension(\'%s\')->%s', get_class($callable[0]), $callable[1])); + } else { + $type = ucfirst($this->getAttribute('type')); + $compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), array', $type, $this->getAttribute('name'))); + $closingParenthesis = true; + } } } else { $compiler->raw($this->getAttribute('thing')->compile()); @@ -73,7 +82,7 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression $first = false; } - if ($this->hasNode('arguments') && null !== $this->getNode('arguments')) { + if ($this->hasNode('arguments')) { $callable = $this->hasAttribute('callable') ? $this->getAttribute('callable') : null; $arguments = $this->getArguments($callable, $this->getNode('arguments')); @@ -92,6 +101,9 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression protected function getArguments($callable, $arguments) { + $callType = $this->getAttribute('type'); + $callName = $this->getAttribute('name'); + $parameters = array(); $named = false; foreach ($arguments as $name => $node) { @@ -99,73 +111,101 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression $named = true; $name = $this->normalizeName($name); } elseif ($named) { - throw new Twig_Error_Syntax(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $this->getAttribute('type'), $this->getAttribute('name'))); + throw new Twig_Error_Syntax(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName)); } $parameters[$name] = $node; } - if (!$named) { + $isVariadic = $this->hasAttribute('is_variadic') && $this->getAttribute('is_variadic'); + if (!$named && !$isVariadic) { return $parameters; } if (!$callable) { - throw new LogicException(sprintf('Named arguments are not supported for %s "%s".', $this->getAttribute('type'), $this->getAttribute('name'))); - } - - // manage named arguments - if (is_array($callable)) { - $r = new ReflectionMethod($callable[0], $callable[1]); - } elseif (is_object($callable) && !$callable instanceof Closure) { - $r = new ReflectionObject($callable); - $r = $r->getMethod('__invoke'); - } else { - $r = new ReflectionFunction($callable); - } - - $definition = $r->getParameters(); - if ($this->hasNode('node')) { - array_shift($definition); - } - if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { - array_shift($definition); - } - if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { - array_shift($definition); - } - if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) { - foreach ($this->getAttribute('arguments') as $argument) { - array_shift($definition); + if ($named) { + $message = sprintf('Named arguments are not supported for %s "%s".', $callType, $callName); + } else { + $message = sprintf('Arbitrary positional arguments are not supported for %s "%s".', $callType, $callName); } + + throw new LogicException($message); } + $callableParameters = $this->getCallableParameters($callable, $isVariadic); $arguments = array(); + $names = array(); + $missingArguments = array(); + $optionalArguments = array(); $pos = 0; - foreach ($definition as $param) { - $name = $this->normalizeName($param->name); + foreach ($callableParameters as $callableParameter) { + $names[] = $name = $this->normalizeName($callableParameter->name); if (array_key_exists($name, $parameters)) { if (array_key_exists($pos, $parameters)) { - throw new Twig_Error_Syntax(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name'))); + throw new Twig_Error_Syntax(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName)); + } + + 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)) + ); } + $arguments = array_merge($arguments, $optionalArguments); $arguments[] = $parameters[$name]; unset($parameters[$name]); + $optionalArguments = array(); } elseif (array_key_exists($pos, $parameters)) { + $arguments = array_merge($arguments, $optionalArguments); $arguments[] = $parameters[$pos]; unset($parameters[$pos]); + $optionalArguments = array(); ++$pos; - } elseif ($param->isDefaultValueAvailable()) { - $arguments[] = new Twig_Node_Expression_Constant($param->getDefaultValue(), -1); - } elseif ($param->isOptional()) { - break; + } elseif ($callableParameter->isDefaultValueAvailable()) { + $optionalArguments[] = new Twig_Node_Expression_Constant($callableParameter->getDefaultValue(), -1); + } elseif ($callableParameter->isOptional()) { + if (empty($parameters)) { + break; + } else { + $missingArguments[] = $name; + } } else { - throw new Twig_Error_Syntax(sprintf('Value for argument "%s" is required for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name'))); + throw new Twig_Error_Syntax(sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName)); + } + } + + if ($isVariadic) { + $arbitraryArguments = new Twig_Node_Expression_Array(array(), -1); + foreach ($parameters as $key => $value) { + if (is_int($key)) { + $arbitraryArguments->addElement($value); + } else { + $arbitraryArguments->addElement($value, new Twig_Node_Expression_Constant($key, -1)); + } + unset($parameters[$key]); + } + + if ($arbitraryArguments->count()) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $arbitraryArguments; } } if (!empty($parameters)) { - throw new Twig_Error_Syntax(sprintf('Unknown argument%s "%s" for %s "%s".', count($parameters) > 1 ? 's' : '' , implode('", "', array_keys($parameters)), $this->getAttribute('type'), $this->getAttribute('name'))); + $unknownParameter = null; + foreach ($parameters as $parameter) { + if ($parameter instanceof Twig_Node) { + $unknownParameter = $parameter; + break; + } + } + + 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->getTemplateLine() : -1); } return $arguments; @@ -175,4 +215,77 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression { return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), $name)); } + + private function getCallableParameters($callable, $isVariadic) + { + list($r) = $this->reflectCallable($callable); + if (null === $r) { + return array(); + } + + $parameters = $r->getParameters(); + if ($this->hasNode('node')) { + array_shift($parameters); + } + if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { + array_shift($parameters); + } + if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { + array_shift($parameters); + } + if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) { + foreach ($this->getAttribute('arguments') as $argument) { + array_shift($parameters); + } + } + if ($isVariadic) { + $argument = end($parameters); + if ($argument && $argument->isArray() && $argument->isDefaultValueAvailable() && array() === $argument->getDefaultValue()) { + array_pop($parameters); + } else { + $callableName = $r->name; + if ($r instanceof ReflectionMethod) { + $callableName = $r->getDeclaringClass()->name.'::'.$callableName; + } + + throw new LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = array()".', $callableName, $this->getAttribute('type'), $this->getAttribute('name'))); + } + } + + return $parameters; + } + + private function reflectCallable($callable) + { + if (null !== $this->reflector) { + return $this->reflector; + } + + 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($class, $method); + } else { + $r = new ReflectionFunction($callable); + } + + return $this->reflector = array($r, $callable); + } } + +class_alias('Twig_Node_Expression_Call', 'Twig\Node\Expression\CallExpression', false); diff --git a/inc/lib/Twig/Node/Expression/Conditional.php b/inc/lib/Twig/Node/Expression/Conditional.php index edcb1e2d..c339d773 100644 --- a/inc/lib/Twig/Node/Expression/Conditional.php +++ b/inc/lib/Twig/Node/Expression/Conditional.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -29,3 +29,5 @@ class Twig_Node_Expression_Conditional extends Twig_Node_Expression ; } } + +class_alias('Twig_Node_Expression_Conditional', 'Twig\Node\Expression\ConditionalExpression', false); diff --git a/inc/lib/Twig/Node/Expression/Constant.php b/inc/lib/Twig/Node/Expression/Constant.php index a91dc698..bf4d031c 100644 --- a/inc/lib/Twig/Node/Expression/Constant.php +++ b/inc/lib/Twig/Node/Expression/Constant.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -21,3 +21,5 @@ class Twig_Node_Expression_Constant extends Twig_Node_Expression $compiler->repr($this->getAttribute('value')); } } + +class_alias('Twig_Node_Expression_Constant', 'Twig\Node\Expression\ConstantExpression', false); diff --git a/inc/lib/Twig/Node/Expression/ExtensionReference.php b/inc/lib/Twig/Node/Expression/ExtensionReference.php index 00ac6701..114b5cd9 100644 --- a/inc/lib/Twig/Node/Expression/ExtensionReference.php +++ b/inc/lib/Twig/Node/Expression/ExtensionReference.php @@ -3,16 +3,20 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Node_Expression_ExtensionReference class is deprecated since version 1.23 and will be removed in 2.0.', E_USER_DEPRECATED); + /** * Represents an extension call node. * * @author Fabien Potencier + * + * @deprecated since 1.23 and will be removed in 2.0. */ class Twig_Node_Expression_ExtensionReference extends Twig_Node_Expression { @@ -21,11 +25,6 @@ class Twig_Node_Expression_ExtensionReference extends Twig_Node_Expression parent::__construct(array(), array('name' => $name), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler->raw(sprintf("\$this->env->getExtension('%s')", $this->getAttribute('name'))); diff --git a/inc/lib/Twig/Node/Expression/Filter.php b/inc/lib/Twig/Node/Expression/Filter.php index 207b062a..12da1d67 100644 --- a/inc/lib/Twig/Node/Expression/Filter.php +++ b/inc/lib/Twig/Node/Expression/Filter.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -30,7 +30,12 @@ class Twig_Node_Expression_Filter extends Twig_Node_Expression_Call if ($filter instanceof Twig_FilterCallableInterface || $filter instanceof Twig_SimpleFilter) { $this->setAttribute('callable', $filter->getCallable()); } + if ($filter instanceof Twig_SimpleFilter) { + $this->setAttribute('is_variadic', $filter->isVariadic()); + } $this->compileCallable($compiler); } } + +class_alias('Twig_Node_Expression_Filter', 'Twig\Node\Expression\FilterExpression', false); diff --git a/inc/lib/Twig/Node/Expression/Filter/Default.php b/inc/lib/Twig/Node/Expression/Filter/Default.php index 1827c888..d2e19d54 100644 --- a/inc/lib/Twig/Node/Expression/Filter/Default.php +++ b/inc/lib/Twig/Node/Expression/Filter/Default.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -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; } @@ -41,3 +41,5 @@ class Twig_Node_Expression_Filter_Default extends Twig_Node_Expression_Filter $compiler->subcompile($this->getNode('node')); } } + +class_alias('Twig_Node_Expression_Filter_Default', 'Twig\Node\Expression\Filter\DefaultFilter', false); diff --git a/inc/lib/Twig/Node/Expression/Function.php b/inc/lib/Twig/Node/Expression/Function.php index 3e1f6b55..cdee7c97 100644 --- a/inc/lib/Twig/Node/Expression/Function.php +++ b/inc/lib/Twig/Node/Expression/Function.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -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,9 +27,19 @@ class Twig_Node_Expression_Function extends Twig_Node_Expression_Call $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()); } $this->compileCallable($compiler); } } + +class_alias('Twig_Node_Expression_Function', 'Twig\Node\Expression\FunctionExpression', false); diff --git a/inc/lib/Twig/Node/Expression/GetAttr.php b/inc/lib/Twig/Node/Expression/GetAttr.php index 55d9fcc3..b7823acc 100644 --- a/inc/lib/Twig/Node/Expression/GetAttr.php +++ b/inc/lib/Twig/Node/Expression/GetAttr.php @@ -3,21 +3,30 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ class Twig_Node_Expression_GetAttr extends Twig_Node_Expression { - public function __construct(Twig_Node_Expression $node, Twig_Node_Expression $attribute, Twig_Node_Expression_Array $arguments, $type, $lineno) + public function __construct(Twig_Node_Expression $node, Twig_Node_Expression $attribute, Twig_Node_Expression $arguments = null, $type, $lineno) { - parent::__construct(array('node' => $node, 'attribute' => $attribute, 'arguments' => $arguments), array('type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'disable_c_ext' => false), $lineno); + $nodes = array('node' => $node, 'attribute' => $attribute); + if (null !== $arguments) { + $nodes['arguments'] = $arguments; + } + + parent::__construct($nodes, array('type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'disable_c_ext' => false), $lineno); } public function compile(Twig_Compiler $compiler) { + if ($this->getAttribute('disable_c_ext')) { + @trigger_error(sprintf('Using the "disable_c_ext" attribute on %s is deprecated since version 1.30 and will be removed in 2.0.', __CLASS__), E_USER_DEPRECATED); + } + if (function_exists('twig_template_get_attributes') && !$this->getAttribute('disable_c_ext')) { $compiler->raw('twig_template_get_attributes($this, '); } else { @@ -32,22 +41,34 @@ class Twig_Node_Expression_GetAttr extends Twig_Node_Expression $compiler->raw(', ')->subcompile($this->getNode('attribute')); - if (count($this->getNode('arguments')) || Twig_Template::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) { - $compiler->raw(', ')->subcompile($this->getNode('arguments')); + // only generate optional arguments when needed (to make generated code more readable) + $needFourth = $this->getAttribute('ignore_strict_check'); + $needThird = $needFourth || $this->getAttribute('is_defined_test'); + $needSecond = $needThird || Twig_Template::ANY_CALL !== $this->getAttribute('type'); + $needFirst = $needSecond || $this->hasNode('arguments'); - if (Twig_Template::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) { - $compiler->raw(', ')->repr($this->getAttribute('type')); + if ($needFirst) { + if ($this->hasNode('arguments')) { + $compiler->raw(', ')->subcompile($this->getNode('arguments')); + } else { + $compiler->raw(', array()'); } + } - if ($this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) { - $compiler->raw(', '.($this->getAttribute('is_defined_test') ? 'true' : 'false')); - } + if ($needSecond) { + $compiler->raw(', ')->repr($this->getAttribute('type')); + } - if ($this->getAttribute('ignore_strict_check')) { - $compiler->raw(', '.($this->getAttribute('ignore_strict_check') ? 'true' : 'false')); - } + if ($needThird) { + $compiler->raw(', ')->repr($this->getAttribute('is_defined_test')); + } + + if ($needFourth) { + $compiler->raw(', ')->repr($this->getAttribute('ignore_strict_check')); } $compiler->raw(')'); } } + +class_alias('Twig_Node_Expression_GetAttr', 'Twig\Node\Expression\GetAttrExpression', false); diff --git a/inc/lib/Twig/Node/Expression/MethodCall.php b/inc/lib/Twig/Node/Expression/MethodCall.php index 620b02bf..709016eb 100644 --- a/inc/lib/Twig/Node/Expression/MethodCall.php +++ b/inc/lib/Twig/Node/Expression/MethodCall.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2012 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -39,3 +39,5 @@ class Twig_Node_Expression_MethodCall extends Twig_Node_Expression $compiler->raw(')'); } } + +class_alias('Twig_Node_Expression_MethodCall', 'Twig\Node\Expression\MethodCallExpression', false); diff --git a/inc/lib/Twig/Node/Expression/Name.php b/inc/lib/Twig/Node/Expression/Name.php index 3b8fae01..9d5a21f8 100644 --- a/inc/lib/Twig/Node/Expression/Name.php +++ b/inc/lib/Twig/Node/Expression/Name.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,7 +12,7 @@ class Twig_Node_Expression_Name extends Twig_Node_Expression { protected $specialVars = array( - '_self' => '$this', + '_self' => '$this', '_context' => '$context', '_charset' => '$this->env->getCharset()', ); @@ -26,6 +26,8 @@ class Twig_Node_Expression_Name extends Twig_Node_Expression { $name = $this->getAttribute('name'); + $compiler->addDebugInfo($this); + if ($this->getAttribute('is_defined_test')) { if ($this->isSpecial()) { $compiler->repr(true); @@ -41,10 +43,20 @@ class Twig_Node_Expression_Name extends Twig_Node_Expression ->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 (version_compare(phpversion(), '5.4.0RC1', '>=')) { + 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[') @@ -86,3 +98,5 @@ class Twig_Node_Expression_Name extends Twig_Node_Expression return !$this->isSpecial() && !$this->getAttribute('is_defined_test'); } } + +class_alias('Twig_Node_Expression_Name', 'Twig\Node\Expression\NameExpression', false); diff --git a/inc/lib/Twig/Node/Expression/NullCoalesce.php b/inc/lib/Twig/Node/Expression/NullCoalesce.php new file mode 100644 index 00000000..eaafa4c9 --- /dev/null +++ b/inc/lib/Twig/Node/Expression/NullCoalesce.php @@ -0,0 +1,48 @@ +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); + } + } +} + +class_alias('Twig_Node_Expression_NullCoalesce', 'Twig\Node\Expression\NullCoalesceExpression', false); diff --git a/inc/lib/Twig/Node/Expression/Parent.php b/inc/lib/Twig/Node/Expression/Parent.php index dcf618c0..78692db2 100644 --- a/inc/lib/Twig/Node/Expression/Parent.php +++ b/inc/lib/Twig/Node/Expression/Parent.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -22,26 +22,23 @@ class Twig_Node_Expression_Parent extends Twig_Node_Expression parent::__construct(array(), array('output' => false, 'name' => $name), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { if ($this->getAttribute('output')) { $compiler ->addDebugInfo($this) - ->write("\$this->displayParentBlock(") + ->write('$this->displayParentBlock(') ->string($this->getAttribute('name')) ->raw(", \$context, \$blocks);\n") ; } else { $compiler - ->raw("\$this->renderParentBlock(") + ->raw('$this->renderParentBlock(') ->string($this->getAttribute('name')) - ->raw(", \$context, \$blocks)") + ->raw(', $context, $blocks)') ; } } } + +class_alias('Twig_Node_Expression_Parent', 'Twig\Node\Expression\ParentExpression', false); diff --git a/inc/lib/Twig/Node/Expression/TempName.php b/inc/lib/Twig/Node/Expression/TempName.php index e6b058e8..0a86e003 100644 --- a/inc/lib/Twig/Node/Expression/TempName.php +++ b/inc/lib/Twig/Node/Expression/TempName.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -24,3 +24,5 @@ class Twig_Node_Expression_TempName extends Twig_Node_Expression ; } } + +class_alias('Twig_Node_Expression_TempName', 'Twig\Node\Expression\TempNameExpression', false); diff --git a/inc/lib/Twig/Node/Expression/Test.php b/inc/lib/Twig/Node/Expression/Test.php index 639f501a..ad102ba6 100644 --- a/inc/lib/Twig/Node/Expression/Test.php +++ b/inc/lib/Twig/Node/Expression/Test.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,7 +12,12 @@ class Twig_Node_Expression_Test extends Twig_Node_Expression_Call { public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $arguments = null, $lineno) { - parent::__construct(array('node' => $node, 'arguments' => $arguments), array('name' => $name), $lineno); + $nodes = array('node' => $node); + if (null !== $arguments) { + $nodes['arguments'] = $arguments; + } + + parent::__construct($nodes, array('name' => $name), $lineno); } public function compile(Twig_Compiler $compiler) @@ -26,7 +31,12 @@ class Twig_Node_Expression_Test extends Twig_Node_Expression_Call if ($test instanceof Twig_TestCallableInterface || $test instanceof Twig_SimpleTest) { $this->setAttribute('callable', $test->getCallable()); } + if ($test instanceof Twig_SimpleTest) { + $this->setAttribute('is_variadic', $test->isVariadic()); + } $this->compileCallable($compiler); } } + +class_alias('Twig_Node_Expression_Test', 'Twig\Node\Expression\TestExpression', false); diff --git a/inc/lib/Twig/Node/Expression/Test/Constant.php b/inc/lib/Twig/Node/Expression/Test/Constant.php index de55f5f5..a51a4ba1 100644 --- a/inc/lib/Twig/Node/Expression/Test/Constant.php +++ b/inc/lib/Twig/Node/Expression/Test/Constant.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -44,3 +44,5 @@ class Twig_Node_Expression_Test_Constant extends Twig_Node_Expression_Test ; } } + +class_alias('Twig_Node_Expression_Test_Constant', 'Twig\Node\Expression\Test\ConstantTest', false); diff --git a/inc/lib/Twig/Node/Expression/Test/Defined.php b/inc/lib/Twig/Node/Expression/Test/Defined.php index 247b2e23..2136c390 100644 --- a/inc/lib/Twig/Node/Expression/Test/Defined.php +++ b/inc/lib/Twig/Node/Expression/Test/Defined.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -25,17 +25,22 @@ class Twig_Node_Expression_Test_Defined extends Twig_Node_Expression_Test { public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $arguments = null, $lineno) { - parent::__construct($node, $name, $arguments, $lineno); - if ($node instanceof Twig_Node_Expression_Name) { $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->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); } protected function changeIgnoreStrictCheck(Twig_Node_Expression_GetAttr $node) @@ -52,3 +57,5 @@ class Twig_Node_Expression_Test_Defined extends Twig_Node_Expression_Test $compiler->subcompile($this->getNode('node')); } } + +class_alias('Twig_Node_Expression_Test_Defined', 'Twig\Node\Expression\Test\DefinedTest', false); diff --git a/inc/lib/Twig/Node/Expression/Test/Divisibleby.php b/inc/lib/Twig/Node/Expression/Test/Divisibleby.php index 0aceb530..a5d71961 100644 --- a/inc/lib/Twig/Node/Expression/Test/Divisibleby.php +++ b/inc/lib/Twig/Node/Expression/Test/Divisibleby.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,7 +13,7 @@ * Checks if a variable is divisible by a number. * *
- *  {% if loop.index is divisibleby(3) %}
+ *  {% if loop.index is divisible by(3) %}
  * 
* * @author Fabien Potencier @@ -31,3 +31,5 @@ class Twig_Node_Expression_Test_Divisibleby extends Twig_Node_Expression_Test ; } } + +class_alias('Twig_Node_Expression_Test_Divisibleby', 'Twig\Node\Expression\Test\DivisiblebyTest', false); diff --git a/inc/lib/Twig/Node/Expression/Test/Even.php b/inc/lib/Twig/Node/Expression/Test/Even.php index d7853e89..7e198bea 100644 --- a/inc/lib/Twig/Node/Expression/Test/Even.php +++ b/inc/lib/Twig/Node/Expression/Test/Even.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -30,3 +30,5 @@ class Twig_Node_Expression_Test_Even extends Twig_Node_Expression_Test ; } } + +class_alias('Twig_Node_Expression_Test_Even', 'Twig\Node\Expression\Test\EvenTest', false); diff --git a/inc/lib/Twig/Node/Expression/Test/Null.php b/inc/lib/Twig/Node/Expression/Test/Null.php index 1c83825a..3746e4cb 100644 --- a/inc/lib/Twig/Node/Expression/Test/Null.php +++ b/inc/lib/Twig/Node/Expression/Test/Null.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -29,3 +29,5 @@ class Twig_Node_Expression_Test_Null extends Twig_Node_Expression_Test ; } } + +class_alias('Twig_Node_Expression_Test_Null', 'Twig\Node\Expression\Test\NullTest', false); diff --git a/inc/lib/Twig/Node/Expression/Test/Odd.php b/inc/lib/Twig/Node/Expression/Test/Odd.php index 421c19e8..0c6c099b 100644 --- a/inc/lib/Twig/Node/Expression/Test/Odd.php +++ b/inc/lib/Twig/Node/Expression/Test/Odd.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -30,3 +30,5 @@ class Twig_Node_Expression_Test_Odd extends Twig_Node_Expression_Test ; } } + +class_alias('Twig_Node_Expression_Test_Odd', 'Twig\Node\Expression\Test\OddTest', false); diff --git a/inc/lib/Twig/Node/Expression/Test/Sameas.php b/inc/lib/Twig/Node/Expression/Test/Sameas.php index b48905ee..e95ff1f2 100644 --- a/inc/lib/Twig/Node/Expression/Test/Sameas.php +++ b/inc/lib/Twig/Node/Expression/Test/Sameas.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -27,3 +27,5 @@ class Twig_Node_Expression_Test_Sameas extends Twig_Node_Expression_Test ; } } + +class_alias('Twig_Node_Expression_Test_Sameas', 'Twig\Node\Expression\Test\SameasTest', false); diff --git a/inc/lib/Twig/Node/Expression/Unary.php b/inc/lib/Twig/Node/Expression/Unary.php index c514388e..5804485e 100644 --- a/inc/lib/Twig/Node/Expression/Unary.php +++ b/inc/lib/Twig/Node/Expression/Unary.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -18,13 +18,12 @@ abstract class Twig_Node_Expression_Unary extends Twig_Node_Expression public function compile(Twig_Compiler $compiler) { - $compiler->raw('('); + $compiler->raw(' '); $this->operator($compiler); - $compiler - ->subcompile($this->getNode('node')) - ->raw(')') - ; + $compiler->subcompile($this->getNode('node')); } abstract public function operator(Twig_Compiler $compiler); } + +class_alias('Twig_Node_Expression_Unary', 'Twig\Node\Expression\Unary\AbstractUnary', false); diff --git a/inc/lib/Twig/Node/Expression/Unary/Neg.php b/inc/lib/Twig/Node/Expression/Unary/Neg.php index 2a3937ec..039d933d 100644 --- a/inc/lib/Twig/Node/Expression/Unary/Neg.php +++ b/inc/lib/Twig/Node/Expression/Unary/Neg.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,3 +16,5 @@ class Twig_Node_Expression_Unary_Neg extends Twig_Node_Expression_Unary $compiler->raw('-'); } } + +class_alias('Twig_Node_Expression_Unary_Neg', 'Twig\Node\Expression\Unary\NegUnary', false); diff --git a/inc/lib/Twig/Node/Expression/Unary/Not.php b/inc/lib/Twig/Node/Expression/Unary/Not.php index f94073cf..a0860b18 100644 --- a/inc/lib/Twig/Node/Expression/Unary/Not.php +++ b/inc/lib/Twig/Node/Expression/Unary/Not.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,3 +16,5 @@ class Twig_Node_Expression_Unary_Not extends Twig_Node_Expression_Unary $compiler->raw('!'); } } + +class_alias('Twig_Node_Expression_Unary_Not', 'Twig\Node\Expression\Unary\NotUnary', false); diff --git a/inc/lib/Twig/Node/Expression/Unary/Pos.php b/inc/lib/Twig/Node/Expression/Unary/Pos.php index 04edb52a..eeff5452 100644 --- a/inc/lib/Twig/Node/Expression/Unary/Pos.php +++ b/inc/lib/Twig/Node/Expression/Unary/Pos.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,3 +16,5 @@ class Twig_Node_Expression_Unary_Pos extends Twig_Node_Expression_Unary $compiler->raw('+'); } } + +class_alias('Twig_Node_Expression_Unary_Pos', 'Twig\Node\Expression\Unary\PosUnary', false); diff --git a/inc/lib/Twig/Node/Flush.php b/inc/lib/Twig/Node/Flush.php index 0467ddce..fcc461ac 100644 --- a/inc/lib/Twig/Node/Flush.php +++ b/inc/lib/Twig/Node/Flush.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -21,11 +21,6 @@ class Twig_Node_Flush extends Twig_Node parent::__construct(array(), array(), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler @@ -34,3 +29,5 @@ class Twig_Node_Flush extends Twig_Node ; } } + +class_alias('Twig_Node_Flush', 'Twig\Node\FlushNode', false); diff --git a/inc/lib/Twig/Node/For.php b/inc/lib/Twig/Node/For.php index d1ff371d..914b70c9 100644 --- a/inc/lib/Twig/Node/For.php +++ b/inc/lib/Twig/Node/For.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -27,26 +27,25 @@ class Twig_Node_For extends Twig_Node $body = new Twig_Node_If(new Twig_Node(array($ifexpr, $body)), null, $lineno, $tag); } - parent::__construct(array('key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body, 'else' => $else), array('with_loop' => true, 'ifexpr' => null !== $ifexpr), $lineno, $tag); + $nodes = array('key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body); + if (null !== $else) { + $nodes['else'] = $else; + } + + parent::__construct($nodes, array('with_loop' => true, 'ifexpr' => null !== $ifexpr), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler ->addDebugInfo($this) - // the (array) cast bypasses a PHP 5.2.6 bug - ->write("\$context['_parent'] = (array) \$context;\n") + ->write("\$context['_parent'] = \$context;\n") ->write("\$context['_seq'] = twig_ensure_traversable(") ->subcompile($this->getNode('seq')) ->raw(");\n") ; - if (null !== $this->getNode('else')) { + if ($this->hasNode('else')) { $compiler->write("\$context['_iterated'] = false;\n"); } @@ -75,14 +74,14 @@ class Twig_Node_For extends Twig_Node } } - $this->loop->setAttribute('else', null !== $this->getNode('else')); + $this->loop->setAttribute('else', $this->hasNode('else')); $this->loop->setAttribute('with_loop', $this->getAttribute('with_loop')); $this->loop->setAttribute('ifexpr', $this->getAttribute('ifexpr')); $compiler ->write("foreach (\$context['_seq'] as ") ->subcompile($this->getNode('key_target')) - ->raw(" => ") + ->raw(' => ') ->subcompile($this->getNode('value_target')) ->raw(") {\n") ->indent() @@ -91,7 +90,7 @@ class Twig_Node_For extends Twig_Node ->write("}\n") ; - if (null !== $this->getNode('else')) { + if ($this->hasNode('else')) { $compiler ->write("if (!\$context['_iterated']) {\n") ->indent() @@ -110,3 +109,5 @@ class Twig_Node_For extends Twig_Node $compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n"); } } + +class_alias('Twig_Node_For', 'Twig\Node\ForNode', false); diff --git a/inc/lib/Twig/Node/ForLoop.php b/inc/lib/Twig/Node/ForLoop.php index b8841583..06477cf1 100644 --- a/inc/lib/Twig/Node/ForLoop.php +++ b/inc/lib/Twig/Node/ForLoop.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -21,11 +21,6 @@ class Twig_Node_ForLoop extends Twig_Node parent::__construct(array(), array('with_loop' => false, 'ifexpr' => false, 'else' => false), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { if ($this->getAttribute('else')) { @@ -53,3 +48,5 @@ class Twig_Node_ForLoop extends Twig_Node } } } + +class_alias('Twig_Node_ForLoop', 'Twig\Node\ForLoopNode', false); diff --git a/inc/lib/Twig/Node/If.php b/inc/lib/Twig/Node/If.php index 4296a8d6..d82edec7 100644 --- a/inc/lib/Twig/Node/If.php +++ b/inc/lib/Twig/Node/If.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -19,22 +19,22 @@ class Twig_Node_If extends Twig_Node { public function __construct(Twig_NodeInterface $tests, Twig_NodeInterface $else = null, $lineno, $tag = null) { - parent::__construct(array('tests' => $tests, 'else' => $else), array(), $lineno, $tag); + $nodes = array('tests' => $tests); + if (null !== $else) { + $nodes['else'] = $else; + } + + parent::__construct($nodes, array(), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler->addDebugInfo($this); - for ($i = 0; $i < count($this->getNode('tests')); $i += 2) { + for ($i = 0, $count = count($this->getNode('tests')); $i < $count; $i += 2) { if ($i > 0) { $compiler ->outdent() - ->write("} elseif (") + ->write('} elseif (') ; } else { $compiler @@ -50,7 +50,7 @@ class Twig_Node_If extends Twig_Node ; } - if ($this->hasNode('else') && null !== $this->getNode('else')) { + if ($this->hasNode('else')) { $compiler ->outdent() ->write("} else {\n") @@ -64,3 +64,5 @@ class Twig_Node_If extends Twig_Node ->write("}\n"); } } + +class_alias('Twig_Node_If', 'Twig\Node\IfNode', false); diff --git a/inc/lib/Twig/Node/Import.php b/inc/lib/Twig/Node/Import.php index 99efc091..c77e320b 100644 --- a/inc/lib/Twig/Node/Import.php +++ b/inc/lib/Twig/Node/Import.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -21,11 +21,6 @@ class Twig_Node_Import extends Twig_Node parent::__construct(array('expr' => $expr, 'var' => $var), array(), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler @@ -36,15 +31,21 @@ class Twig_Node_Import extends Twig_Node ; if ($this->getNode('expr') instanceof Twig_Node_Expression_Name && '_self' === $this->getNode('expr')->getAttribute('name')) { - $compiler->raw("\$this"); + $compiler->raw('$this'); } else { $compiler - ->raw('$this->env->loadTemplate(') + ->raw('$this->loadTemplate(') ->subcompile($this->getNode('expr')) - ->raw(")") + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') ; } $compiler->raw(";\n"); } } + +class_alias('Twig_Node_Import', 'Twig\Node\ImportNode', false); diff --git a/inc/lib/Twig/Node/Include.php b/inc/lib/Twig/Node/Include.php index ed4a3751..2a5114cb 100644 --- a/inc/lib/Twig/Node/Include.php +++ b/inc/lib/Twig/Node/Include.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -19,14 +19,14 @@ class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface { public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null) { - parent::__construct(array('expr' => $expr, 'variables' => $variables), array('only' => (Boolean) $only, 'ignore_missing' => (Boolean) $ignoreMissing), $lineno, $tag); + $nodes = array('expr' => $expr); + if (null !== $variables) { + $nodes['variables'] = $variables; + } + + parent::__construct($nodes, array('only' => (bool) $only, 'ignore_missing' => (bool) $ignoreMissing), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler->addDebugInfo($this); @@ -60,40 +60,31 @@ class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface protected function addGetTemplate(Twig_Compiler $compiler) { - if ($this->getNode('expr') instanceof Twig_Node_Expression_Constant) { - $compiler - ->write("\$this->env->loadTemplate(") - ->subcompile($this->getNode('expr')) - ->raw(")") - ; - } else { - $compiler - ->write("\$template = \$this->env->resolveTemplate(") - ->subcompile($this->getNode('expr')) - ->raw(");\n") - ->write('$template') - ; - } + $compiler + ->write('$this->loadTemplate(') + ->subcompile($this->getNode('expr')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') + ; } protected function addTemplateArguments(Twig_Compiler $compiler) { - if (false === $this->getAttribute('only')) { - if (null === $this->getNode('variables')) { - $compiler->raw('$context'); - } else { - $compiler - ->raw('array_merge($context, ') - ->subcompile($this->getNode('variables')) - ->raw(')') - ; - } + if (!$this->hasNode('variables')) { + $compiler->raw(false === $this->getAttribute('only') ? '$context' : 'array()'); + } elseif (false === $this->getAttribute('only')) { + $compiler + ->raw('array_merge($context, ') + ->subcompile($this->getNode('variables')) + ->raw(')') + ; } else { - if (null === $this->getNode('variables')) { - $compiler->raw('array()'); - } else { - $compiler->subcompile($this->getNode('variables')); - } + $compiler->subcompile($this->getNode('variables')); } } } + +class_alias('Twig_Node_Include', 'Twig\Node\IncludeNode', false); diff --git a/inc/lib/Twig/Node/Macro.php b/inc/lib/Twig/Node/Macro.php index 43c75e5c..3cf54977 100644 --- a/inc/lib/Twig/Node/Macro.php +++ b/inc/lib/Twig/Node/Macro.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,28 +16,31 @@ */ class Twig_Node_Macro extends Twig_Node { + const VARARGS_NAME = 'varargs'; + public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface $arguments, $lineno, $tag = null) { - parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name, 'method' => 'get'.ucfirst($name)), $lineno, $tag); + 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->getTemplateLine()); + } + } + + parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler ->addDebugInfo($this) - ->write(sprintf("public function %s(", $this->getAttribute('method'))) + ->write(sprintf('public function get%s(', $this->getAttribute('name'))) ; $count = count($this->getNode('arguments')); $pos = 0; foreach ($this->getNode('arguments') as $name => $default) { $compiler - ->raw('$_'.$name.' = ') + ->raw('$__'.$name.'__ = ') ->subcompile($default) ; @@ -46,36 +49,55 @@ class Twig_Node_Macro extends Twig_Node } } + if (PHP_VERSION_ID >= 50600) { + if ($count) { + $compiler->raw(', '); + } + + $compiler->raw('...$__varargs__'); + } + $compiler ->raw(")\n") ->write("{\n") ->indent() ; - if (!count($this->getNode('arguments'))) { - $compiler->write("\$context = \$this->env->getGlobals();\n\n"); - } else { + $compiler + ->write("\$context = \$this->env->mergeGlobals(array(\n") + ->indent() + ; + + foreach ($this->getNode('arguments') as $name => $default) { $compiler - ->write("\$context = \$this->env->mergeGlobals(array(\n") - ->indent() + ->write('') + ->string($name) + ->raw(' => $__'.$name.'__') + ->raw(",\n") ; + } - foreach ($this->getNode('arguments') as $name => $default) { - $compiler - ->write('') - ->string($name) - ->raw(' => $_'.$name) - ->raw(",\n") - ; - } + $compiler + ->write('') + ->string(self::VARARGS_NAME) + ->raw(' => ') + ; + if (PHP_VERSION_ID >= 50600) { + $compiler->raw("\$__varargs__,\n"); + } else { $compiler - ->outdent() - ->write("));\n\n") + ->raw('func_num_args() > ') + ->repr($count) + ->raw(' ? array_slice(func_get_args(), ') + ->repr($count) + ->raw(") : array(),\n") ; } $compiler + ->outdent() + ->write("));\n\n") ->write("\$blocks = array();\n\n") ->write("ob_start();\n") ->write("try {\n") @@ -87,6 +109,11 @@ class Twig_Node_Macro extends Twig_Node ->write("ob_end_clean();\n\n") ->write("throw \$e;\n") ->outdent() + ->write("} catch (Throwable \$e) {\n") + ->indent() + ->write("ob_end_clean();\n\n") + ->write("throw \$e;\n") + ->outdent() ->write("}\n\n") ->write("return ('' === \$tmp = ob_get_clean()) ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset());\n") ->outdent() @@ -94,3 +121,5 @@ class Twig_Node_Macro extends Twig_Node ; } } + +class_alias('Twig_Node_Macro', 'Twig\Node\MacroNode', false); diff --git a/inc/lib/Twig/Node/Module.php b/inc/lib/Twig/Node/Module.php index 224410a2..5cd8d050 100644 --- a/inc/lib/Twig/Node/Module.php +++ b/inc/lib/Twig/Node/Module.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,14 +13,52 @@ /** * Represents a module node. * + * Consider this class as being final. If you need to customize the behavior of + * the generated class, consider adding nodes to the following nodes: display_start, + * display_end, constructor_start, constructor_end, and class_end. + * * @author Fabien Potencier */ 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) + 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, + 'macros' => $macros, + 'traits' => $traits, + 'display_start' => new Twig_Node(), + 'display_end' => new Twig_Node(), + 'constructor_start' => new Twig_Node(), + 'constructor_end' => new Twig_Node(), + 'class_end' => new Twig_Node(), + ); + if (null !== $parent) { + $nodes['parent'] = $parent; + } + // embedded templates are set as attributes so that they are only visited once by the visitors - parent::__construct(array('parent' => $parent, 'body' => $body, 'blocks' => $blocks, 'macros' => $macros, 'traits' => $traits), array('filename' => $filename, 'index' => null, 'embedded_templates' => $embeddedTemplates), 1); + parent::__construct($nodes, array( + // 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) @@ -28,11 +66,6 @@ class Twig_Node_Module extends Twig_Node $this->setAttribute('index', $index); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $this->compileTemplate($compiler); @@ -50,17 +83,20 @@ class Twig_Node_Module extends Twig_Node $this->compileClassHeader($compiler); - if (count($this->getNode('blocks')) || count($this->getNode('traits')) || null === $this->getNode('parent') || $this->getNode('parent') instanceof Twig_Node_Expression_Constant) { + if ( + count($this->getNode('blocks')) + || count($this->getNode('traits')) + || !$this->hasNode('parent') + || $this->getNode('parent') instanceof Twig_Node_Expression_Constant + || count($this->getNode('constructor_start')) + || count($this->getNode('constructor_end')) + ) { $this->compileConstructor($compiler); } $this->compileGetParent($compiler); - $this->compileDisplayHeader($compiler); - - $this->compileDisplayBody($compiler); - - $this->compileDisplayFooter($compiler); + $this->compileDisplay($compiler); $compiler->subcompile($this->getNode('blocks')); @@ -72,28 +108,38 @@ class Twig_Node_Module extends Twig_Node $this->compileDebugInfo($compiler); + $this->compileGetSource($compiler); + + $this->compileGetSourceContext($compiler); + $this->compileClassFooter($compiler); } protected function compileGetParent(Twig_Compiler $compiler) { - if (null === $this->getNode('parent')) { + if (!$this->hasNode('parent')) { return; } + $parent = $this->getNode('parent'); $compiler ->write("protected function doGetParent(array \$context)\n", "{\n") ->indent() - ->write("return ") + ->addDebugInfo($parent) + ->write('return ') ; - if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) { - $compiler->subcompile($this->getNode('parent')); + if ($parent instanceof Twig_Node_Expression_Constant) { + $compiler->subcompile($parent); } else { $compiler - ->raw("\$this->env->resolveTemplate(") - ->subcompile($this->getNode('parent')) - ->raw(")") + ->raw('$this->loadTemplate(') + ->subcompile($parent) + ->raw(', ') + ->repr($this->source->getName()) + ->raw(', ') + ->repr($parent->getTemplateLine()) + ->raw(')') ; } @@ -104,27 +150,13 @@ class Twig_Node_Module extends Twig_Node ; } - protected function compileDisplayBody(Twig_Compiler $compiler) - { - $compiler->subcompile($this->getNode('body')); - - if (null !== $this->getNode('parent')) { - if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) { - $compiler->write("\$this->parent"); - } else { - $compiler->write("\$this->getParent(\$context)"); - } - $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n"); - } - } - 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() @@ -136,17 +168,23 @@ class Twig_Node_Module extends Twig_Node $compiler ->write("public function __construct(Twig_Environment \$env)\n", "{\n") ->indent() + ->subcompile($this->getNode('constructor_start')) ->write("parent::__construct(\$env);\n\n") ; // parent - if (null === $this->getNode('parent')) { + if (!$this->hasNode('parent')) { $compiler->write("\$this->parent = false;\n\n"); - } elseif ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) { + } elseif (($parent = $this->getNode('parent')) && $parent instanceof Twig_Node_Expression_Constant) { $compiler - ->write("\$this->parent = \$this->env->loadTemplate(") - ->subcompile($this->getNode('parent')) - ->raw(");\n\n") + ->addDebugInfo($parent) + ->write('$this->parent = $this->loadTemplate(') + ->subcompile($parent) + ->raw(', ') + ->repr($this->source->getName()) + ->raw(', ') + ->repr($parent->getTemplateLine()) + ->raw(");\n") ; } @@ -170,11 +208,23 @@ class Twig_Node_Module extends Twig_Node foreach ($trait->getNode('targets') as $key => $value) { $compiler - ->write(sprintf("\$_trait_%s_blocks[", $i)) + ->write(sprintf('if (!isset($_trait_%s_blocks[', $i)) + ->string($key) + ->raw("])) {\n") + ->indent() + ->write("throw new Twig_Error_Runtime(sprintf('Block ") + ->string($key) + ->raw(' is not defined in trait ') + ->subcompile($trait->getNode('template')) + ->raw(".'));\n") + ->outdent() + ->write("}\n\n") + + ->write(sprintf('$_trait_%s_blocks[', $i)) ->subcompile($value) - ->raw(sprintf("] = \$_trait_%s_blocks[", $i)) + ->raw(sprintf('] = $_trait_%s_blocks[', $i)) ->string($key) - ->raw(sprintf("]; unset(\$_trait_%s_blocks[", $i)) + ->raw(sprintf(']; unset($_trait_%s_blocks[', $i)) ->string($key) ->raw("]);\n\n") ; @@ -187,9 +237,9 @@ class Twig_Node_Module extends Twig_Node ->indent() ; - for ($i = 0; $i < $countTraits; $i++) { + for ($i = 0; $i < $countTraits; ++$i) { $compiler - ->write(sprintf("\$_trait_%s_blocks".($i == $countTraits - 1 ? '' : ',')."\n", $i)) + ->write(sprintf('$_trait_%s_blocks'.($i == $countTraits - 1 ? '' : ',')."\n", $i)) ; } @@ -233,57 +283,37 @@ class Twig_Node_Module extends Twig_Node ; } - $compiler - ->outdent() - ->write(");\n\n") - ; - - // macro information - $compiler - ->write("\$this->macros = array(\n") - ->indent() - ; - - foreach ($this->getNode('macros') as $name => $node) { - $compiler - ->addIndentation()->repr($name)->raw(" => array(\n") - ->indent() - ->write("'method' => ")->repr($node->getAttribute('method'))->raw(",\n") - ->write("'arguments' => array(\n") - ->indent() - ; - foreach ($node->getNode('arguments') as $argument => $value) { - $compiler->addIndentation()->repr($argument)->raw (' => ')->subcompile($value)->raw(",\n"); - } - $compiler - ->outdent() - ->write("),\n") - ->outdent() - ->write("),\n") - ; - } $compiler ->outdent() ->write(");\n") - ; - - $compiler ->outdent() + ->subcompile($this->getNode('constructor_end')) ->write("}\n\n") ; } - protected function compileDisplayHeader(Twig_Compiler $compiler) + protected function compileDisplay(Twig_Compiler $compiler) { $compiler ->write("protected function doDisplay(array \$context, array \$blocks = array())\n", "{\n") ->indent() + ->subcompile($this->getNode('display_start')) + ->subcompile($this->getNode('body')) ; - } - protected function compileDisplayFooter(Twig_Compiler $compiler) - { + if ($this->hasNode('parent')) { + $parent = $this->getNode('parent'); + $compiler->addDebugInfo($parent); + if ($parent instanceof Twig_Node_Expression_Constant) { + $compiler->write('$this->parent'); + } else { + $compiler->write('$this->getParent($context)'); + } + $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n"); + } + $compiler + ->subcompile($this->getNode('display_end')) ->outdent() ->write("}\n\n") ; @@ -292,6 +322,7 @@ class Twig_Node_Module extends Twig_Node protected function compileClassFooter(Twig_Compiler $compiler) { $compiler + ->subcompile($this->getNode('class_end')) ->outdent() ->write("}\n") ; @@ -308,7 +339,7 @@ class Twig_Node_Module extends Twig_Node ->write("public function getTemplateName()\n", "{\n") ->indent() ->write('return ') - ->repr($this->getAttribute('filename')) + ->repr($this->source->getName()) ->raw(";\n") ->outdent() ->write("}\n\n") @@ -324,7 +355,7 @@ class Twig_Node_Module extends Twig_Node // // Put another way, a template can be used as a trait if it // only contains blocks and use statements. - $traitable = null === $this->getNode('parent') && 0 === count($this->getNode('macros')); + $traitable = !$this->hasNode('parent') && 0 === count($this->getNode('macros')); if ($traitable) { if ($this->getNode('body') instanceof Twig_Node_Body) { $nodes = $this->getNode('body')->getNode(0); @@ -374,6 +405,37 @@ class Twig_Node_Module extends Twig_Node ->indent() ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true)))) ->outdent() + ->write("}\n\n") + ; + } + + 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("@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") ; } @@ -382,22 +444,18 @@ class Twig_Node_Module extends Twig_Node { if ($node instanceof Twig_Node_Expression_Constant) { $compiler - ->write(sprintf("%s = \$this->env->loadTemplate(", $var)) + ->write(sprintf('%s = $this->loadTemplate(', $var)) ->subcompile($node) + ->raw(', ') + ->repr($node->getTemplateName()) + ->raw(', ') + ->repr($node->getTemplateLine()) ->raw(");\n") ; } else { - $compiler - ->write(sprintf("%s = ", $var)) - ->subcompile($node) - ->raw(";\n") - ->write(sprintf("if (!%s", $var)) - ->raw(" instanceof Twig_Template) {\n") - ->indent() - ->write(sprintf("%s = \$this->env->loadTemplate(%s);\n", $var, $var)) - ->outdent() - ->write("}\n") - ; + throw new LogicException('Trait templates can only be constant nodes.'); } } } + +class_alias('Twig_Node_Module', 'Twig\Node\ModuleNode', false); diff --git a/inc/lib/Twig/Node/Print.php b/inc/lib/Twig/Node/Print.php index b0c41d1d..374db89b 100644 --- a/inc/lib/Twig/Node/Print.php +++ b/inc/lib/Twig/Node/Print.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -22,11 +22,6 @@ class Twig_Node_Print extends Twig_Node implements Twig_NodeOutputInterface parent::__construct(array('expr' => $expr), array(), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler @@ -37,3 +32,5 @@ class Twig_Node_Print extends Twig_Node implements Twig_NodeOutputInterface ; } } + +class_alias('Twig_Node_Print', 'Twig\Node\PrintNode', false); diff --git a/inc/lib/Twig/Node/Sandbox.php b/inc/lib/Twig/Node/Sandbox.php index 8cf3ed44..44b30ab9 100644 --- a/inc/lib/Twig/Node/Sandbox.php +++ b/inc/lib/Twig/Node/Sandbox.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -21,16 +21,11 @@ class Twig_Node_Sandbox extends Twig_Node parent::__construct(array('body' => $body), array(), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler ->addDebugInfo($this) - ->write("\$sandbox = \$this->env->getExtension('sandbox');\n") + ->write("\$sandbox = \$this->env->getExtension('Twig_Extension_Sandbox');\n") ->write("if (!\$alreadySandboxed = \$sandbox->isSandboxed()) {\n") ->indent() ->write("\$sandbox->enableSandbox();\n") @@ -45,3 +40,5 @@ class Twig_Node_Sandbox extends Twig_Node ; } } + +class_alias('Twig_Node_Sandbox', 'Twig\Node\SandboxNode', false); diff --git a/inc/lib/Twig/Node/SandboxedPrint.php b/inc/lib/Twig/Node/SandboxedPrint.php index 73dfaa96..a08f21f5 100644 --- a/inc/lib/Twig/Node/SandboxedPrint.php +++ b/inc/lib/Twig/Node/SandboxedPrint.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -21,21 +21,11 @@ */ class Twig_Node_SandboxedPrint extends Twig_Node_Print { - public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null) - { - parent::__construct($expr, $lineno, $tag); - } - - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler ->addDebugInfo($this) - ->write('echo $this->env->getExtension(\'sandbox\')->ensureToStringAllowed(') + ->write('echo $this->env->getExtension(\'Twig_Extension_Sandbox\')->ensureToStringAllowed(') ->subcompile($this->getNode('expr')) ->raw(");\n") ; @@ -46,9 +36,9 @@ class Twig_Node_SandboxedPrint extends Twig_Node_Print * * 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')); @@ -57,3 +47,5 @@ class Twig_Node_SandboxedPrint extends Twig_Node_Print return $node; } } + +class_alias('Twig_Node_SandboxedPrint', 'Twig\Node\SandboxedPrintNode', false); diff --git a/inc/lib/Twig/Node/Set.php b/inc/lib/Twig/Node/Set.php index 4c9c16ce..6c6743ee 100644 --- a/inc/lib/Twig/Node/Set.php +++ b/inc/lib/Twig/Node/Set.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -14,7 +14,7 @@ * * @author Fabien Potencier */ -class Twig_Node_Set extends Twig_Node +class Twig_Node_Set extends Twig_Node implements Twig_NodeCaptureInterface { public function __construct($capture, Twig_NodeInterface $names, Twig_NodeInterface $values, $lineno, $tag = null) { @@ -30,17 +30,12 @@ class Twig_Node_Set extends Twig_Node $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); } } } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler->addDebugInfo($this); @@ -99,3 +94,5 @@ class Twig_Node_Set extends Twig_Node $compiler->raw(";\n"); } } + +class_alias('Twig_Node_Set', 'Twig\Node\SetNode', false); diff --git a/inc/lib/Twig/Node/SetTemp.php b/inc/lib/Twig/Node/SetTemp.php index 3bdd1cb7..996fdcde 100644 --- a/inc/lib/Twig/Node/SetTemp.php +++ b/inc/lib/Twig/Node/SetTemp.php @@ -3,12 +3,15 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +/** + * @internal + */ class Twig_Node_SetTemp extends Twig_Node { public function __construct($name, $lineno) @@ -33,3 +36,5 @@ class Twig_Node_SetTemp extends Twig_Node ; } } + +class_alias('Twig_Node_SetTemp', 'Twig\Node\SetTempNode', false); diff --git a/inc/lib/Twig/Node/Spaceless.php b/inc/lib/Twig/Node/Spaceless.php index 7555fa0f..76f90cde 100644 --- a/inc/lib/Twig/Node/Spaceless.php +++ b/inc/lib/Twig/Node/Spaceless.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -23,11 +23,6 @@ class Twig_Node_Spaceless extends Twig_Node parent::__construct(array('body' => $body), array(), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler @@ -38,3 +33,5 @@ class Twig_Node_Spaceless extends Twig_Node ; } } + +class_alias('Twig_Node_Spaceless', 'Twig\Node\SpacelessNode', false); diff --git a/inc/lib/Twig/Node/Text.php b/inc/lib/Twig/Node/Text.php index 21bdcea1..f4577fee 100644 --- a/inc/lib/Twig/Node/Text.php +++ b/inc/lib/Twig/Node/Text.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -22,11 +22,6 @@ class Twig_Node_Text extends Twig_Node implements Twig_NodeOutputInterface parent::__construct(array(), array('data' => $data), $lineno); } - /** - * Compiles the node to PHP. - * - * @param Twig_Compiler A Twig_Compiler instance - */ public function compile(Twig_Compiler $compiler) { $compiler @@ -37,3 +32,5 @@ class Twig_Node_Text extends Twig_Node implements Twig_NodeOutputInterface ; } } + +class_alias('Twig_Node_Text', 'Twig\Node\TextNode', false); diff --git a/inc/lib/Twig/Node/With.php b/inc/lib/Twig/Node/With.php new file mode 100644 index 00000000..2ab0ea5d --- /dev/null +++ b/inc/lib/Twig/Node/With.php @@ -0,0 +1,64 @@ + + */ +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") + ; + } +} + +class_alias('Twig_Node_With', 'Twig\Node\WithNode', false); diff --git a/inc/lib/Twig/NodeCaptureInterface.php b/inc/lib/Twig/NodeCaptureInterface.php new file mode 100644 index 00000000..6638834b --- /dev/null +++ b/inc/lib/Twig/NodeCaptureInterface.php @@ -0,0 +1,21 @@ + + */ +interface Twig_NodeCaptureInterface +{ +} + +class_alias('Twig_NodeCaptureInterface', 'Twig\Node\NodeCaptureInterface', false); diff --git a/inc/lib/Twig/NodeInterface.php b/inc/lib/Twig/NodeInterface.php index f0ef7258..78e758bd 100644 --- a/inc/lib/Twig/NodeInterface.php +++ b/inc/lib/Twig/NodeInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,17 +13,19 @@ * Represents a node in the AST. * * @author Fabien Potencier - * @deprecated since 1.12 (to be removed in 2.0) + * + * @deprecated since 1.12 (to be removed in 3.0) */ interface Twig_NodeInterface extends Countable, IteratorAggregate { /** * Compiles the node to PHP. - * - * @param Twig_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/inc/lib/Twig/NodeOutputInterface.php b/inc/lib/Twig/NodeOutputInterface.php index 22172c09..5a8eaa9c 100644 --- a/inc/lib/Twig/NodeOutputInterface.php +++ b/inc/lib/Twig/NodeOutputInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,3 +17,5 @@ interface Twig_NodeOutputInterface { } + +class_alias('Twig_NodeOutputInterface', 'Twig\Node\NodeOutputInterface', false); diff --git a/inc/lib/Twig/NodeTraverser.php b/inc/lib/Twig/NodeTraverser.php index 28cba1ad..f00a0bf5 100644 --- a/inc/lib/Twig/NodeTraverser.php +++ b/inc/lib/Twig/NodeTraverser.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,35 +12,29 @@ /** * Twig_NodeTraverser is a node traverser. * - * It visits all nodes and their children and call the given visitor for each. + * It visits all nodes and their children and calls the given visitor for each. + * + * @final * * @author Fabien Potencier */ class Twig_NodeTraverser { protected $env; - protected $visitors; + protected $visitors = array(); /** - * Constructor. - * - * @param Twig_Environment $env A Twig_Environment instance - * @param array $visitors An array of Twig_NodeVisitorInterface instances + * @param Twig_Environment $env + * @param Twig_NodeVisitorInterface[] $visitors */ public function __construct(Twig_Environment $env, array $visitors = array()) { $this->env = $env; - $this->visitors = array(); foreach ($visitors as $visitor) { $this->addVisitor($visitor); } } - /** - * Adds a visitor. - * - * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance - */ public function addVisitor(Twig_NodeVisitorInterface $visitor) { if (!isset($this->visitors[$visitor->getPriority()])) { @@ -53,7 +47,7 @@ class Twig_NodeTraverser /** * 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) { @@ -70,14 +64,16 @@ class Twig_NodeTraverser protected function traverseForVisitor(Twig_NodeVisitorInterface $visitor, Twig_NodeInterface $node = null) { if (null === $node) { - return null; + return; } $node = $visitor->enterNode($node, $this->env); foreach ($node as $k => $n) { - if (false !== $n = $this->traverseForVisitor($visitor, $n)) { - $node->setNode($k, $n); + if (false !== $m = $this->traverseForVisitor($visitor, $n)) { + if ($m !== $n) { + $node->setNode($k, $m); + } } else { $node->removeNode($k); } @@ -86,3 +82,5 @@ class Twig_NodeTraverser return $visitor->leaveNode($node, $this->env); } } + +class_alias('Twig_NodeTraverser', 'Twig\NodeTraverser', false); diff --git a/inc/lib/Twig/NodeVisitor/Escaper.php b/inc/lib/Twig/NodeVisitor/Escaper.php index cc4b3d71..1a1ae66f 100644 --- a/inc/lib/Twig/NodeVisitor/Escaper.php +++ b/inc/lib/Twig/NodeVisitor/Escaper.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,9 +12,11 @@ /** * Twig_NodeVisitor_Escaper implements output escaping. * + * @final + * * @author Fabien Potencier */ -class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface +class Twig_NodeVisitor_Escaper extends Twig_BaseNodeVisitor { protected $statusStack = array(); protected $blocks = array(); @@ -28,21 +30,14 @@ class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface $this->safeAnalysis = new Twig_NodeVisitor_SafeAnalysis(); } - /** - * Called before child nodes are visited. - * - * @param Twig_NodeInterface $node The node to visit - * @param Twig_Environment $env The Twig environment instance - * - * @return Twig_NodeInterface The modified node - */ - public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + protected function doEnterNode(Twig_Node $node, Twig_Environment $env) { if ($node instanceof Twig_Node_Module) { - if ($env->hasExtension('escaper') && $defaultStrategy = $env->getExtension('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(); + $this->blocks = array(); } elseif ($node instanceof Twig_Node_AutoEscape) { $this->statusStack[] = $node->getAttribute('value'); } elseif ($node instanceof Twig_Node_Block) { @@ -54,19 +49,12 @@ class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface return $node; } - /** - * 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 The modified node - */ - public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) + protected function doLeaveNode(Twig_Node $node, Twig_Environment $env) { if ($node instanceof Twig_Node_Module) { $this->defaultStrategy = false; $this->safeVars = array(); + $this->blocks = array(); } elseif ($node instanceof Twig_Node_Expression_Filter) { return $this->preEscapeFilterNode($node, $env); } elseif ($node instanceof Twig_Node_Print) { @@ -98,7 +86,7 @@ class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface return new $class( $this->getEscaperFilter($type, $expression), - $node->getLine() + $node->getTemplateLine() ); } @@ -150,18 +138,17 @@ class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface 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))); return new Twig_Node_Expression_Filter($node, $name, $args, $line); } - /** - * {@inheritdoc} - */ public function getPriority() { return 0; } } + +class_alias('Twig_NodeVisitor_Escaper', 'Twig\NodeVisitor\EscaperNodeVisitor', false); diff --git a/inc/lib/Twig/NodeVisitor/Optimizer.php b/inc/lib/Twig/NodeVisitor/Optimizer.php index a254def7..c55e40ff 100644 --- a/inc/lib/Twig/NodeVisitor/Optimizer.php +++ b/inc/lib/Twig/NodeVisitor/Optimizer.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,48 +17,46 @@ * You can configure which optimizations you want to activate via the * optimizer mode. * + * @final + * * @author Fabien Potencier */ -class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface +class Twig_NodeVisitor_Optimizer extends Twig_BaseNodeVisitor { - const OPTIMIZE_ALL = -1; - const OPTIMIZE_NONE = 0; - const OPTIMIZE_FOR = 2; - const OPTIMIZE_RAW_FILTER = 4; - const OPTIMIZE_VAR_ACCESS = 8; + const OPTIMIZE_ALL = -1; + const OPTIMIZE_NONE = 0; + const OPTIMIZE_FOR = 2; + const OPTIMIZE_RAW_FILTER = 4; + const OPTIMIZE_VAR_ACCESS = 8; protected $loops = array(); + protected $loopsTargets = array(); protected $optimizers; protected $prependedNodes = array(); protected $inABody = false; /** - * Constructor. - * - * @param integer $optimizers The optimizer mode + * @param int $optimizers The optimizer mode */ public function __construct($optimizers = -1) { - if (!is_int($optimizers) || $optimizers > 2) { + if (!is_int($optimizers) || $optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_VAR_ACCESS)) { throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers)); } $this->optimizers = $optimizers; } - /** - * {@inheritdoc} - */ - public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + protected function doEnterNode(Twig_Node $node, Twig_Environment $env) { if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { $this->enterOptimizeFor($node, $env); } - if (!version_compare(phpversion(), '5.4.0RC1', '>=') && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) { + if (PHP_VERSION_ID < 50400 && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('Twig_Extension_Sandbox')) { if ($this->inABody) { if (!$node instanceof Twig_Node_Expression) { - if (get_class($node) !== 'Twig_Node') { + if ('Twig_Node' !== get_class($node)) { array_unshift($this->prependedNodes, array()); } } else { @@ -72,10 +70,7 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface return $node; } - /** - * {@inheritdoc} - */ - public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) + protected function doLeaveNode(Twig_Node $node, Twig_Environment $env) { $expression = $node instanceof Twig_Node_Expression; @@ -89,14 +84,14 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface $node = $this->optimizePrintNode($node, $env); - if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) { + if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('Twig_Extension_Sandbox')) { if ($node instanceof Twig_Node_Body) { $this->inABody = false; } elseif ($this->inABody) { - if (!$expression && get_class($node) !== 'Twig_Node' && $prependedNodes = array_shift($this->prependedNodes)) { + if (!$expression && 'Twig_Node' !== get_class($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; @@ -108,12 +103,12 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface return $node; } - protected function optimizeVariables($node, $env) + protected function optimizeVariables(Twig_NodeInterface $node, Twig_Environment $env) { if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) { $this->prependedNodes[0][] = $node->getAttribute('name'); - return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getLine()); + return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getTemplateLine()); } return $node; @@ -126,22 +121,22 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface * * * "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($node, $env) + protected function optimizePrintNode(Twig_NodeInterface $node, Twig_Environment $env) { if (!$node instanceof Twig_Node_Print) { 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; @@ -150,10 +145,9 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface /** * Removes "raw" filters. * - * @param Twig_NodeInterface $node A Node - * @param Twig_Environment $env The current Twig environment + * @return Twig_NodeInterface */ - protected function optimizeRawFilter($node, $env) + protected function optimizeRawFilter(Twig_NodeInterface $node, Twig_Environment $env) { if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) { return $node->getNode('node'); @@ -164,16 +158,15 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface /** * Optimizes "for" tag by removing the "loop" variable creation whenever possible. - * - * @param Twig_NodeInterface $node A Node - * @param Twig_Environment $env The current Twig environment */ - protected function enterOptimizeFor($node, $env) + protected function enterOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env) { if ($node instanceof Twig_Node_For) { // disable the loop variable by default $node->setAttribute('with_loop', false); array_unshift($this->loops, $node); + array_unshift($this->loopsTargets, $node->getNode('value_target')->getAttribute('name')); + array_unshift($this->loopsTargets, $node->getNode('key_target')->getAttribute('name')); } elseif (!$this->loops) { // we are outside a loop return; @@ -183,9 +176,15 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface // the loop variable is referenced for the current loop elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) { + $node->setAttribute('always_defined', true); $this->addLoopToCurrent(); } + // optimize access to loop targets + elseif ($node instanceof Twig_Node_Expression_Name && in_array($node->getAttribute('name'), $this->loopsTargets)) { + $node->setAttribute('always_defined', true); + } + // block reference elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) { $this->addLoopToCurrent(); @@ -196,6 +195,16 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface $this->addLoopToAll(); } + // include function without the with_context=false parameter + elseif ($node instanceof Twig_Node_Expression_Function + && 'include' === $node->getAttribute('name') + && (!$node->getNode('arguments')->hasNode('with_context') + || false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value') + ) + ) { + $this->addLoopToAll(); + } + // the loop variable is referenced via an attribute elseif ($node instanceof Twig_Node_Expression_GetAttr && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant @@ -213,14 +222,13 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface /** * Optimizes "for" tag by removing the "loop" variable creation whenever possible. - * - * @param Twig_NodeInterface $node A Node - * @param Twig_Environment $env The current Twig environment */ - protected function leaveOptimizeFor($node, $env) + protected function leaveOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env) { if ($node instanceof Twig_Node_For) { array_shift($this->loops); + array_shift($this->loopsTargets); + array_shift($this->loopsTargets); } } @@ -236,11 +244,10 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface } } - /** - * {@inheritdoc} - */ public function getPriority() { return 255; } } + +class_alias('Twig_NodeVisitor_Optimizer', 'Twig\NodeVisitor\OptimizerNodeVisitor', false); diff --git a/inc/lib/Twig/NodeVisitor/SafeAnalysis.php b/inc/lib/Twig/NodeVisitor/SafeAnalysis.php index b0c658cd..ca31c8fc 100644 --- a/inc/lib/Twig/NodeVisitor/SafeAnalysis.php +++ b/inc/lib/Twig/NodeVisitor/SafeAnalysis.php @@ -1,6 +1,18 @@ data[$hash])) { - foreach ($this->data[$hash] as $bucket) { - if ($bucket['key'] === $node) { - return $bucket['value']; - } + if (!isset($this->data[$hash])) { + return; + } + + foreach ($this->data[$hash] as $bucket) { + if ($bucket['key'] !== $node) { + continue; } + + if (in_array('html_attr', $bucket['value'])) { + $bucket['value'][] = 'html'; + } + + return $bucket['value']; } } @@ -40,12 +60,12 @@ class Twig_NodeVisitor_SafeAnalysis implements Twig_NodeVisitorInterface ); } - public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + protected function doEnterNode(Twig_Node $node, Twig_Environment $env) { return $node; } - public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) + protected function doLeaveNode(Twig_Node $node, Twig_Environment $env) { if ($node instanceof Twig_Node_Expression_Constant) { // constants are marked safe for all @@ -89,8 +109,6 @@ class Twig_NodeVisitor_SafeAnalysis implements Twig_NodeVisitorInterface } else { $this->setSafe($node, array()); } - } elseif ($node instanceof Twig_Node_Expression_MacroCall) { - $this->setSafe($node, array('all')); } elseif ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name) { $name = $node->getNode('node')->getAttribute('name'); // attributes on template instances are safe @@ -123,11 +141,10 @@ class Twig_NodeVisitor_SafeAnalysis implements Twig_NodeVisitorInterface return array_intersect($a, $b); } - /** - * {@inheritdoc} - */ public function getPriority() { return 0; } } + +class_alias('Twig_NodeVisitor_SafeAnalysis', 'Twig\NodeVisitor\SafeAnalysisNodeVisitor', false); diff --git a/inc/lib/Twig/NodeVisitor/Sandbox.php b/inc/lib/Twig/NodeVisitor/Sandbox.php index fb27045b..71aa4f02 100644 --- a/inc/lib/Twig/NodeVisitor/Sandbox.php +++ b/inc/lib/Twig/NodeVisitor/Sandbox.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,24 +12,18 @@ /** * Twig_NodeVisitor_Sandbox implements sandboxing. * + * @final + * * @author Fabien Potencier */ -class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface +class Twig_NodeVisitor_Sandbox extends Twig_BaseNodeVisitor { protected $inAModule = false; protected $tags; protected $filters; protected $functions; - /** - * Called before child nodes are visited. - * - * @param Twig_NodeInterface $node The node to visit - * @param Twig_Environment $env The Twig environment instance - * - * @return Twig_NodeInterface The modified node - */ - public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) + protected function doEnterNode(Twig_Node $node, Twig_Environment $env) { if ($node instanceof Twig_Node_Module) { $this->inAModule = true; @@ -40,53 +34,49 @@ class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface return $node; } elseif ($this->inAModule) { // look for tags - if ($node->getNodeTag()) { - $this->tags[] = $node->getNodeTag(); + if ($node->getNodeTag() && !isset($this->tags[$node->getNodeTag()])) { + $this->tags[$node->getNodeTag()] = $node; } // look for filters - if ($node instanceof Twig_Node_Expression_Filter) { - $this->filters[] = $node->getNode('filter')->getAttribute('value'); + if ($node instanceof Twig_Node_Expression_Filter && !isset($this->filters[$node->getNode('filter')->getAttribute('value')])) { + $this->filters[$node->getNode('filter')->getAttribute('value')] = $node; } // look for functions - if ($node instanceof Twig_Node_Expression_Function) { - $this->functions[] = $node->getAttribute('name'); + if ($node instanceof Twig_Node_Expression_Function && !isset($this->functions[$node->getAttribute('name')])) { + $this->functions[$node->getAttribute('name')] = $node; + } + + // the .. operator is equivalent to the range() function + if ($node instanceof Twig_Node_Expression_Binary_Range && !isset($this->functions['range'])) { + $this->functions['range'] = $node; } // 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()); } } return $node; } - /** - * 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 The modified node - */ - public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) + protected function doLeaveNode(Twig_Node $node, Twig_Environment $env) { if ($node instanceof Twig_Node_Module) { $this->inAModule = false; - return new Twig_Node_SandboxedModule($node, array_unique($this->filters), array_unique($this->tags), array_unique($this->functions)); + $node->setNode('display_start', new Twig_Node(array(new Twig_Node_CheckSecurity($this->filters, $this->tags, $this->functions), $node->getNode('display_start')))); } return $node; } - /** - * {@inheritdoc} - */ public function getPriority() { return 0; } } + +class_alias('Twig_NodeVisitor_Sandbox', 'Twig\NodeVisitor\SandboxNodeVisitor', false); diff --git a/inc/lib/Twig/NodeVisitorInterface.php b/inc/lib/Twig/NodeVisitorInterface.php index f33c13fc..1270a372 100644 --- a/inc/lib/Twig/NodeVisitorInterface.php +++ b/inc/lib/Twig/NodeVisitorInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -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 @@ interface Twig_NodeVisitorInterface /** * 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); @@ -41,7 +35,11 @@ interface Twig_NodeVisitorInterface * * Priority should be between -10 and 10 (0 is the default). * - * @return integer The priority level + * @return int The priority level */ public function getPriority(); } + +class_alias('Twig_NodeVisitorInterface', 'Twig\NodeVisitor\NodeVisitorInterface', false); +class_exists('Twig_Environment'); +class_exists('Twig_Node'); diff --git a/inc/lib/Twig/Parser.php b/inc/lib/Twig/Parser.php index bebdd9bb..6de879a5 100644 --- a/inc/lib/Twig/Parser.php +++ b/inc/lib/Twig/Parser.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -31,44 +31,49 @@ class Twig_Parser implements Twig_ParserInterface protected $importedSymbols; protected $traits; protected $embeddedTemplates = array(); + private $varNameSalt = 0; - /** - * 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; } public function getVarName() { - return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false)); + return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->stream->getSourceContext()->getCode().$this->varNameSalt++)); } + /** + * @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(); } - /** - * Converts a token stream to a node tree. - * - * @param Twig_TokenStream $stream A token stream instance - * - * @return Twig_Node_Module A node tree - */ public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = false) { // push all variables into the stack to keep the current state of the parser - $vars = get_object_vars($this); - unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser']); + // 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; + } + + unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames']); $this->stack[] = $vars; // tag handlers @@ -83,7 +88,7 @@ class Twig_Parser implements Twig_ParserInterface } 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; @@ -94,18 +99,17 @@ class Twig_Parser implements Twig_ParserInterface $this->blockStack = array(); $this->importedSymbols = array(array()); $this->embeddedTemplates = array(); + $this->varNameSalt = 0; try { $body = $this->subparse($test, $dropNeedle); - if (null !== $this->parent) { - if (null === $body = $this->filterBodyNodes($body)) { - $body = new Twig_Node(); - } + if (null !== $this->parent && null === $body = $this->filterBodyNodes($body)) { + $body = new Twig_Node(); } } catch (Twig_Error_Syntax $e) { - if (!$e->getTemplateFile()) { - $e->setTemplateFile($this->getFilename()); + if (!$e->getSourceContext()) { + $e->setSourceContext($this->stream->getSourceContext()); } if (!$e->getTemplateLine()) { @@ -115,7 +119,7 @@ class Twig_Parser implements Twig_ParserInterface 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()); + $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,8 +155,8 @@ class Twig_Parser implements Twig_ParserInterface $this->stream->next(); $token = $this->getCurrentToken(); - if ($token->getType() !== Twig_Token::NAME_TYPE) { - throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine(), $this->getFilename()); + if (Twig_Token::NAME_TYPE !== $token->getType()) { + throw new Twig_Error_Syntax('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext()); } if (null !== $test && call_user_func($test, $token)) { @@ -170,20 +174,17 @@ class Twig_Parser implements Twig_ParserInterface $subparser = $this->handlers->getTokenParser($token->getValue()); if (null === $subparser) { if (null !== $test) { - $error = sprintf('Unexpected tag name "%s"', $token->getValue()); + $e = new Twig_Error_Syntax(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); + if (is_array($test) && isset($test[0]) && $test[0] instanceof Twig_TokenParserInterface) { - $error .= sprintf(' (expecting closing tag for the "%s" tag defined near line %s)', $test[0]->getTag(), $lineno); + $e->appendMessage(sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno)); } - - throw new Twig_Error_Syntax($error, $token->getLine(), $this->getFilename()); - } - - $message = sprintf('Unknown tag name "%s"', $token->getValue()); - if ($alternatives = $this->env->computeAlternatives($token->getValue(), array_keys($this->env->getTags()))) { - $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives)); + } else { + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); + $e->addSuggestions($token->getValue(), array_keys($this->env->getTags())); } - throw new Twig_Error_Syntax($message, $token->getLine(), $this->getFilename()); + throw $e; } $this->stream->next(); @@ -195,7 +196,7 @@ class Twig_Parser implements Twig_ParserInterface 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.', $this->getCurrentToken()->getLine(), $this->stream->getSourceContext()); } } @@ -206,13 +207,23 @@ class Twig_Parser implements Twig_ParserInterface 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; } @@ -246,9 +257,9 @@ class Twig_Parser implements Twig_ParserInterface return $this->blocks[$name]; } - public function setBlock($name, $value) + public function setBlock($name, Twig_Node_Block $value) { - $this->blocks[$name] = new Twig_Node_Body(array($value), array(), $value->getLine()); + $this->blocks[$name] = new Twig_Node_Body(array($value), array(), $value->getTemplateLine()); } public function hasMacro($name) @@ -257,20 +268,29 @@ class Twig_Parser implements Twig_ParserInterface } 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->getTemplateLine(), $this->stream->getSourceContext()); + } + + $this->macros[$name] = $node; + } + + public function isReservedMacroName($name) { if (null === $this->reservedMacroNames) { $this->reservedMacroNames = array(); $r = new ReflectionClass($this->env->getBaseTemplateClass()); foreach ($r->getMethods() as $method) { - $this->reservedMacroNames[] = $method->getName(); - } - } + $methodName = strtolower($method->getName()); - if (in_array($name, $this->reservedMacroNames)) { - throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine(), $this->getFilename()); + if ('get' === substr($methodName, 0, 3) && isset($methodName[3])) { + $this->reservedMacroNames[] = substr($methodName, 3); + } + } } - $this->macros[$name] = $node; + return in_array(strtolower($name), $this->reservedMacroNames); } public function addTrait($trait) @@ -320,9 +340,7 @@ class Twig_Parser implements Twig_ParserInterface } /** - * Gets the expression parser. - * - * @return Twig_ExpressionParser The expression parser + * @return Twig_ExpressionParser */ public function getExpressionParser() { @@ -340,9 +358,7 @@ class Twig_Parser implements Twig_ParserInterface } /** - * Gets the token stream. - * - * @return Twig_TokenStream The token stream + * @return Twig_TokenStream */ public function getStream() { @@ -350,9 +366,7 @@ class Twig_Parser implements Twig_ParserInterface } /** - * Gets the current token. - * - * @return Twig_Token The current token + * @return Twig_Token */ public function getCurrentToken() { @@ -368,14 +382,14 @@ class Twig_Parser implements Twig_ParserInterface (!$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 have a body but a byte order mark (BOM) has been detected; 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()); } - throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->getFilename()); + throw new Twig_Error_Syntax('A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext()); } - // bypass "set" nodes as they "capture" the output - if ($node instanceof Twig_Node_Set) { + // bypass nodes that will "capture" the output + if ($node instanceof Twig_NodeCaptureInterface) { return $node; } @@ -384,7 +398,7 @@ class Twig_Parser implements Twig_ParserInterface } foreach ($node as $k => $n) { - if (null !== $n && null === $n = $this->filterBodyNodes($n)) { + if (null !== $n && null === $this->filterBodyNodes($n)) { $node->removeNode($k); } } @@ -392,3 +406,7 @@ class Twig_Parser implements Twig_ParserInterface return $node; } } + +class_alias('Twig_Parser', 'Twig\Parser', false); +class_exists('Twig_Node'); +class_exists('Twig_TokenStream'); diff --git a/inc/lib/Twig/ParserInterface.php b/inc/lib/Twig/ParserInterface.php index f0d79009..85c6e67b 100644 --- a/inc/lib/Twig/ParserInterface.php +++ b/inc/lib/Twig/ParserInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,16 +13,17 @@ * Interface implemented by parser classes. * * @author Fabien Potencier - * @deprecated since 1.12 (to be removed in 2.0) + * + * @deprecated since 1.12 (to be removed in 3.0) */ interface Twig_ParserInterface { /** * Converts a token stream to a node tree. * - * @param Twig_TokenStream $stream A token stream instance + * @return Twig_Node_Module * - * @return Twig_Node_Module A node tree + * @throws Twig_Error_Syntax When the token stream is syntactically or semantically wrong */ public function parse(Twig_TokenStream $stream); } diff --git a/inc/lib/Twig/Profiler/Dumper/Base.php b/inc/lib/Twig/Profiler/Dumper/Base.php new file mode 100644 index 00000000..913afd4f --- /dev/null +++ b/inc/lib/Twig/Profiler/Dumper/Base.php @@ -0,0 +1,62 @@ + + */ +abstract class Twig_Profiler_Dumper_Base +{ + private $root; + + public function dump(Twig_Profiler_Profile $profile) + { + return $this->dumpProfile($profile); + } + + abstract protected function formatTemplate(Twig_Profiler_Profile $profile, $prefix); + + abstract protected function formatNonTemplate(Twig_Profiler_Profile $profile, $prefix); + + abstract protected function formatTime(Twig_Profiler_Profile $profile, $percent); + + private function dumpProfile(Twig_Profiler_Profile $profile, $prefix = '', $sibling = false) + { + if ($profile->isRoot()) { + $this->root = $profile->getDuration(); + $start = $profile->getName(); + } else { + if ($profile->isTemplate()) { + $start = $this->formatTemplate($profile, $prefix); + } else { + $start = $this->formatNonTemplate($profile, $prefix); + } + $prefix .= $sibling ? '│ ' : ' '; + } + + $percent = $this->root ? $profile->getDuration() / $this->root * 100 : 0; + + if ($profile->getDuration() * 1000 < 1) { + $str = $start."\n"; + } else { + $str = sprintf("%s %s\n", $start, $this->formatTime($profile, $percent)); + } + + $nCount = count($profile->getProfiles()); + foreach ($profile as $i => $p) { + $str .= $this->dumpProfile($p, $prefix, $i + 1 !== $nCount); + } + + return $str; + } +} + +class_alias('Twig_Profiler_Dumper_Base', 'Twig\Profiler\Dumper\BaseDumper', false); +class_exists('Twig_Profiler_Profile'); diff --git a/inc/lib/Twig/Profiler/Dumper/Blackfire.php b/inc/lib/Twig/Profiler/Dumper/Blackfire.php new file mode 100644 index 00000000..7a33baf2 --- /dev/null +++ b/inc/lib/Twig/Profiler/Dumper/Blackfire.php @@ -0,0 +1,72 @@ + + * + * @final + */ +class Twig_Profiler_Dumper_Blackfire +{ + public function dump(Twig_Profiler_Profile $profile) + { + $data = array(); + $this->dumpProfile('main()', $profile, $data); + $this->dumpChildren('main()', $profile, $data); + + $start = sprintf('%f', microtime(true)); + $str = << $values) { + $str .= "{$name}//{$values['ct']} {$values['wt']} {$values['mu']} {$values['pmu']}\n"; + } + + return $str; + } + + private function dumpChildren($parent, Twig_Profiler_Profile $profile, &$data) + { + foreach ($profile as $p) { + if ($p->isTemplate()) { + $name = $p->getTemplate(); + } else { + $name = sprintf('%s::%s(%s)', $p->getTemplate(), $p->getType(), $p->getName()); + } + $this->dumpProfile(sprintf('%s==>%s', $parent, $name), $p, $data); + $this->dumpChildren($name, $p, $data); + } + } + + private function dumpProfile($edge, Twig_Profiler_Profile $profile, &$data) + { + if (isset($data[$edge])) { + $data[$edge]['ct'] += 1; + $data[$edge]['wt'] += floor($profile->getDuration() * 1000000); + $data[$edge]['mu'] += $profile->getMemoryUsage(); + $data[$edge]['pmu'] += $profile->getPeakMemoryUsage(); + } else { + $data[$edge] = array( + 'ct' => 1, + 'wt' => floor($profile->getDuration() * 1000000), + 'mu' => $profile->getMemoryUsage(), + 'pmu' => $profile->getPeakMemoryUsage(), + ); + } + } +} + +class_alias('Twig_Profiler_Dumper_Blackfire', 'Twig\Profiler\Dumper\BlackfireDumper', false); diff --git a/inc/lib/Twig/Profiler/Dumper/Html.php b/inc/lib/Twig/Profiler/Dumper/Html.php new file mode 100644 index 00000000..b57a2551 --- /dev/null +++ b/inc/lib/Twig/Profiler/Dumper/Html.php @@ -0,0 +1,47 @@ + + * + * @final + */ +class Twig_Profiler_Dumper_Html extends Twig_Profiler_Dumper_Base +{ + private static $colors = array( + 'block' => '#dfd', + 'macro' => '#ddf', + 'template' => '#ffd', + 'big' => '#d44', + ); + + public function dump(Twig_Profiler_Profile $profile) + { + return '
'.parent::dump($profile).'
'; + } + + protected function formatTemplate(Twig_Profiler_Profile $profile, $prefix) + { + return sprintf('%sâ”” %s', $prefix, self::$colors['template'], $profile->getTemplate()); + } + + protected function formatNonTemplate(Twig_Profiler_Profile $profile, $prefix) + { + return sprintf('%sâ”” %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), isset(self::$colors[$profile->getType()]) ? self::$colors[$profile->getType()] : 'auto', $profile->getName()); + } + + protected function formatTime(Twig_Profiler_Profile $profile, $percent) + { + return sprintf('%.2fms/%.0f%%', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent); + } +} + +class_alias('Twig_Profiler_Dumper_Html', 'Twig\Profiler\Dumper\HtmlDumper', false); diff --git a/inc/lib/Twig/Profiler/Dumper/Text.php b/inc/lib/Twig/Profiler/Dumper/Text.php new file mode 100644 index 00000000..69d2c4bf --- /dev/null +++ b/inc/lib/Twig/Profiler/Dumper/Text.php @@ -0,0 +1,35 @@ + + * + * @final + */ +class Twig_Profiler_Dumper_Text extends Twig_Profiler_Dumper_Base +{ + protected function formatTemplate(Twig_Profiler_Profile $profile, $prefix) + { + return sprintf('%sâ”” %s', $prefix, $profile->getTemplate()); + } + + protected function formatNonTemplate(Twig_Profiler_Profile $profile, $prefix) + { + return sprintf('%sâ”” %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), $profile->getName()); + } + + protected function formatTime(Twig_Profiler_Profile $profile, $percent) + { + return sprintf('%.2fms/%.0f%%', $profile->getDuration() * 1000, $percent); + } +} + +class_alias('Twig_Profiler_Dumper_Text', 'Twig\Profiler\Dumper\TextDumper', false); diff --git a/inc/lib/Twig/Profiler/Node/EnterProfile.php b/inc/lib/Twig/Profiler/Node/EnterProfile.php new file mode 100644 index 00000000..69c8f797 --- /dev/null +++ b/inc/lib/Twig/Profiler/Node/EnterProfile.php @@ -0,0 +1,39 @@ + + */ +class Twig_Profiler_Node_EnterProfile extends Twig_Node +{ + public function __construct($extensionName, $type, $name, $varName) + { + parent::__construct(array(), array('extension_name' => $extensionName, 'name' => $name, 'type' => $type, 'var_name' => $varName)); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->write(sprintf('$%s = $this->env->getExtension(', $this->getAttribute('var_name'))) + ->repr($this->getAttribute('extension_name')) + ->raw(");\n") + ->write(sprintf('$%s->enter($%s = new Twig_Profiler_Profile($this->getTemplateName(), ', $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) + ->repr($this->getAttribute('type')) + ->raw(', ') + ->repr($this->getAttribute('name')) + ->raw("));\n\n") + ; + } +} + +class_alias('Twig_Profiler_Node_EnterProfile', 'Twig\Profiler\Node\EnterProfileNode', false); diff --git a/inc/lib/Twig/Profiler/Node/LeaveProfile.php b/inc/lib/Twig/Profiler/Node/LeaveProfile.php new file mode 100644 index 00000000..d1d6a7cc --- /dev/null +++ b/inc/lib/Twig/Profiler/Node/LeaveProfile.php @@ -0,0 +1,33 @@ + + */ +class Twig_Profiler_Node_LeaveProfile extends Twig_Node +{ + public function __construct($varName) + { + parent::__construct(array(), array('var_name' => $varName)); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler + ->write("\n") + ->write(sprintf("\$%s->leave(\$%s);\n\n", $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) + ; + } +} + +class_alias('Twig_Profiler_Node_LeaveProfile', 'Twig\Profiler\Node\LeaveProfileNode', false); diff --git a/inc/lib/Twig/Profiler/NodeVisitor/Profiler.php b/inc/lib/Twig/Profiler/NodeVisitor/Profiler.php new file mode 100644 index 00000000..5db41fea --- /dev/null +++ b/inc/lib/Twig/Profiler/NodeVisitor/Profiler.php @@ -0,0 +1,67 @@ + + * + * @final + */ +class Twig_Profiler_NodeVisitor_Profiler extends Twig_BaseNodeVisitor +{ + private $extensionName; + + public function __construct($extensionName) + { + $this->extensionName = $extensionName; + } + + protected function doEnterNode(Twig_Node $node, Twig_Environment $env) + { + return $node; + } + + 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->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(); + $node->setNode('body', new Twig_Node_Body(array( + new Twig_Profiler_Node_EnterProfile($this->extensionName, Twig_Profiler_Profile::BLOCK, $node->getAttribute('name'), $varName), + $node->getNode('body'), + new Twig_Profiler_Node_LeaveProfile($varName), + ))); + } elseif ($node instanceof Twig_Node_Macro) { + $varName = $this->getVarName(); + $node->setNode('body', new Twig_Node_Body(array( + new Twig_Profiler_Node_EnterProfile($this->extensionName, Twig_Profiler_Profile::MACRO, $node->getAttribute('name'), $varName), + $node->getNode('body'), + new Twig_Profiler_Node_LeaveProfile($varName), + ))); + } + + return $node; + } + + private function getVarName() + { + return sprintf('__internal_%s', hash('sha256', $this->extensionName)); + } + + public function getPriority() + { + return 0; + } +} + +class_alias('Twig_Profiler_NodeVisitor_Profiler', 'Twig\Profiler\NodeVisitor\ProfilerNodeVisitor', false); diff --git a/inc/lib/Twig/Profiler/Profile.php b/inc/lib/Twig/Profiler/Profile.php new file mode 100644 index 00000000..3fdc1a8a --- /dev/null +++ b/inc/lib/Twig/Profiler/Profile.php @@ -0,0 +1,170 @@ + + * + * @final + */ +class Twig_Profiler_Profile implements IteratorAggregate, Serializable +{ + const ROOT = 'ROOT'; + const BLOCK = 'block'; + const TEMPLATE = 'template'; + const MACRO = 'macro'; + + private $template; + private $name; + private $type; + private $starts = array(); + private $ends = array(); + private $profiles = array(); + + public function __construct($template = 'main', $type = self::ROOT, $name = 'main') + { + $this->template = $template; + $this->type = $type; + $this->name = 0 === strpos($name, '__internal_') ? 'INTERNAL' : $name; + $this->enter(); + } + + public function getTemplate() + { + return $this->template; + } + + public function getType() + { + return $this->type; + } + + public function getName() + { + return $this->name; + } + + public function isRoot() + { + return self::ROOT === $this->type; + } + + public function isTemplate() + { + return self::TEMPLATE === $this->type; + } + + public function isBlock() + { + return self::BLOCK === $this->type; + } + + public function isMacro() + { + return self::MACRO === $this->type; + } + + public function getProfiles() + { + return $this->profiles; + } + + public function addProfile(Twig_Profiler_Profile $profile) + { + $this->profiles[] = $profile; + } + + /** + * Returns the duration in microseconds. + * + * @return int + */ + public function getDuration() + { + if ($this->isRoot() && $this->profiles) { + // for the root node with children, duration is the sum of all child durations + $duration = 0; + foreach ($this->profiles as $profile) { + $duration += $profile->getDuration(); + } + + return $duration; + } + + return isset($this->ends['wt']) && isset($this->starts['wt']) ? $this->ends['wt'] - $this->starts['wt'] : 0; + } + + /** + * Returns the memory usage in bytes. + * + * @return int + */ + public function getMemoryUsage() + { + return isset($this->ends['mu']) && isset($this->starts['mu']) ? $this->ends['mu'] - $this->starts['mu'] : 0; + } + + /** + * Returns the peak memory usage in bytes. + * + * @return int + */ + public function getPeakMemoryUsage() + { + return isset($this->ends['pmu']) && isset($this->starts['pmu']) ? $this->ends['pmu'] - $this->starts['pmu'] : 0; + } + + /** + * Starts the profiling. + */ + public function enter() + { + $this->starts = array( + 'wt' => microtime(true), + 'mu' => memory_get_usage(), + 'pmu' => memory_get_peak_usage(), + ); + } + + /** + * Stops the profiling. + */ + public function leave() + { + $this->ends = array( + 'wt' => microtime(true), + 'mu' => memory_get_usage(), + 'pmu' => memory_get_peak_usage(), + ); + } + + public function reset() + { + $this->starts = $this->ends = $this->profiles = array(); + $this->enter(); + } + + public function getIterator() + { + return new ArrayIterator($this->profiles); + } + + public function serialize() + { + return serialize(array($this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles)); + } + + public function unserialize($data) + { + list($this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles) = unserialize($data); + } +} + +class_alias('Twig_Profiler_Profile', 'Twig\Profiler\Profile', false); diff --git a/inc/lib/Twig/RuntimeLoaderInterface.php b/inc/lib/Twig/RuntimeLoaderInterface.php new file mode 100644 index 00000000..f5eb14e0 --- /dev/null +++ b/inc/lib/Twig/RuntimeLoaderInterface.php @@ -0,0 +1,29 @@ + + */ +interface Twig_RuntimeLoaderInterface +{ + /** + * Creates the runtime implementation of a Twig element (filter/function/test). + * + * @param string $class A runtime class + * + * @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class + */ + public function load($class); +} + +class_alias('Twig_RuntimeLoaderInterface', 'Twig\RuntimeLoader\RuntimeLoaderInterface', false); diff --git a/inc/lib/Twig/Sandbox/SecurityError.php b/inc/lib/Twig/Sandbox/SecurityError.php index 015bfaea..b6707e38 100644 --- a/inc/lib/Twig/Sandbox/SecurityError.php +++ b/inc/lib/Twig/Sandbox/SecurityError.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,3 +17,5 @@ class Twig_Sandbox_SecurityError extends Twig_Error { } + +class_alias('Twig_Sandbox_SecurityError', 'Twig\Sandbox\SecurityError', false); diff --git a/inc/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php b/inc/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php new file mode 100644 index 00000000..0ba33276 --- /dev/null +++ b/inc/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php @@ -0,0 +1,33 @@ + + */ +class Twig_Sandbox_SecurityNotAllowedFilterError extends Twig_Sandbox_SecurityError +{ + private $filterName; + + public function __construct($message, $functionName, $lineno = -1, $filename = null, Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->filterName = $functionName; + } + + public function getFilterName() + { + return $this->filterName; + } +} + +class_alias('Twig_Sandbox_SecurityNotAllowedFilterError', 'Twig\Sandbox\SecurityNotAllowedFilterError', false); diff --git a/inc/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php b/inc/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php new file mode 100644 index 00000000..aa391429 --- /dev/null +++ b/inc/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php @@ -0,0 +1,33 @@ + + */ +class Twig_Sandbox_SecurityNotAllowedFunctionError extends Twig_Sandbox_SecurityError +{ + private $functionName; + + public function __construct($message, $functionName, $lineno = -1, $filename = null, Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->functionName = $functionName; + } + + public function getFunctionName() + { + return $this->functionName; + } +} + +class_alias('Twig_Sandbox_SecurityNotAllowedFunctionError', 'Twig\Sandbox\SecurityNotAllowedFunctionError', false); diff --git a/inc/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php b/inc/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php new file mode 100644 index 00000000..93012fe9 --- /dev/null +++ b/inc/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php @@ -0,0 +1,40 @@ + + */ +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; + } +} + +class_alias('Twig_Sandbox_SecurityNotAllowedMethodError', 'Twig\Sandbox\SecurityNotAllowedMethodError', false); diff --git a/inc/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php b/inc/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php new file mode 100644 index 00000000..f27969c1 --- /dev/null +++ b/inc/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php @@ -0,0 +1,40 @@ + + */ +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; + } +} + +class_alias('Twig_Sandbox_SecurityNotAllowedPropertyError', 'Twig\Sandbox\SecurityNotAllowedPropertyError', false); diff --git a/inc/lib/Twig/Sandbox/SecurityNotAllowedTagError.php b/inc/lib/Twig/Sandbox/SecurityNotAllowedTagError.php new file mode 100644 index 00000000..4bbd2238 --- /dev/null +++ b/inc/lib/Twig/Sandbox/SecurityNotAllowedTagError.php @@ -0,0 +1,33 @@ + + */ +class Twig_Sandbox_SecurityNotAllowedTagError extends Twig_Sandbox_SecurityError +{ + private $tagName; + + public function __construct($message, $tagName, $lineno = -1, $filename = null, Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->tagName = $tagName; + } + + public function getTagName() + { + return $this->tagName; + } +} + +class_alias('Twig_Sandbox_SecurityNotAllowedTagError', 'Twig\Sandbox\SecurityNotAllowedTagError', false); diff --git a/inc/lib/Twig/Sandbox/SecurityPolicy.php b/inc/lib/Twig/Sandbox/SecurityPolicy.php index 66ee2332..dca0b82b 100644 --- a/inc/lib/Twig/Sandbox/SecurityPolicy.php +++ b/inc/lib/Twig/Sandbox/SecurityPolicy.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,6 +12,8 @@ /** * Represents a security policy which need to be enforced when sandbox mode is enabled. * + * @final + * * @author Fabien Potencier */ class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterface @@ -63,19 +65,19 @@ class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterfac { foreach ($tags as $tag) { if (!in_array($tag, $this->allowedTags)) { - throw new Twig_Sandbox_SecurityError(sprintf('Tag "%s" is not allowed.', $tag)); + throw new Twig_Sandbox_SecurityNotAllowedTagError(sprintf('Tag "%s" is not allowed.', $tag), $tag); } } foreach ($filters as $filter) { if (!in_array($filter, $this->allowedFilters)) { - throw new Twig_Sandbox_SecurityError(sprintf('Filter "%s" is not allowed.', $filter)); + throw new Twig_Sandbox_SecurityNotAllowedFilterError(sprintf('Filter "%s" is not allowed.', $filter), $filter); } } foreach ($functions as $function) { if (!in_array($function, $this->allowedFunctions)) { - throw new Twig_Sandbox_SecurityError(sprintf('Function "%s" is not allowed.', $function)); + throw new Twig_Sandbox_SecurityNotAllowedFunctionError(sprintf('Function "%s" is not allowed.', $function), $function); } } } @@ -97,7 +99,8 @@ class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterfac } 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 +116,10 @@ class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterfac } 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); } } } + +class_alias('Twig_Sandbox_SecurityPolicy', 'Twig\Sandbox\SecurityPolicy', false); diff --git a/inc/lib/Twig/Sandbox/SecurityPolicyInterface.php b/inc/lib/Twig/Sandbox/SecurityPolicyInterface.php index 6ab48e3c..88f64447 100644 --- a/inc/lib/Twig/Sandbox/SecurityPolicyInterface.php +++ b/inc/lib/Twig/Sandbox/SecurityPolicyInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -22,3 +22,5 @@ interface Twig_Sandbox_SecurityPolicyInterface public function checkPropertyAllowed($obj, $method); } + +class_alias('Twig_Sandbox_SecurityPolicyInterface', 'Twig\Sandbox\SecurityPolicyInterface', false); diff --git a/inc/lib/Twig/SimpleFilter.php b/inc/lib/Twig/SimpleFilter.php index d35c5633..ee4c0aeb 100644 --- a/inc/lib/Twig/SimpleFilter.php +++ b/inc/lib/Twig/SimpleFilter.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009-2012 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,6 +12,8 @@ /** * Represents a template filter. * + * @final + * * @author Fabien Potencier */ class Twig_SimpleFilter @@ -27,12 +29,15 @@ class Twig_SimpleFilter $this->callable = $callable; $this->options = array_merge(array( 'needs_environment' => false, - 'needs_context' => false, - 'is_safe' => null, - 'is_safe_callback' => null, - 'pre_escape' => null, - 'preserves_safety' => null, - 'node_class' => 'Twig_Node_Expression_Filter', + 'needs_context' => false, + 'is_variadic' => false, + 'is_safe' => null, + 'is_safe_callback' => null, + 'pre_escape' => null, + 'preserves_safety' => null, + 'node_class' => 'Twig_Node_Expression_Filter', + 'deprecated' => false, + 'alternative' => null, ), $options); } @@ -91,4 +96,26 @@ class Twig_SimpleFilter { return $this->options['pre_escape']; } + + public function isVariadic() + { + return $this->options['is_variadic']; + } + + public function isDeprecated() + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion() + { + return $this->options['deprecated']; + } + + public function getAlternative() + { + return $this->options['alternative']; + } } + +class_alias('Twig_SimpleFilter', 'Twig\TwigFilter', false); diff --git a/inc/lib/Twig/SimpleFunction.php b/inc/lib/Twig/SimpleFunction.php index 8ef6aca2..a6aa7ca7 100644 --- a/inc/lib/Twig/SimpleFunction.php +++ b/inc/lib/Twig/SimpleFunction.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010-2012 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,6 +12,8 @@ /** * Represents a template function. * + * @final + * * @author Fabien Potencier */ class Twig_SimpleFunction @@ -27,10 +29,13 @@ class Twig_SimpleFunction $this->callable = $callable; $this->options = array_merge(array( 'needs_environment' => false, - 'needs_context' => false, - 'is_safe' => null, - 'is_safe_callback' => null, - 'node_class' => 'Twig_Node_Expression_Function', + 'needs_context' => false, + 'is_variadic' => false, + 'is_safe' => null, + 'is_safe_callback' => null, + 'node_class' => 'Twig_Node_Expression_Function', + 'deprecated' => false, + 'alternative' => null, ), $options); } @@ -81,4 +86,26 @@ class Twig_SimpleFunction return array(); } + + public function isVariadic() + { + return $this->options['is_variadic']; + } + + public function isDeprecated() + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion() + { + return $this->options['deprecated']; + } + + public function getAlternative() + { + return $this->options['alternative']; + } } + +class_alias('Twig_SimpleFunction', 'Twig\TwigFunction', false); diff --git a/inc/lib/Twig/SimpleTest.php b/inc/lib/Twig/SimpleTest.php index 225459c9..fee5778b 100644 --- a/inc/lib/Twig/SimpleTest.php +++ b/inc/lib/Twig/SimpleTest.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010-2012 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,6 +12,8 @@ /** * Represents a template test. * + * @final + * * @author Fabien Potencier */ class Twig_SimpleTest @@ -25,7 +27,10 @@ class Twig_SimpleTest $this->name = $name; $this->callable = $callable; $this->options = array_merge(array( + 'is_variadic' => false, 'node_class' => 'Twig_Node_Expression_Test', + 'deprecated' => false, + 'alternative' => null, ), $options); } @@ -43,4 +48,26 @@ class Twig_SimpleTest { return $this->options['node_class']; } + + public function isVariadic() + { + return $this->options['is_variadic']; + } + + public function isDeprecated() + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion() + { + return $this->options['deprecated']; + } + + public function getAlternative() + { + return $this->options['alternative']; + } } + +class_alias('Twig_SimpleTest', 'Twig\TwigTest', false); diff --git a/inc/lib/Twig/Source.php b/inc/lib/Twig/Source.php new file mode 100644 index 00000000..bd8d869f --- /dev/null +++ b/inc/lib/Twig/Source.php @@ -0,0 +1,53 @@ + + */ +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; + } +} + +class_alias('Twig_Source', 'Twig\Source', false); diff --git a/inc/lib/Twig/SourceContextLoaderInterface.php b/inc/lib/Twig/SourceContextLoaderInterface.php new file mode 100644 index 00000000..a6e8c425 --- /dev/null +++ b/inc/lib/Twig/SourceContextLoaderInterface.php @@ -0,0 +1,33 @@ + + * + * @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); +} + +class_alias('Twig_SourceContextLoaderInterface', 'Twig\Loader\SourceContextLoaderInterface', false); diff --git a/inc/lib/Twig/Template.php b/inc/lib/Twig/Template.php index a42fab28..3709232a 100644 --- a/inc/lib/Twig/Template.php +++ b/inc/lib/Twig/Template.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,30 +13,38 @@ /** * 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; - protected $parents; + protected $parents = array(); protected $env; - protected $blocks; - protected $traits; - protected $macros; + protected $blocks = array(); + protected $traits = array(); - /** - * Constructor. - * - * @param Twig_Environment $env A Twig_Environment instance - */ public function __construct(Twig_Environment $env) { $this->env = $env; - $this->blocks = array(); - $this->traits = array(); - $this->macros = array(); + } + + /** + * @internal this method will be removed in 2.0 and is only used internally to provide an upgrade path from 1.x to 2.0 + */ + public function __toString() + { + return $this->getTemplateName(); } /** @@ -47,10 +55,48 @@ abstract class Twig_Template implements Twig_TemplateInterface abstract public function getTemplateName(); /** - * {@inheritdoc} + * Returns debug information about the template. + * + * @return array Debug information + * + * @internal + */ + 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) */ public function getEnvironment() { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.20 and will be removed in 2.0.', E_USER_DEPRECATED); + return $this->env; } @@ -60,7 +106,11 @@ abstract class Twig_Template implements Twig_TemplateInterface * This method is for internal use only and should never be called * directly. * + * @param array $context + * * @return Twig_TemplateInterface|false The parent template or false if there is no parent + * + * @internal */ public function getParent(array $context) { @@ -68,15 +118,25 @@ abstract class Twig_Template implements Twig_TemplateInterface return $this->parent; } - $parent = $this->doGetParent($context); - if (false === $parent) { - return false; - } elseif ($parent instanceof Twig_Template) { - $name = $parent->getTemplateName(); - $this->parents[$name] = $parent; - $parent = $name; - } elseif (!isset($this->parents[$parent])) { - $this->parents[$parent] = $this->env->loadTemplate($parent); + try { + $parent = $this->doGetParent($context); + + if (false === $parent) { + return false; + } + + if ($parent instanceof self) { + return $this->parents[$parent->getTemplateName()] = $parent; + } + + if (!isset($this->parents[$parent])) { + $this->parents[$parent] = $this->loadTemplate($parent); + } + } catch (Twig_Error_Loader $e) { + $e->setSourceContext(null); + $e->guess(); + + throw $e; } return $this->parents[$parent]; @@ -101,17 +161,19 @@ abstract class Twig_Template implements Twig_TemplateInterface * @param string $name The block name to display from the parent * @param array $context The context * @param array $blocks The current set of blocks + * + * @internal */ public function displayParentBlock($name, array $context, array $blocks = array()) { $name = (string) $name; if (isset($this->traits[$name])) { - $this->traits[$name][0]->displayBlock($name, $context, $blocks); + $this->traits[$name][0]->displayBlock($name, $context, $blocks, false); } elseif (false !== $parent = $this->getParent($context)) { - $parent->displayBlock($name, $context, $blocks); + $parent->displayBlock($name, $context, $blocks, false); } else { - throw new Twig_Error_Runtime(sprintf('The template has no parent and no traits defining the "%s" block', $name), -1, $this->getTemplateName()); + throw new Twig_Error_Runtime(sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext()); } } @@ -121,22 +183,56 @@ abstract class Twig_Template implements Twig_TemplateInterface * This method is for internal use only and should never be called * directly. * - * @param string $name The block name to display - * @param array $context The context - * @param array $blocks The current set of blocks + * @param string $name The block name to display + * @param array $context The context + * @param array $blocks The current set of blocks + * @param bool $useBlocks Whether to use the current set of blocks + * + * @internal */ - public function displayBlock($name, array $context, array $blocks = array()) + public function displayBlock($name, array $context, array $blocks = array(), $useBlocks = true) { $name = (string) $name; - if (isset($blocks[$name])) { - $b = $blocks; - unset($b[$name]); - call_user_func($blocks[$name], $context, $b); + if ($useBlocks && isset($blocks[$name])) { + $template = $blocks[$name][0]; + $block = $blocks[$name][1]; } elseif (isset($this->blocks[$name])) { - call_user_func($this->blocks[$name], $context, $blocks); + $template = $this->blocks[$name][0]; + $block = $this->blocks[$name][1]; + } else { + $template = null; + $block = null; + } + + // avoid RCEs when sandbox is enabled + if (null !== $template && !$template instanceof self) { + throw new LogicException('A block must be a method on a Twig_Template instance.'); + } + + if (null !== $template) { + try { + $template->$block($context, $blocks); + } catch (Twig_Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($template->getSourceContext()); + } + + // this is mostly useful for Twig_Error_Loader exceptions + // see Twig_Error_Loader + if (false === $e->getTemplateLine()) { + $e->setTemplateLine(-1); + $e->guess(); + } + + throw $e; + } catch (Exception $e) { + throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e); + } } elseif (false !== $parent = $this->getParent($context)) { - $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks)); + $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false); + } else { + @trigger_error(sprintf('Silent display of undefined block "%s" in template "%s" is deprecated since version 1.29 and will throw an exception in 2.0. Use the "block(\'%s\') is defined" expression to test for block existence.', $name, $this->getTemplateName(), $name), E_USER_DEPRECATED); } } @@ -151,6 +247,8 @@ abstract class Twig_Template implements Twig_TemplateInterface * @param array $blocks The current set of blocks * * @return string The rendered block + * + * @internal */ public function renderParentBlock($name, array $context, array $blocks = array()) { @@ -166,55 +264,123 @@ abstract class Twig_Template implements Twig_TemplateInterface * This method is for internal use only and should never be called * directly. * - * @param string $name The block name to render - * @param array $context The context - * @param array $blocks The current set of blocks + * @param string $name The block name to render + * @param array $context The context + * @param array $blocks The current set of blocks + * @param bool $useBlocks Whether to use the current set of blocks * * @return string The rendered block + * + * @internal */ - public function renderBlock($name, array $context, array $blocks = array()) + public function renderBlock($name, array $context, array $blocks = array(), $useBlocks = true) { ob_start(); - $this->displayBlock($name, $context, $blocks); + $this->displayBlock($name, $context, $blocks, $useBlocks); return ob_get_clean(); } /** - * 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 array $context The context + * @param array $blocks The current set of blocks * - * @param string $name The block name + * @return bool true if the block exists, false otherwise * - * @return Boolean 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. + * + * @param array $context The context + * @param array $blocks The current set of blocks * * @return array An array of block names * - * @see hasBlock + * @internal */ - public function getBlockNames() + public function getBlockNames(array $context = null, array $blocks = array()) + { + 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) { - return array_keys($this->blocks); + try { + if (is_array($template)) { + return $this->env->resolveTemplate($template); + } + + if ($template instanceof self) { + return $template; + } + + if ($template instanceof Twig_TemplateWrapper) { + return $template; + } + + return $this->env->loadTemplate($template, $index); + } catch (Twig_Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($templateName ? new Twig_Source('', $templateName) : $this->getSourceContext()); + } + + if ($e->getTemplateLine()) { + throw $e; + } + + if (!$line) { + $e->guess(); + } else { + $e->setTemplateLine($line); + } + + throw $e; + } } /** @@ -225,24 +391,18 @@ abstract class Twig_Template implements Twig_TemplateInterface * * @return array An array of blocks * - * @see hasBlock + * @internal */ public function getBlocks() { return $this->blocks; } - /** - * {@inheritdoc} - */ public function display(array $context, array $blocks = array()) { - $this->displayWithErrorHandling($this->env->mergeGlobals($context), $blocks); + $this->displayWithErrorHandling($this->env->mergeGlobals($context), array_merge($this->blocks, $blocks)); } - /** - * {@inheritdoc} - */ public function render(array $context) { $level = ob_get_level(); @@ -254,6 +414,12 @@ abstract class Twig_Template implements Twig_TemplateInterface ob_end_clean(); } + throw $e; + } catch (Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + throw $e; } @@ -265,8 +431,8 @@ abstract class Twig_Template implements Twig_TemplateInterface try { $this->doDisplay($context, $blocks); } catch (Twig_Error $e) { - if (!$e->getTemplateFile()) { - $e->setTemplateFile($this->getTemplateName()); + if (!$e->getSourceContext()) { + $e->setSourceContext($this->getSourceContext()); } // this is mostly useful for Twig_Error_Loader exceptions @@ -278,7 +444,7 @@ abstract class Twig_Template implements Twig_TemplateInterface throw $e; } catch (Exception $e) { - throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, null, $e); + throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e); } } @@ -301,22 +467,24 @@ abstract class Twig_Template implements Twig_TemplateInterface * access for versions of PHP before 5.4. This is not a way to override * the way to get a variable value. * - * @param array $context The context - * @param string $item The variable to return from the context - * @param Boolean $ignoreStrictCheck Whether to ignore the strict variable check or not + * @param array $context The context + * @param string $item The variable to return from the context + * @param bool $ignoreStrictCheck Whether to ignore the strict variable check or not * - * @return The content of the context variable + * @return mixed The content of the context variable * * @throws Twig_Error_Runtime if the variable does not exist and Twig is running in strict mode + * + * @internal */ final protected function getContext($context, $item, $ignoreStrictCheck = false) { if (!array_key_exists($item, $context)) { if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { - return null; + return; } - throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist', $item), -1, $this->getTemplateName()); + throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist.', $item), -1, $this->getSourceContext()); } return $context[$item]; @@ -325,24 +493,26 @@ abstract class Twig_Template implements Twig_TemplateInterface /** * Returns the attribute value for a given array/object. * - * @param mixed $object The object or array from where to get the item - * @param mixed $item The item to get from the array or object - * @param array $arguments An array of arguments to pass if the item is an object method - * @param string $type The type of attribute (@see Twig_Template constants) - * @param Boolean $isDefinedTest Whether this is only a defined check - * @param Boolean $ignoreStrictCheck Whether to ignore the strict attribute check or not + * @param mixed $object The object or array from where to get the item + * @param mixed $item The item to get from the array or object + * @param array $arguments An array of arguments to pass if the item is an object method + * @param string $type The type of attribute (@see Twig_Template constants) + * @param bool $isDefinedTest Whether this is only a defined check + * @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not * * @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 = Twig_Template::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) + protected function getAttribute($object, $item, array $arguments = array(), $type = self::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) { // array - if (Twig_Template::METHOD_CALL !== $type) { + 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) { @@ -352,24 +522,38 @@ abstract class Twig_Template implements Twig_TemplateInterface return $object[$arrayItem]; } - if (Twig_Template::ARRAY_CALL === $type || !is_object($object)) { + if (self::ARRAY_CALL === $type || !is_object($object)) { if ($isDefinedTest) { return false; } if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { - return null; + return; } - if (is_object($object)) { - throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $arrayItem, get_class($object)), -1, $this->getTemplateName()); + if ($object instanceof ArrayAccess) { + $message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, get_class($object)); + } elseif (is_object($object)) { + $message = sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, get_class($object)); } elseif (is_array($object)) { - throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $arrayItem, implode(', ', array_keys($object))), -1, $this->getTemplateName()); - } elseif (Twig_Template::ARRAY_CALL === $type) { - throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName()); + if (empty($object)) { + $message = sprintf('Key "%s" does not exist as the array is empty.', $arrayItem); + } else { + $message = sprintf('Key "%s" for array with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); + } + } elseif (self::ARRAY_CALL === $type) { + if (null === $object) { + $message = sprintf('Impossible to access a key ("%s") on a null variable.', $item); + } else { + $message = sprintf('Impossible to access a key ("%s") on a %s variable ("%s").', $item, gettype($object), $object); + } + } elseif (null === $object) { + $message = sprintf('Impossible to access an attribute ("%s") on a null variable.', $item); } else { - throw new Twig_Error_Runtime(sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName()); + $message = sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, gettype($object), $object); } + + throw new Twig_Error_Runtime($message, -1, $this->getSourceContext()); } } @@ -379,139 +563,146 @@ abstract class Twig_Template implements Twig_TemplateInterface } if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { - return null; + return; } - throw new Twig_Error_Runtime(sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName()); - } + if (null === $object) { + $message = sprintf('Impossible to invoke a method ("%s") on a null variable.', $item); + } elseif (is_array($object)) { + $message = sprintf('Impossible to invoke a method ("%s") on an array.', $item); + } else { + $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, gettype($object), $object); + } - $class = get_class($object); + throw new Twig_Error_Runtime($message, -1, $this->getSourceContext()); + } // object property - if (Twig_Template::METHOD_CALL !== $type) { + if (self::METHOD_CALL !== $type && !$object instanceof self) { // Twig_Template does not have public properties, and we don't want to allow access to internal ones if (isset($object->$item) || array_key_exists((string) $item, $object)) { if ($isDefinedTest) { return true; } - if ($this->env->hasExtension('sandbox')) { - $this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item); + if ($this->env->hasExtension('Twig_Extension_Sandbox')) { + $this->env->getExtension('Twig_Extension_Sandbox')->checkPropertyAllowed($object, $item); } return $object->$item; } } + $class = get_class($object); + // object method - if (!isset(self::$cache[$class]['methods'])) { - self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object))); + 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) { + // Accessing the environment from templates is forbidden to prevent untrusted changes to the environment + if ('getenvironment' !== strtolower($refMethod->name)) { + $methods[] = $refMethod->name; + } + } + } else { + $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; + } + + // skip get() and is() methods (in which case, $name is empty) + if ($name) { + if (!isset($cache[$name])) { + $cache[$name] = $method; + } + if (!isset($cache[$lcName])) { + $cache[$lcName] = $method; + } + } + } + self::$cache[$class] = $cache; } - $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; + $call = false; + 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) { return false; } if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { - return null; + return; } - throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $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->getSourceContext()); } if ($isDefinedTest) { return true; } - if ($this->env->hasExtension('sandbox')) { - $this->env->getExtension('sandbox')->checkMethodAllowed($object, $method); - } - - $ret = call_user_func_array(array($object, $method), $arguments); - - // useful when calling a template method from a template - // this is not supported but unfortunately heavily used in the Symfony profiler - if ($object instanceof Twig_TemplateInterface) { - return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset()); - } - - return $ret; - } - - /** - * Calls macro in a template. - * - * @param Twig_Template $template The template - * @param string $macro The name of macro - * @param array $arguments The arguments of macro - * @param array $namedNames An array of names of arguments as keys - * @param integer $namedCount The count of named arguments - * @param integer $positionalCount The count of positional arguments - * - * @return string The content of a macro - * - * @throws Twig_Error_Runtime if the macro is not defined - * @throws Twig_Error_Runtime if the argument is defined twice - * @throws Twig_Error_Runtime if the argument is unknown - */ - protected function callMacro(Twig_Template $template, $macro, array $arguments, array $namedNames = array(), $namedCount = 0, $positionalCount = -1) - { - if (!isset($template->macros[$macro]['reflection'])) { - if (!isset($template->macros[$macro])) { - throw new Twig_Error_Runtime(sprintf('Macro "%s" is not defined in the template "%s".', $macro, $template->getTemplateName())); - } - - $template->macros[$macro]['reflection'] = new ReflectionMethod($template, $template->macros[$macro]['method']); - } - - if ($namedCount < 1) { - return $template->macros[$macro]['reflection']->invokeArgs($template, $arguments); + if ($this->env->hasExtension('Twig_Extension_Sandbox')) { + $this->env->getExtension('Twig_Extension_Sandbox')->checkMethodAllowed($object, $method); } - $i = 0; - $args = array(); - foreach ($template->macros[$macro]['arguments'] as $name => $value) { - if (isset($namedNames[$name])) { - if ($i < $positionalCount) { - throw new Twig_Error_Runtime(sprintf('Argument "%s" is defined twice for macro "%s" defined in the template "%s".', $name, $macro, $template->getTemplateName())); - } - - $args[] = $arguments[$name]; - if (--$namedCount < 1) { - break; - } - } elseif ($i < $positionalCount) { - $args[] = $arguments[$i]; + // 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 { + if (!$arguments) { + $ret = $object->$method(); } else { - $args[] = $value; + $ret = call_user_func_array(array($object, $method), $arguments); } - - $i++; + } catch (BadMethodCallException $e) { + if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) { + return; + } + throw $e; } - if ($namedCount > 0) { - $parameters = array_keys(array_diff_key($namedNames, $template->macros[$macro]['arguments'])); + // @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.', $item, $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); - throw new Twig_Error_Runtime(sprintf('Unknown argument%s "%s" for macro "%s" defined in the template "%s".', count($parameters) > 1 ? 's' : '' , implode('", "', $parameters), $macro, $template->getTemplateName())); + return '' === $ret ? '' : new Twig_Markup($ret, $this->env->getCharset()); } - return $template->macros[$macro]['reflection']->invokeArgs($template, $args); - } - - /** - * This method is only useful when testing Twig. Do not use it. - */ - public static function clearCache() - { - self::$cache = array(); + return $ret; } } + +class_alias('Twig_Template', 'Twig\Template', false); diff --git a/inc/lib/Twig/TemplateInterface.php b/inc/lib/Twig/TemplateInterface.php index 879f503e..457ef7d7 100644 --- a/inc/lib/Twig/TemplateInterface.php +++ b/inc/lib/Twig/TemplateInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,12 +13,13 @@ * Interface implemented by all compiled templates. * * @author Fabien Potencier - * @deprecated since 1.12 (to be removed in 2.0) + * + * @deprecated since 1.12 (to be removed in 3.0) */ interface Twig_TemplateInterface { - const ANY_CALL = 'any'; - const ARRAY_CALL = 'array'; + const ANY_CALL = 'any'; + const ARRAY_CALL = 'array'; const METHOD_CALL = 'method'; /** @@ -41,7 +42,7 @@ interface Twig_TemplateInterface /** * Returns the bound environment for this template. * - * @return Twig_Environment The current environment + * @return Twig_Environment */ public function getEnvironment(); } diff --git a/inc/lib/Twig/TemplateWrapper.php b/inc/lib/Twig/TemplateWrapper.php new file mode 100644 index 00000000..497f6e98 --- /dev/null +++ b/inc/lib/Twig/TemplateWrapper.php @@ -0,0 +1,133 @@ + + */ +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(); + } +} + +class_alias('Twig_TemplateWrapper', 'Twig\TemplateWrapper', false); diff --git a/inc/lib/Twig/Test.php b/inc/lib/Twig/Test.php index 3baff885..b450ec62 100644 --- a/inc/lib/Twig/Test.php +++ b/inc/lib/Twig/Test.php @@ -3,16 +3,19 @@ /* * This file is part of Twig. * - * (c) 2012 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Test class is deprecated since version 1.12 and will be removed in 2.0. Use Twig_SimpleTest instead.', E_USER_DEPRECATED); + /** * Represents a template test. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ abstract class Twig_Test implements Twig_TestInterface, Twig_TestCallableInterface diff --git a/inc/lib/Twig/Test/Function.php b/inc/lib/Twig/Test/Function.php index 4be6b9b9..9e83c3f8 100644 --- a/inc/lib/Twig/Test/Function.php +++ b/inc/lib/Twig/Test/Function.php @@ -3,16 +3,19 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Test_Function class is deprecated since version 1.12 and will be removed in 2.0. Use Twig_SimpleTest instead.', E_USER_DEPRECATED); + /** * Represents a function template test. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ class Twig_Test_Function extends Twig_Test diff --git a/inc/lib/Twig/Test/IntegrationTestCase.php b/inc/lib/Twig/Test/IntegrationTestCase.php index 724f0941..016951aa 100644 --- a/inc/lib/Twig/Test/IntegrationTestCase.php +++ b/inc/lib/Twig/Test/IntegrationTestCase.php @@ -3,23 +3,67 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +use PHPUnit\Framework\TestCase; + /** - * Integration test helper + * Integration test helper. * * @author Fabien Potencier * @author Karma Dordrak */ -abstract class Twig_Test_IntegrationTestCase extends PHPUnit_Framework_TestCase +abstract class Twig_Test_IntegrationTestCase extends TestCase { - abstract protected function getExtensions(); + /** + * @return string + */ abstract protected function getFixturesDir(); + /** + * @return Twig_RuntimeLoaderInterface[] + */ + protected function getRuntimeLoaders() + { + return array(); + } + + /** + * @return Twig_ExtensionInterface[] + */ + protected function getExtensions() + { + return array(); + } + + /** + * @return Twig_SimpleFilter[] + */ + protected function getTwigFilters() + { + return array(); + } + + /** + * @return Twig_SimpleFunction[] + */ + protected function getTwigFunctions() + { + return array(); + } + + /** + * @return Twig_SimpleTest[] + */ + protected function getTwigTests() + { + return array(); + } + /** * @dataProvider getTests */ @@ -28,7 +72,16 @@ abstract class Twig_Test_IntegrationTestCase extends PHPUnit_Framework_TestCase $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs); } - public function getTests() + /** + * @dataProvider getLegacyTests + * @group legacy + */ + public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs) + { + $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs); + } + + public function getTests($name, $legacyTests = false) { $fixturesDir = realpath($this->getFixturesDir()); $tests = array(); @@ -38,19 +91,22 @@ abstract class Twig_Test_IntegrationTestCase extends PHPUnit_Framework_TestCase continue; } + if ($legacyTests xor false !== strpos($file->getRealpath(), '.legacy.test')) { + continue; + } + $test = file_get_contents($file->getRealpath()); - if (preg_match('/ - --TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)\s*(?:--DATA--\s*(.*))?\s*--EXCEPTION--\s*(.*)/sx', $test, $match)) { + if (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)\s*(?:--DATA--\s*(.*))?\s*--EXCEPTION--\s*(.*)/sx', $test, $match)) { $message = $match[1]; $condition = $match[2]; - $templates = $this->parseTemplates($match[3]); + $templates = self::parseTemplates($match[3]); $exception = $match[5]; $outputs = array(array(null, $match[4], null, '')); } elseif (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match)) { $message = $match[1]; $condition = $match[2]; - $templates = $this->parseTemplates($match[3]); + $templates = self::parseTemplates($match[3]); $exception = false; preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $outputs, PREG_SET_ORDER); } else { @@ -60,11 +116,25 @@ abstract class Twig_Test_IntegrationTestCase extends PHPUnit_Framework_TestCase $tests[] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $templates, $exception, $outputs); } + if ($legacyTests && empty($tests)) { + // add a dummy test to avoid a PHPUnit message + return array(array('not', '-', '', array(), '', array())); + } + return $tests; } + public function getLegacyTests() + { + return $this->getTests('testLegacyIntegration', true); + } + protected function doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs) { + if (!$outputs) { + $this->markTestSkipped('no legacy tests to run'); + } + if ($condition) { eval('$ret = '.$condition.';'); if (!$ret) { @@ -74,32 +144,53 @@ abstract class Twig_Test_IntegrationTestCase extends PHPUnit_Framework_TestCase $loader = new Twig_Loader_Array($templates); - foreach ($outputs as $match) { + foreach ($outputs as $i => $match) { $config = array_merge(array( 'cache' => false, 'strict_variables' => true, ), $match[2] ? eval($match[2].';') : array()); $twig = new Twig_Environment($loader, $config); $twig->addGlobal('global', 'global'); + foreach ($this->getRuntimeLoaders() as $runtimeLoader) { + $twig->addRuntimeLoader($runtimeLoader); + } + foreach ($this->getExtensions() as $extension) { $twig->addExtension($extension); } + foreach ($this->getTwigFilters() as $filter) { + $twig->addFilter($filter); + } + + foreach ($this->getTwigTests() as $test) { + $twig->addTest($test); + } + + foreach ($this->getTwigFunctions() as $function) { + $twig->addFunction($function); + } + + // avoid using the same PHP class name for different cases + // only for PHP 5.2+ + if (PHP_VERSION_ID >= 50300) { + $p = new ReflectionProperty($twig, 'templateClassPrefix'); + $p->setAccessible(true); + $p->setValue($twig, '__TwigTemplate_'.hash('sha256', uniqid(mt_rand(), true), false).'_'); + } + try { $template = $twig->loadTemplate('index.twig'); } catch (Exception $e) { if (false !== $exception) { - $this->assertEquals(trim($exception), trim(sprintf('%s: %s', get_class($e), $e->getMessage()))); + $message = $e->getMessage(); + $this->assertSame(trim($exception), trim(sprintf('%s: %s', get_class($e), $message))); + $last = substr($message, strlen($message) - 1); + $this->assertTrue('.' === $last || '?' === $last, $message, 'Exception message must end with a dot or a question mark.'); return; } - if ($e instanceof Twig_Error_Syntax) { - $e->setTemplateFile($file); - - throw $e; - } - throw new Twig_Error(sprintf('%s: %s', get_class($e), $e->getMessage()), -1, $file, $e); } @@ -107,34 +198,36 @@ abstract class Twig_Test_IntegrationTestCase extends PHPUnit_Framework_TestCase $output = trim($template->render(eval($match[1].';')), "\n "); } catch (Exception $e) { if (false !== $exception) { - $this->assertEquals(trim($exception), trim(sprintf('%s: %s', get_class($e), $e->getMessage()))); + $this->assertSame(trim($exception), trim(sprintf('%s: %s', get_class($e), $e->getMessage()))); return; } - if ($e instanceof Twig_Error_Syntax) { - $e->setTemplateFile($file); - } else { - $e = new Twig_Error(sprintf('%s: %s', get_class($e), $e->getMessage()), -1, $file, $e); - } + $e = new Twig_Error(sprintf('%s: %s', get_class($e), $e->getMessage()), -1, $file, $e); $output = trim(sprintf('%s: %s', get_class($e), $e->getMessage())); } if (false !== $exception) { - list($class, ) = explode(':', $exception); - $this->assertThat(NULL, new PHPUnit_Framework_Constraint_Exception($class)); + list($class) = explode(':', $exception); + $constraintClass = class_exists('PHPUnit\Framework\Constraint\Exception') ? 'PHPUnit\Framework\Constraint\Exception' : 'PHPUnit_Framework_Constraint_Exception'; + $this->assertThat(null, new $constraintClass($class)); } $expected = trim($match[3], "\n "); - if ($expected != $output) { - echo 'Compiled template that failed:'; + if ($expected !== $output) { + printf("Compiled templates that failed on case %d:\n", $i + 1); 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.')'); @@ -152,3 +245,5 @@ abstract class Twig_Test_IntegrationTestCase extends PHPUnit_Framework_TestCase return $templates; } } + +class_alias('Twig_Test_IntegrationTestCase', 'Twig\Test\IntegrationTestCase', false); diff --git a/inc/lib/Twig/Test/Method.php b/inc/lib/Twig/Test/Method.php index 17c6c041..feccd5db 100644 --- a/inc/lib/Twig/Test/Method.php +++ b/inc/lib/Twig/Test/Method.php @@ -3,16 +3,19 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Test_Method class is deprecated since version 1.12 and will be removed in 2.0. Use Twig_SimpleTest instead.', E_USER_DEPRECATED); + /** * Represents a method template test. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ class Twig_Test_Method extends Twig_Test @@ -32,6 +35,6 @@ class Twig_Test_Method extends Twig_Test public function compile() { - return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method); + return sprintf('$this->env->getExtension(\'%s\')->%s', get_class($this->extension), $this->method); } } diff --git a/inc/lib/Twig/Test/Node.php b/inc/lib/Twig/Test/Node.php index c832a57b..6098a527 100644 --- a/inc/lib/Twig/Test/Node.php +++ b/inc/lib/Twig/Test/Node.php @@ -3,16 +3,19 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ +@trigger_error('The Twig_Test_Node class is deprecated since version 1.12 and will be removed in 2.0.', E_USER_DEPRECATED); + /** * Represents a template test as a Node. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ class Twig_Test_Node extends Twig_Test diff --git a/inc/lib/Twig/Test/NodeTestCase.php b/inc/lib/Twig/Test/NodeTestCase.php index b15c85ff..47942675 100644 --- a/inc/lib/Twig/Test/NodeTestCase.php +++ b/inc/lib/Twig/Test/NodeTestCase.php @@ -8,24 +8,31 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -abstract class Twig_Test_NodeTestCase extends PHPUnit_Framework_TestCase + +use PHPUnit\Framework\TestCase; + +abstract class Twig_Test_NodeTestCase extends TestCase { abstract public function getTests(); /** * @dataProvider getTests */ - public function testCompile($node, $source, $environment = null) + public function testCompile($node, $source, $environment = null, $isPattern = false) { - $this->assertNodeCompilation($source, $node, $environment); + $this->assertNodeCompilation($source, $node, $environment, $isPattern); } - public function assertNodeCompilation($source, Twig_Node $node, Twig_Environment $environment = null) + public function assertNodeCompilation($source, Twig_Node $node, Twig_Environment $environment = null, $isPattern = false) { $compiler = $this->getCompiler($environment); $compiler->compile($node); - $this->assertEquals($source, trim($compiler->getSource())); + if ($isPattern) { + $this->assertStringMatchesFormat($source, trim($compiler->getSource())); + } else { + $this->assertEquals($source, trim($compiler->getSource())); + } } protected function getCompiler(Twig_Environment $environment = null) @@ -35,16 +42,22 @@ abstract class Twig_Test_NodeTestCase extends PHPUnit_Framework_TestCase protected function getEnvironment() { - return new Twig_Environment(); + return new Twig_Environment(new Twig_Loader_Array(array())); } - protected function getVariableGetter($name) + protected function getVariableGetter($name, $line = false) { - if (version_compare(phpversion(), '5.4.0RC1', '>=')) { - return sprintf('(isset($context["%s"]) ? $context["%s"] : null)', $name, $name); + $line = $line > 0 ? "// line {$line}\n" : ''; + + if (PHP_VERSION_ID >= 70000) { + return sprintf('%s($context["%s"] ?? null)', $line, $name, $name); } - return sprintf('$this->getContext($context, "%s")', $name); + if (PHP_VERSION_ID >= 50400) { + return sprintf('%s(isset($context["%s"]) ? $context["%s"] : null)', $line, $name, $name); + } + + return sprintf('%s$this->getContext($context, "%s")', $line, $name); } protected function getAttributeGetter() @@ -56,3 +69,7 @@ abstract class Twig_Test_NodeTestCase extends PHPUnit_Framework_TestCase return '$this->getAttribute('; } } + +class_alias('Twig_Test_NodeTestCase', 'Twig\Test\NodeTestCase', false); +class_exists('Twig_Environment'); +class_exists('Twig_Node'); diff --git a/inc/lib/Twig/TestCallableInterface.php b/inc/lib/Twig/TestCallableInterface.php index 0db43682..51ecb9a2 100644 --- a/inc/lib/Twig/TestCallableInterface.php +++ b/inc/lib/Twig/TestCallableInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2012 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,6 +13,7 @@ * Represents a callable template test. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ interface Twig_TestCallableInterface diff --git a/inc/lib/Twig/TestInterface.php b/inc/lib/Twig/TestInterface.php index 30d8a2c4..91664075 100644 --- a/inc/lib/Twig/TestInterface.php +++ b/inc/lib/Twig/TestInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,6 +13,7 @@ * Represents a template test. * * @author Fabien Potencier + * * @deprecated since 1.12 (to be removed in 2.0) */ interface Twig_TestInterface diff --git a/inc/lib/Twig/Token.php b/inc/lib/Twig/Token.php index bbca90db..89066869 100644 --- a/inc/lib/Twig/Token.php +++ b/inc/lib/Twig/Token.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -14,6 +14,8 @@ * Represents a Token. * * @author Fabien Potencier + * + * @final */ class Twig_Token { @@ -21,56 +23,49 @@ class Twig_Token protected $type; protected $lineno; - const EOF_TYPE = -1; - const TEXT_TYPE = 0; - const BLOCK_START_TYPE = 1; - const VAR_START_TYPE = 2; - const BLOCK_END_TYPE = 3; - const VAR_END_TYPE = 4; - const NAME_TYPE = 5; - const NUMBER_TYPE = 6; - const STRING_TYPE = 7; - const OPERATOR_TYPE = 8; - const PUNCTUATION_TYPE = 9; - const INTERPOLATION_START_TYPE = 10; - const INTERPOLATION_END_TYPE = 11; + const EOF_TYPE = -1; + const TEXT_TYPE = 0; + const BLOCK_START_TYPE = 1; + const VAR_START_TYPE = 2; + const BLOCK_END_TYPE = 3; + const VAR_END_TYPE = 4; + const NAME_TYPE = 5; + const NUMBER_TYPE = 6; + const STRING_TYPE = 7; + const OPERATOR_TYPE = 8; + const PUNCTUATION_TYPE = 9; + const INTERPOLATION_START_TYPE = 10; + const INTERPOLATION_END_TYPE = 11; /** - * Constructor. - * - * @param integer $type The type of the token - * @param string $value The token value - * @param integer $lineno The line position in the source + * @param int $type The type of the token + * @param string $value The token value + * @param int $lineno The line position in the source */ public function __construct($type, $value, $lineno) { - $this->type = $type; - $this->value = $value; + $this->type = $type; + $this->value = $value; $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->lineno), $this->value); + return sprintf('%s(%s)', self::typeToString($this->type, true), $this->value); } /** * 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|integer $type The type to test + * @param array|string|int $type The type to test * @param array|string|null $values The token value * - * @return Boolean + * @return bool */ public function test($type, $values = null) { @@ -87,9 +82,7 @@ class Twig_Token } /** - * Gets the line. - * - * @return integer The source line + * @return int */ public function getLine() { @@ -97,9 +90,7 @@ class Twig_Token } /** - * Gets the token type. - * - * @return integer The token type + * @return int */ public function getType() { @@ -107,9 +98,7 @@ class Twig_Token } /** - * Gets the token value. - * - * @return string The token value + * @return string */ public function getValue() { @@ -119,13 +108,12 @@ class Twig_Token /** * Returns the constant representation (internal) of a given type. * - * @param integer $type The type as an integer - * @param Boolean $short Whether to return a short representation or not - * @param integer $line The code line + * @param int $type The type as an integer + * @param bool $short Whether to return a short representation or not * * @return string The string representation */ - public static function typeToString($type, $short = false, $line = -1) + public static function typeToString($type, $short = false) { switch ($type) { case self::EOF_TYPE: @@ -175,14 +163,13 @@ class Twig_Token } /** - * Returns the english representation of a given type. + * Returns the English representation of a given type. * - * @param integer $type The type as an integer - * @param integer $line The code line + * @param int $type The type as an integer * * @return string The string representation */ - public static function typeToEnglish($type, $line = -1) + public static function typeToEnglish($type) { switch ($type) { case self::EOF_TYPE: @@ -216,3 +203,5 @@ class Twig_Token } } } + +class_alias('Twig_Token', 'Twig\Token', false); diff --git a/inc/lib/Twig/TokenParser.php b/inc/lib/Twig/TokenParser.php index decebd5e..1b4de14d 100644 --- a/inc/lib/Twig/TokenParser.php +++ b/inc/lib/Twig/TokenParser.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -22,12 +22,12 @@ abstract class Twig_TokenParser implements Twig_TokenParserInterface protected $parser; /** - * Sets the parser associated with this token parser - * - * @param $parser A Twig_Parser instance + * Sets the parser associated with this token parser. */ public function setParser(Twig_Parser $parser) { $this->parser = $parser; } } + +class_alias('Twig_TokenParser', 'Twig\TokenParser\AbstractTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/AutoEscape.php b/inc/lib/Twig/TokenParser/AutoEscape.php index 27560288..a20dedd1 100644 --- a/inc/lib/Twig/TokenParser/AutoEscape.php +++ b/inc/lib/Twig/TokenParser/AutoEscape.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -26,16 +26,11 @@ * using the js escaping strategy * {% endautoescape %} * + * + * @final */ class Twig_TokenParser_AutoEscape extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $lineno = $token->getLine(); @@ -46,7 +41,7 @@ class Twig_TokenParser_AutoEscape extends Twig_TokenParser } 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 Boolean.', $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()); } $value = $expr->getAttribute('value'); @@ -57,8 +52,10 @@ class Twig_TokenParser_AutoEscape extends Twig_TokenParser } if ($compat && $stream->test(Twig_Token::NAME_TYPE)) { + @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()); } $value = $stream->next()->getValue(); @@ -77,13 +74,10 @@ class Twig_TokenParser_AutoEscape extends Twig_TokenParser return $token->test('endautoescape'); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'autoescape'; } } + +class_alias('Twig_TokenParser_AutoEscape', 'Twig\TokenParser\AutoEscapeTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/Block.php b/inc/lib/Twig/TokenParser/Block.php index a2e017f3..f30f86b5 100644 --- a/inc/lib/Twig/TokenParser/Block.php +++ b/inc/lib/Twig/TokenParser/Block.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -19,37 +19,30 @@ * {% block title %}{% endblock %} - My Webpage * {% endblock %} * + * + * @final */ class Twig_TokenParser_Block extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $lineno = $token->getLine(); $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 '$name' has already been defined line %d", $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()); } $this->parser->setBlock($name, $block = new Twig_Node_Block($name, new Twig_Node(array()), $lineno)); $this->parser->pushLocalScope(); $this->parser->pushBlockStack($name); - if ($stream->test(Twig_Token::BLOCK_END_TYPE)) { - $stream->next(); - + if ($stream->nextIf(Twig_Token::BLOCK_END_TYPE)) { $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); - if ($stream->test(Twig_Token::NAME_TYPE)) { - $value = $stream->next()->getValue(); + if ($token = $stream->nextIf(Twig_Token::NAME_TYPE)) { + $value = $token->getValue(); if ($value != $name) { - throw new Twig_Error_Syntax(sprintf("Expected endblock for block '$name' (but %s given)", $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()); } } } else { @@ -71,13 +64,10 @@ class Twig_TokenParser_Block extends Twig_TokenParser return $token->test('endblock'); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'block'; } } + +class_alias('Twig_TokenParser_Block', 'Twig\TokenParser\BlockTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/Do.php b/inc/lib/Twig/TokenParser/Do.php index f50939dd..8ce08808 100644 --- a/inc/lib/Twig/TokenParser/Do.php +++ b/inc/lib/Twig/TokenParser/Do.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -11,16 +11,11 @@ /** * Evaluates an expression, discarding the returned value. + * + * @final */ class Twig_TokenParser_Do extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $expr = $this->parser->getExpressionParser()->parseExpression(); @@ -30,13 +25,10 @@ class Twig_TokenParser_Do extends Twig_TokenParser return new Twig_Node_Do($expr, $token->getLine(), $this->getTag()); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'do'; } } + +class_alias('Twig_TokenParser_Do', 'Twig\TokenParser\DoTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/Embed.php b/inc/lib/Twig/TokenParser/Embed.php index 69cb5f35..44644cc6 100644 --- a/inc/lib/Twig/TokenParser/Embed.php +++ b/inc/lib/Twig/TokenParser/Embed.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2012 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -11,16 +11,11 @@ /** * Embeds a template. + * + * @final */ class Twig_TokenParser_Embed extends Twig_TokenParser_Include { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $stream = $this->parser->getStream(); @@ -29,24 +24,33 @@ class Twig_TokenParser_Embed extends Twig_TokenParser_Include list($variables, $only, $ignoreMissing) = $this->parseArguments(); + $parentToken = $fakeParentToken = new Twig_Token(Twig_Token::STRING_TYPE, '__parent__', $token->getLine()); + if ($parent instanceof Twig_Node_Expression_Constant) { + $parentToken = new Twig_Token(Twig_Token::STRING_TYPE, $parent->getAttribute('value'), $token->getLine()); + } elseif ($parent instanceof Twig_Node_Expression_Name) { + $parentToken = new Twig_Token(Twig_Token::NAME_TYPE, $parent->getAttribute('name'), $token->getLine()); + } + // inject a fake parent to make the parent() function work $stream->injectTokens(array( new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', $token->getLine()), new Twig_Token(Twig_Token::NAME_TYPE, 'extends', $token->getLine()), - new Twig_Token(Twig_Token::STRING_TYPE, '__parent__', $token->getLine()), + $parentToken, new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', $token->getLine()), )); $module = $this->parser->parse($stream, array($this, 'decideBlockEnd'), true); // override the parent with the correct one - $module->setNode('parent', $parent); + if ($fakeParentToken === $parentToken) { + $module->setNode('parent', $parent); + } $this->parser->embedTemplate($module); $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) @@ -54,13 +58,10 @@ class Twig_TokenParser_Embed extends Twig_TokenParser_Include return $token->test('endembed'); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'embed'; } } + +class_alias('Twig_TokenParser_Embed', 'Twig\TokenParser\EmbedTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/Extends.php b/inc/lib/Twig/TokenParser/Extends.php index f5ecee21..31168cce 100644 --- a/inc/lib/Twig/TokenParser/Extends.php +++ b/inc/lib/Twig/TokenParser/Extends.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,37 +16,31 @@ *
  *  {% extends "base.html" %}
  * 
+ * + * @final */ class Twig_TokenParser_Extends extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { + $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()); } 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()); } $this->parser->setParent($this->parser->getExpressionParser()->parseExpression()); - $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + $stream->expect(Twig_Token::BLOCK_END_TYPE); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'extends'; } } + +class_alias('Twig_TokenParser_Extends', 'Twig\TokenParser\ExtendsTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/Filter.php b/inc/lib/Twig/TokenParser/Filter.php index 2b97475a..76017829 100644 --- a/inc/lib/Twig/TokenParser/Filter.php +++ b/inc/lib/Twig/TokenParser/Filter.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,20 +17,15 @@ * This text becomes uppercase * {% endfilter %} * + * + * @final */ class Twig_TokenParser_Filter extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $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); @@ -49,13 +44,10 @@ class Twig_TokenParser_Filter extends Twig_TokenParser return $token->test('endfilter'); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'filter'; } } + +class_alias('Twig_TokenParser_Filter', 'Twig\TokenParser\FilterTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/Flush.php b/inc/lib/Twig/TokenParser/Flush.php index 4e15e785..51832c77 100644 --- a/inc/lib/Twig/TokenParser/Flush.php +++ b/inc/lib/Twig/TokenParser/Flush.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,16 +13,11 @@ * Flushes the output to the client. * * @see flush() + * + * @final */ class Twig_TokenParser_Flush extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); @@ -30,13 +25,10 @@ class Twig_TokenParser_Flush extends Twig_TokenParser return new Twig_Node_Flush($token->getLine(), $this->getTag()); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'flush'; } } + +class_alias('Twig_TokenParser_Flush', 'Twig\TokenParser\FlushTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/For.php b/inc/lib/Twig/TokenParser/For.php index 98a6d079..8e737c5f 100644 --- a/inc/lib/Twig/TokenParser/For.php +++ b/inc/lib/Twig/TokenParser/For.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -20,16 +20,11 @@ * {% endfor %} * * + * + * @final */ class Twig_TokenParser_For extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $lineno = $token->getLine(); @@ -39,14 +34,13 @@ class Twig_TokenParser_For extends Twig_TokenParser $seq = $this->parser->getExpressionParser()->parseExpression(); $ifexpr = null; - if ($stream->test(Twig_Token::NAME_TYPE, 'if')) { - $stream->next(); + if ($stream->nextIf(Twig_Token::NAME_TYPE, 'if')) { $ifexpr = $this->parser->getExpressionParser()->parseExpression(); } $stream->expect(Twig_Token::BLOCK_END_TYPE); $body = $this->parser->subparse(array($this, 'decideForFork')); - if ($stream->next()->getValue() == 'else') { + if ('else' == $stream->next()->getValue()) { $stream->expect(Twig_Token::BLOCK_END_TYPE); $else = $this->parser->subparse(array($this, 'decideForEnd'), true); } else { @@ -56,13 +50,13 @@ class Twig_TokenParser_For extends Twig_TokenParser 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) { @@ -87,7 +81,7 @@ class Twig_TokenParser_For extends Twig_TokenParser 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()); } foreach ($node as $n) { @@ -106,7 +100,7 @@ class Twig_TokenParser_For extends Twig_TokenParser 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()); } } @@ -124,13 +118,10 @@ class Twig_TokenParser_For extends Twig_TokenParser } } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'for'; } } + +class_alias('Twig_TokenParser_For', 'Twig\TokenParser\ForTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/From.php b/inc/lib/Twig/TokenParser/From.php index ff6e5756..f3053da4 100644 --- a/inc/lib/Twig/TokenParser/From.php +++ b/inc/lib/Twig/TokenParser/From.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,16 +15,11 @@ *
  *   {% from 'forms.html' import forms %}
  * 
+ * + * @final */ class Twig_TokenParser_From extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $macro = $this->parser->getExpressionParser()->parseExpression(); @@ -36,19 +31,15 @@ class Twig_TokenParser_From extends Twig_TokenParser $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); $alias = $name; - if ($stream->test('as')) { - $stream->next(); - + if ($stream->nextIf('as')) { $alias = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); } $targets[$name] = $alias; - if (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ',')) { + if (!$stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) { break; } - - $stream->next(); } while (true); $stream->expect(Twig_Token::BLOCK_END_TYPE); @@ -56,19 +47,20 @@ class Twig_TokenParser_From extends Twig_TokenParser $node = new Twig_Node_Import($macro, new Twig_Node_Expression_AssignName($this->parser->getVarName(), $token->getLine()), $token->getLine(), $this->getTag()); foreach ($targets as $name => $alias) { - $this->parser->addImportedSymbol('macro', $alias, $name, $node->getNode('var')); + 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->getSourceContext()); + } + + $this->parser->addImportedSymbol('function', $alias, 'get'.$name, $node->getNode('var')); } return $node; } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'from'; } } + +class_alias('Twig_TokenParser_From', 'Twig\TokenParser\FromTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/If.php b/inc/lib/Twig/TokenParser/If.php index 3d7d1f51..f081df3a 100644 --- a/inc/lib/Twig/TokenParser/If.php +++ b/inc/lib/Twig/TokenParser/If.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -22,16 +22,11 @@ * * {% endif %} * + * + * @final */ class Twig_TokenParser_If extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $lineno = $token->getLine(); @@ -63,7 +58,7 @@ class Twig_TokenParser_If extends Twig_TokenParser 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()); } } @@ -82,13 +77,10 @@ class Twig_TokenParser_If extends Twig_TokenParser return $token->test(array('endif')); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'if'; } } + +class_alias('Twig_TokenParser_If', 'Twig\TokenParser\IfTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/Import.php b/inc/lib/Twig/TokenParser/Import.php index e7050c70..47802f50 100644 --- a/inc/lib/Twig/TokenParser/Import.php +++ b/inc/lib/Twig/TokenParser/Import.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -15,16 +15,11 @@ *
  *   {% import 'forms.html' as forms %}
  * 
+ * + * @final */ class Twig_TokenParser_Import extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $macro = $this->parser->getExpressionParser()->parseExpression(); @@ -37,13 +32,10 @@ class Twig_TokenParser_Import extends Twig_TokenParser return new Twig_Node_Import($macro, $var, $token->getLine(), $this->getTag()); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'import'; } } + +class_alias('Twig_TokenParser_Import', 'Twig\TokenParser\ImportTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/Include.php b/inc/lib/Twig/TokenParser/Include.php index 4a317868..309f11db 100644 --- a/inc/lib/Twig/TokenParser/Include.php +++ b/inc/lib/Twig/TokenParser/Include.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -21,13 +21,6 @@ */ class Twig_TokenParser_Include extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $expr = $this->parser->getExpressionParser()->parseExpression(); @@ -42,24 +35,19 @@ class Twig_TokenParser_Include extends Twig_TokenParser $stream = $this->parser->getStream(); $ignoreMissing = false; - if ($stream->test(Twig_Token::NAME_TYPE, 'ignore')) { - $stream->next(); + if ($stream->nextIf(Twig_Token::NAME_TYPE, 'ignore')) { $stream->expect(Twig_Token::NAME_TYPE, 'missing'); $ignoreMissing = true; } $variables = null; - if ($stream->test(Twig_Token::NAME_TYPE, 'with')) { - $stream->next(); - + if ($stream->nextIf(Twig_Token::NAME_TYPE, 'with')) { $variables = $this->parser->getExpressionParser()->parseExpression(); } $only = false; - if ($stream->test(Twig_Token::NAME_TYPE, 'only')) { - $stream->next(); - + if ($stream->nextIf(Twig_Token::NAME_TYPE, 'only')) { $only = true; } @@ -68,13 +56,10 @@ class Twig_TokenParser_Include extends Twig_TokenParser return array($variables, $only, $ignoreMissing); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'include'; } } + +class_alias('Twig_TokenParser_Include', 'Twig\TokenParser\IncludeTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/Macro.php b/inc/lib/Twig/TokenParser/Macro.php index 82b4fa6d..4287934b 100644 --- a/inc/lib/Twig/TokenParser/Macro.php +++ b/inc/lib/Twig/TokenParser/Macro.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,16 +17,11 @@ * * {% endmacro %} * + * + * @final */ class Twig_TokenParser_Macro extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $lineno = $token->getLine(); @@ -38,11 +33,11 @@ class Twig_TokenParser_Macro extends Twig_TokenParser $stream->expect(Twig_Token::BLOCK_END_TYPE); $this->parser->pushLocalScope(); $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); - if ($stream->test(Twig_Token::NAME_TYPE)) { - $value = $stream->next()->getValue(); + if ($token = $stream->nextIf(Twig_Token::NAME_TYPE)) { + $value = $token->getValue(); if ($value != $name) { - throw new Twig_Error_Syntax(sprintf("Expected endmacro for macro '$name' (but %s given)", $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()); } } $this->parser->popLocalScope(); @@ -56,13 +51,10 @@ class Twig_TokenParser_Macro extends Twig_TokenParser return $token->test('endmacro'); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'macro'; } } + +class_alias('Twig_TokenParser_Macro', 'Twig\TokenParser\MacroTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/Sandbox.php b/inc/lib/Twig/TokenParser/Sandbox.php index 9457325a..7fc70d9a 100644 --- a/inc/lib/Twig/TokenParser/Sandbox.php +++ b/inc/lib/Twig/TokenParser/Sandbox.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -18,22 +18,18 @@ * {% endsandbox %} * * - * @see http://www.twig-project.org/doc/api.html#sandbox-extension for details + * @see https://twig.symfony.com/doc/api.html#sandbox-extension for details + * + * @final */ class Twig_TokenParser_Sandbox extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { - $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) { @@ -43,7 +39,7 @@ class Twig_TokenParser_Sandbox extends Twig_TokenParser } 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()); } } } @@ -56,13 +52,10 @@ class Twig_TokenParser_Sandbox extends Twig_TokenParser return $token->test('endsandbox'); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'sandbox'; } } + +class_alias('Twig_TokenParser_Sandbox', 'Twig\TokenParser\SandboxTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/Set.php b/inc/lib/Twig/TokenParser/Set.php index 70e0b41b..48c6b3ae 100644 --- a/inc/lib/Twig/TokenParser/Set.php +++ b/inc/lib/Twig/TokenParser/Set.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -25,16 +25,11 @@ * * {% set foo %}Some content{% endset %} * + * + * @final */ class Twig_TokenParser_Set extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $lineno = $token->getLine(); @@ -42,20 +37,19 @@ class Twig_TokenParser_Set extends Twig_TokenParser $names = $this->parser->getExpressionParser()->parseAssignmentExpression(); $capture = false; - if ($stream->test(Twig_Token::OPERATOR_TYPE, '=')) { - $stream->next(); + if ($stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) { $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); $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()); } } 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()); } $stream->expect(Twig_Token::BLOCK_END_TYPE); @@ -72,13 +66,10 @@ class Twig_TokenParser_Set extends Twig_TokenParser return $token->test('endset'); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'set'; } } + +class_alias('Twig_TokenParser_Set', 'Twig\TokenParser\SetTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/Spaceless.php b/inc/lib/Twig/TokenParser/Spaceless.php index 1e3fa8f3..cecf27c6 100644 --- a/inc/lib/Twig/TokenParser/Spaceless.php +++ b/inc/lib/Twig/TokenParser/Spaceless.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -21,16 +21,11 @@ * * {# output will be
foo
#} * + * + * @final */ class Twig_TokenParser_Spaceless extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $lineno = $token->getLine(); @@ -47,13 +42,10 @@ class Twig_TokenParser_Spaceless extends Twig_TokenParser return $token->test('endspaceless'); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'spaceless'; } } + +class_alias('Twig_TokenParser_Spaceless', 'Twig\TokenParser\SpacelessTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/Use.php b/inc/lib/Twig/TokenParser/Use.php index bc0e09ef..1ab24e2c 100644 --- a/inc/lib/Twig/TokenParser/Use.php +++ b/inc/lib/Twig/TokenParser/Use.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2011 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -21,62 +21,50 @@ * {% block content %}{% endblock %} * * - * @see http://www.twig-project.org/doc/templates.html#horizontal-reuse for details. + * @see https://twig.symfony.com/doc/templates.html#horizontal-reuse for details. + * + * @final */ class Twig_TokenParser_Use extends Twig_TokenParser { - /** - * Parses a token and returns a node. - * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance - */ public function parse(Twig_Token $token) { $template = $this->parser->getExpressionParser()->parseExpression(); $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()); } $targets = array(); - if ($stream->test('with')) { - $stream->next(); - + if ($stream->nextIf('with')) { do { $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); $alias = $name; - if ($stream->test('as')) { - $stream->next(); - + if ($stream->nextIf('as')) { $alias = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); } $targets[$name] = new Twig_Node_Expression_Constant($alias, -1); - if (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ',')) { + if (!$stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) { break; } - - $stream->next(); } while (true); } $stream->expect(Twig_Token::BLOCK_END_TYPE); $this->parser->addTrait(new Twig_Node(array('template' => $template, 'targets' => new Twig_Node($targets)))); + + return new Twig_Node(); } - /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name - */ public function getTag() { return 'use'; } } + +class_alias('Twig_TokenParser_Use', 'Twig\TokenParser\UseTokenParser', false); diff --git a/inc/lib/Twig/TokenParser/With.php b/inc/lib/Twig/TokenParser/With.php new file mode 100644 index 00000000..7a692597 --- /dev/null +++ b/inc/lib/Twig/TokenParser/With.php @@ -0,0 +1,52 @@ + + * + * @final + */ +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'; + } +} + +class_alias('Twig_TokenParser_With', 'Twig\TokenParser\WithTokenParser', false); diff --git a/inc/lib/Twig/TokenParserBroker.php b/inc/lib/Twig/TokenParserBroker.php index ec3fba67..0d7d6e52 100644 --- a/inc/lib/Twig/TokenParserBroker.php +++ b/inc/lib/Twig/TokenParserBroker.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier - * (c) 2010 Arnaud Le Blanc + * (c) Fabien Potencier + * (c) Arnaud Le Blanc * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -14,6 +14,7 @@ * Default implementation of a token parser broker. * * @author Arnaud Le Blanc + * * @deprecated since 1.12 (to be removed in 2.0) */ class Twig_TokenParserBroker implements Twig_TokenParserBrokerInterface @@ -23,42 +24,35 @@ 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 array|Traversable $parsers A Traversable of Twig_TokenParserInterface instances + * @param array|Traversable $brokers A Traversable of Twig_TokenParserBrokerInterface instances + * @param bool $triggerDeprecationError */ - public function __construct($parsers = array(), $brokers = array()) + public function __construct($parsers = array(), $brokers = array(), $triggerDeprecationError = true) { + if ($triggerDeprecationError) { + @trigger_error('The '.__CLASS__.' class is deprecated since version 1.12 and will be removed in 2.0.', E_USER_DEPRECATED); + } + foreach ($parsers as $parser) { if (!$parser instanceof Twig_TokenParserInterface) { - throw new LogicException('$parsers must a an array of Twig_TokenParserInterface'); + throw new LogicException('$parsers must a an array of Twig_TokenParserInterface.'); } $this->parsers[$parser->getTag()] = $parser; } foreach ($brokers as $broker) { if (!$broker instanceof Twig_TokenParserBrokerInterface) { - throw new LogicException('$brokers must a an array of Twig_TokenParserBrokerInterface'); + throw new LogicException('$brokers must a an array of Twig_TokenParserBrokerInterface.'); } $this->brokers[] = $broker; } } - /** - * 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(); @@ -67,22 +61,12 @@ class Twig_TokenParserBroker implements Twig_TokenParserBrokerInterface } } - /** - * Adds a TokenParserBroker. - * - * @param Twig_TokenParserBroker $broker A Twig_TokenParserBroker instance - */ - public function addTokenParserBroker(Twig_TokenParserBroker $broker) + public function addTokenParserBroker(self $broker) { $this->brokers[] = $broker; } - /** - * Removes a TokenParserBroker. - * - * @param Twig_TokenParserBroker $broker A Twig_TokenParserBroker instance - */ - public function removeTokenParserBroker(Twig_TokenParserBroker $broker) + public function removeTokenParserBroker(self $broker) { if (false !== $pos = array_search($broker, $this->brokers)) { unset($this->brokers[$pos]); diff --git a/inc/lib/Twig/TokenParserBrokerInterface.php b/inc/lib/Twig/TokenParserBrokerInterface.php index 3f006e33..6c93f5ea 100644 --- a/inc/lib/Twig/TokenParserBrokerInterface.php +++ b/inc/lib/Twig/TokenParserBrokerInterface.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier - * (c) 2010 Arnaud Le Blanc + * (c) Fabien Potencier + * (c) Arnaud Le Blanc * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -16,6 +16,7 @@ * Token parser brokers allows to implement custom logic in the process of resolving a token parser for a given tag name. * * @author Arnaud Le Blanc + * * @deprecated since 1.12 (to be removed in 2.0) */ interface Twig_TokenParserBrokerInterface @@ -25,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/inc/lib/Twig/TokenParserInterface.php b/inc/lib/Twig/TokenParserInterface.php index bbde7714..14acc808 100644 --- a/inc/lib/Twig/TokenParserInterface.php +++ b/inc/lib/Twig/TokenParserInterface.php @@ -3,7 +3,7 @@ /* * This file is part of Twig. * - * (c) 2010 Fabien Potencier + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -17,18 +17,16 @@ interface Twig_TokenParserInterface { /** - * Sets the parser associated with this token parser - * - * @param $parser A Twig_Parser instance + * Sets the parser associated with this token parser. */ public function setParser(Twig_Parser $parser); /** * Parses a token and returns a node. * - * @param Twig_Token $token A Twig_Token instance + * @return Twig_NodeInterface * - * @return Twig_NodeInterface A Twig_NodeInterface instance + * @throws Twig_Error_Syntax */ public function parse(Twig_Token $token); @@ -39,3 +37,7 @@ interface Twig_TokenParserInterface */ public function getTag(); } + +class_alias('Twig_TokenParserInterface', 'Twig\TokenParser\TokenParserInterface', false); +class_exists('Twig_Parser'); +class_exists('Twig_Token'); diff --git a/inc/lib/Twig/TokenStream.php b/inc/lib/Twig/TokenStream.php index a78189f6..81c043ca 100644 --- a/inc/lib/Twig/TokenStream.php +++ b/inc/lib/Twig/TokenStream.php @@ -3,8 +3,8 @@ /* * This file is part of Twig. * - * (c) 2009 Fabien Potencier - * (c) 2009 Armin Ronacher + * (c) Fabien Potencier + * (c) Armin Ronacher * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,32 +13,40 @@ /** * Represents a token stream. * + * @final + * * @author Fabien Potencier */ class Twig_TokenStream { protected $tokens; - protected $current; + protected $current = 0; protected $filename; + private $source; + /** - * Constructor. - * - * @param array $tokens An array of tokens - * @param string $filename The name of the filename which tokens are associated with + * @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) + public function __construct(array $tokens, $name = null, $source = null) { - $this->tokens = $tokens; - $this->current = 0; - $this->filename = $filename; + 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; + + // 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); @@ -57,12 +65,24 @@ class Twig_TokenStream 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); } return $this->tokens[$this->current - 1]; } + /** + * Tests a token, sets the pointer to the next one and returns it or throws a syntax error. + * + * @return Twig_Token|null The next token if the condition is true, null otherwise + */ + public function nextIf($primary, $secondary = null) + { + if ($this->tokens[$this->current]->test($primary, $secondary)) { + return $this->next(); + } + } + /** * Tests a token and returns it or throws a syntax error. * @@ -73,12 +93,12 @@ class Twig_TokenStream $token = $this->tokens[$this->current]; if (!$token->test($type, $value)) { $line = $token->getLine(); - throw new Twig_Error_Syntax(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s)', + throw new Twig_Error_Syntax(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s).', $message ? $message.'. ' : '', - Twig_Token::typeToEnglish($token->getType(), $line), $token->getValue(), - Twig_Token::typeToEnglish($type, $line), $value ? sprintf(' with value "%s"', $value) : ''), + Twig_Token::typeToEnglish($token->getType()), $token->getValue(), + Twig_Token::typeToEnglish($type), $value ? sprintf(' with value "%s"', $value) : ''), $line, - $this->filename + $this->source ); } $this->next(); @@ -89,21 +109,21 @@ class Twig_TokenStream /** * Looks at the next token. * - * @param integer $number + * @param int $number * * @return Twig_Token */ 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); } return $this->tokens[$this->current + $number]; } /** - * Tests the current token + * Tests the current token. * * @return bool */ @@ -113,18 +133,16 @@ class Twig_TokenStream } /** - * Checks if end of stream was reached + * Checks if end of stream was reached. * * @return bool */ public function isEOF() { - return $this->tokens[$this->current]->getType() === Twig_Token::EOF_TYPE; + return Twig_Token::EOF_TYPE === $this->tokens[$this->current]->getType(); } /** - * Gets the current token - * * @return Twig_Token */ public function getCurrent() @@ -133,12 +151,46 @@ class Twig_TokenStream } /** - * Gets the filename associated with this stream + * Gets the name associated with this stream (null if not defined). * - * @return string + * @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; } } + +class_alias('Twig_TokenStream', 'Twig\TokenStream', false); diff --git a/inc/lib/Twig/Util/DeprecationCollector.php b/inc/lib/Twig/Util/DeprecationCollector.php new file mode 100644 index 00000000..c7bf53be --- /dev/null +++ b/inc/lib/Twig/Util/DeprecationCollector.php @@ -0,0 +1,86 @@ + + * + * @final + */ +class Twig_Util_DeprecationCollector +{ + private $twig; + private $deprecations; + + public function __construct(Twig_Environment $twig) + { + $this->twig = $twig; + } + + /** + * Returns deprecations for templates contained in a directory. + * + * @param string $dir A directory where templates are stored + * @param string $ext Limit the loaded templates by extension + * + * @return array An array of deprecations + */ + public function collectDir($dir, $ext = '.twig') + { + $iterator = new RegexIterator( + new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($dir), RecursiveIteratorIterator::LEAVES_ONLY + ), '{'.preg_quote($ext).'$}' + ); + + return $this->collect(new Twig_Util_TemplateDirIterator($iterator)); + } + + /** + * Returns deprecations for passed templates. + * + * @param Traversable $iterator An iterator of templates (where keys are template names and values the contents of the template) + * + * @return array An array of deprecations + */ + public function collect(Traversable $iterator) + { + $this->deprecations = array(); + + set_error_handler(array($this, 'errorHandler')); + + foreach ($iterator as $name => $contents) { + try { + $this->twig->parse($this->twig->tokenize(new Twig_Source($contents, $name))); + } catch (Twig_Error_Syntax $e) { + // ignore templates containing syntax errors + } + } + + restore_error_handler(); + + $deprecations = $this->deprecations; + $this->deprecations = array(); + + return $deprecations; + } + + /** + * @internal + */ + public function errorHandler($type, $msg) + { + if (E_USER_DEPRECATED === $type) { + $this->deprecations[] = $msg; + } + } +} + +class_alias('Twig_Util_DeprecationCollector', 'Twig\Util\DeprecationCollector', false); diff --git a/inc/lib/Twig/Util/TemplateDirIterator.php b/inc/lib/Twig/Util/TemplateDirIterator.php new file mode 100644 index 00000000..c8682335 --- /dev/null +++ b/inc/lib/Twig/Util/TemplateDirIterator.php @@ -0,0 +1,28 @@ + + */ +class Twig_Util_TemplateDirIterator extends IteratorIterator +{ + public function current() + { + return file_get_contents(parent::current()); + } + + public function key() + { + return (string) parent::key(); + } +} + +class_alias('Twig_Util_TemplateDirIterator', 'Twig\Util\TemplateDirIterator', false);