mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-12 23:14:18 +01:00
N°7854 - ⬆️ Bump twig version
This commit is contained in:
@@ -178,6 +178,7 @@ return array(
|
||||
'CSVBulkExport' => $baseDir . '/core/csvbulkexport.class.inc.php',
|
||||
'CSVParser' => $baseDir . '/core/csvparser.class.inc.php',
|
||||
'CSVParserException' => $baseDir . '/application/exceptions/CSVParserException.php',
|
||||
'CURLStringFile' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php',
|
||||
'CellChangeSpec' => $baseDir . '/core/bulkchange.class.inc.php',
|
||||
'CellStatus_Ambiguous' => $baseDir . '/core/bulkchange.class.inc.php',
|
||||
'CellStatus_Issue' => $baseDir . '/core/bulkchange.class.inc.php',
|
||||
@@ -1503,6 +1504,7 @@ return array(
|
||||
'RestResultWithObjects' => $baseDir . '/core/restservices.class.inc.php',
|
||||
'RestResultWithRelations' => $baseDir . '/core/restservices.class.inc.php',
|
||||
'RestUtils' => $baseDir . '/application/applicationextension.inc.php',
|
||||
'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php',
|
||||
'RotatingLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php',
|
||||
'RowStatus' => $baseDir . '/core/bulkchange.class.inc.php',
|
||||
'RowStatus_Disappeared' => $baseDir . '/core/bulkchange.class.inc.php',
|
||||
@@ -2869,6 +2871,7 @@ return array(
|
||||
'Symfony\\Polyfill\\Php72\\Php72' => $vendorDir . '/symfony/polyfill-php72/Php72.php',
|
||||
'Symfony\\Polyfill\\Php80\\Php80' => $vendorDir . '/symfony/polyfill-php80/Php80.php',
|
||||
'Symfony\\Polyfill\\Php80\\PhpToken' => $vendorDir . '/symfony/polyfill-php80/PhpToken.php',
|
||||
'Symfony\\Polyfill\\Php81\\Php81' => $vendorDir . '/symfony/polyfill-php81/Php81.php',
|
||||
'Symfony\\Polyfill\\Php83\\Php83' => $vendorDir . '/symfony/polyfill-php83/Php83.php',
|
||||
'Symfony\\Runtime\\Symfony\\Component\\Console\\ApplicationRuntime' => $vendorDir . '/symfony/runtime/Internal/Console/ApplicationRuntime.php',
|
||||
'Symfony\\Runtime\\Symfony\\Component\\Console\\Command\\CommandRuntime' => $vendorDir . '/symfony/runtime/Internal/Console/Command/CommandRuntime.php',
|
||||
@@ -2916,10 +2919,17 @@ return array(
|
||||
'TriggerOnStateLeave' => $baseDir . '/core/trigger.class.inc.php',
|
||||
'TriggerOnThresholdReached' => $baseDir . '/core/trigger.class.inc.php',
|
||||
'TrueExpression' => $baseDir . '/core/oql/expression.class.inc.php',
|
||||
'Twig\\AbstractTwigCallable' => $vendorDir . '/twig/twig/src/AbstractTwigCallable.php',
|
||||
'Twig\\Attribute\\FirstClassTwigCallableReady' => $vendorDir . '/twig/twig/src/Attribute/FirstClassTwigCallableReady.php',
|
||||
'Twig\\Attribute\\YieldReady' => $vendorDir . '/twig/twig/src/Attribute/YieldReady.php',
|
||||
'Twig\\Cache\\CacheInterface' => $vendorDir . '/twig/twig/src/Cache/CacheInterface.php',
|
||||
'Twig\\Cache\\ChainCache' => $vendorDir . '/twig/twig/src/Cache/ChainCache.php',
|
||||
'Twig\\Cache\\FilesystemCache' => $vendorDir . '/twig/twig/src/Cache/FilesystemCache.php',
|
||||
'Twig\\Cache\\NullCache' => $vendorDir . '/twig/twig/src/Cache/NullCache.php',
|
||||
'Twig\\Cache\\ReadOnlyFilesystemCache' => $vendorDir . '/twig/twig/src/Cache/ReadOnlyFilesystemCache.php',
|
||||
'Twig\\Cache\\RemovableCacheInterface' => $vendorDir . '/twig/twig/src/Cache/RemovableCacheInterface.php',
|
||||
'Twig\\Compiler' => $vendorDir . '/twig/twig/src/Compiler.php',
|
||||
'Twig\\DeprecatedCallableInfo' => $vendorDir . '/twig/twig/src/DeprecatedCallableInfo.php',
|
||||
'Twig\\Environment' => $vendorDir . '/twig/twig/src/Environment.php',
|
||||
'Twig\\Error\\Error' => $vendorDir . '/twig/twig/src/Error/Error.php',
|
||||
'Twig\\Error\\LoaderError' => $vendorDir . '/twig/twig/src/Error/LoaderError.php',
|
||||
@@ -2939,6 +2949,7 @@ return array(
|
||||
'Twig\\Extension\\SandboxExtension' => $vendorDir . '/twig/twig/src/Extension/SandboxExtension.php',
|
||||
'Twig\\Extension\\StagingExtension' => $vendorDir . '/twig/twig/src/Extension/StagingExtension.php',
|
||||
'Twig\\Extension\\StringLoaderExtension' => $vendorDir . '/twig/twig/src/Extension/StringLoaderExtension.php',
|
||||
'Twig\\Extension\\YieldNotReadyExtension' => $vendorDir . '/twig/twig/src/Extension/YieldNotReadyExtension.php',
|
||||
'Twig\\FileExtensionEscapingStrategy' => $vendorDir . '/twig/twig/src/FileExtensionEscapingStrategy.php',
|
||||
'Twig\\Lexer' => $vendorDir . '/twig/twig/src/Lexer.php',
|
||||
'Twig\\Loader\\ArrayLoader' => $vendorDir . '/twig/twig/src/Loader/ArrayLoader.php',
|
||||
@@ -2949,21 +2960,23 @@ return array(
|
||||
'Twig\\NodeTraverser' => $vendorDir . '/twig/twig/src/NodeTraverser.php',
|
||||
'Twig\\NodeVisitor\\AbstractNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php',
|
||||
'Twig\\NodeVisitor\\EscaperNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php',
|
||||
'Twig\\NodeVisitor\\MacroAutoImportNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php',
|
||||
'Twig\\NodeVisitor\\NodeVisitorInterface' => $vendorDir . '/twig/twig/src/NodeVisitor/NodeVisitorInterface.php',
|
||||
'Twig\\NodeVisitor\\OptimizerNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php',
|
||||
'Twig\\NodeVisitor\\SafeAnalysisNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php',
|
||||
'Twig\\NodeVisitor\\SandboxNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php',
|
||||
'Twig\\NodeVisitor\\YieldNotReadyNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/YieldNotReadyNodeVisitor.php',
|
||||
'Twig\\Node\\AutoEscapeNode' => $vendorDir . '/twig/twig/src/Node/AutoEscapeNode.php',
|
||||
'Twig\\Node\\BlockNode' => $vendorDir . '/twig/twig/src/Node/BlockNode.php',
|
||||
'Twig\\Node\\BlockReferenceNode' => $vendorDir . '/twig/twig/src/Node/BlockReferenceNode.php',
|
||||
'Twig\\Node\\BodyNode' => $vendorDir . '/twig/twig/src/Node/BodyNode.php',
|
||||
'Twig\\Node\\CaptureNode' => $vendorDir . '/twig/twig/src/Node/CaptureNode.php',
|
||||
'Twig\\Node\\CheckSecurityCallNode' => $vendorDir . '/twig/twig/src/Node/CheckSecurityCallNode.php',
|
||||
'Twig\\Node\\CheckSecurityNode' => $vendorDir . '/twig/twig/src/Node/CheckSecurityNode.php',
|
||||
'Twig\\Node\\CheckToStringNode' => $vendorDir . '/twig/twig/src/Node/CheckToStringNode.php',
|
||||
'Twig\\Node\\DeprecatedNode' => $vendorDir . '/twig/twig/src/Node/DeprecatedNode.php',
|
||||
'Twig\\Node\\DoNode' => $vendorDir . '/twig/twig/src/Node/DoNode.php',
|
||||
'Twig\\Node\\EmbedNode' => $vendorDir . '/twig/twig/src/Node/EmbedNode.php',
|
||||
'Twig\\Node\\EmptyNode' => $vendorDir . '/twig/twig/src/Node/EmptyNode.php',
|
||||
'Twig\\Node\\Expression\\AbstractExpression' => $vendorDir . '/twig/twig/src/Node/Expression/AbstractExpression.php',
|
||||
'Twig\\Node\\Expression\\ArrayExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ArrayExpression.php',
|
||||
'Twig\\Node\\Expression\\ArrowFunctionExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ArrowFunctionExpression.php',
|
||||
@@ -2997,15 +3010,20 @@ return array(
|
||||
'Twig\\Node\\Expression\\Binary\\SpaceshipBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php',
|
||||
'Twig\\Node\\Expression\\Binary\\StartsWithBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php',
|
||||
'Twig\\Node\\Expression\\Binary\\SubBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/SubBinary.php',
|
||||
'Twig\\Node\\Expression\\Binary\\XorBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/XorBinary.php',
|
||||
'Twig\\Node\\Expression\\BlockReferenceExpression' => $vendorDir . '/twig/twig/src/Node/Expression/BlockReferenceExpression.php',
|
||||
'Twig\\Node\\Expression\\CallExpression' => $vendorDir . '/twig/twig/src/Node/Expression/CallExpression.php',
|
||||
'Twig\\Node\\Expression\\ConditionalExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ConditionalExpression.php',
|
||||
'Twig\\Node\\Expression\\ConstantExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ConstantExpression.php',
|
||||
'Twig\\Node\\Expression\\FilterExpression' => $vendorDir . '/twig/twig/src/Node/Expression/FilterExpression.php',
|
||||
'Twig\\Node\\Expression\\Filter\\DefaultFilter' => $vendorDir . '/twig/twig/src/Node/Expression/Filter/DefaultFilter.php',
|
||||
'Twig\\Node\\Expression\\Filter\\RawFilter' => $vendorDir . '/twig/twig/src/Node/Expression/Filter/RawFilter.php',
|
||||
'Twig\\Node\\Expression\\FunctionExpression' => $vendorDir . '/twig/twig/src/Node/Expression/FunctionExpression.php',
|
||||
'Twig\\Node\\Expression\\FunctionNode\\EnumCasesFunction' => $vendorDir . '/twig/twig/src/Node/Expression/FunctionNode/EnumCasesFunction.php',
|
||||
'Twig\\Node\\Expression\\FunctionNode\\EnumFunction' => $vendorDir . '/twig/twig/src/Node/Expression/FunctionNode/EnumFunction.php',
|
||||
'Twig\\Node\\Expression\\GetAttrExpression' => $vendorDir . '/twig/twig/src/Node/Expression/GetAttrExpression.php',
|
||||
'Twig\\Node\\Expression\\InlinePrint' => $vendorDir . '/twig/twig/src/Node/Expression/InlinePrint.php',
|
||||
'Twig\\Node\\Expression\\MacroReferenceExpression' => $vendorDir . '/twig/twig/src/Node/Expression/MacroReferenceExpression.php',
|
||||
'Twig\\Node\\Expression\\MethodCallExpression' => $vendorDir . '/twig/twig/src/Node/Expression/MethodCallExpression.php',
|
||||
'Twig\\Node\\Expression\\NameExpression' => $vendorDir . '/twig/twig/src/Node/Expression/NameExpression.php',
|
||||
'Twig\\Node\\Expression\\NullCoalesceExpression' => $vendorDir . '/twig/twig/src/Node/Expression/NullCoalesceExpression.php',
|
||||
@@ -3023,6 +3041,13 @@ return array(
|
||||
'Twig\\Node\\Expression\\Unary\\NegUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/NegUnary.php',
|
||||
'Twig\\Node\\Expression\\Unary\\NotUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/NotUnary.php',
|
||||
'Twig\\Node\\Expression\\Unary\\PosUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/PosUnary.php',
|
||||
'Twig\\Node\\Expression\\Unary\\SpreadUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/SpreadUnary.php',
|
||||
'Twig\\Node\\Expression\\Unary\\StringCastUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/StringCastUnary.php',
|
||||
'Twig\\Node\\Expression\\Variable\\AssignContextVariable' => $vendorDir . '/twig/twig/src/Node/Expression/Variable/AssignContextVariable.php',
|
||||
'Twig\\Node\\Expression\\Variable\\AssignTemplateVariable' => $vendorDir . '/twig/twig/src/Node/Expression/Variable/AssignTemplateVariable.php',
|
||||
'Twig\\Node\\Expression\\Variable\\ContextVariable' => $vendorDir . '/twig/twig/src/Node/Expression/Variable/ContextVariable.php',
|
||||
'Twig\\Node\\Expression\\Variable\\LocalVariable' => $vendorDir . '/twig/twig/src/Node/Expression/Variable/LocalVariable.php',
|
||||
'Twig\\Node\\Expression\\Variable\\TemplateVariable' => $vendorDir . '/twig/twig/src/Node/Expression/Variable/TemplateVariable.php',
|
||||
'Twig\\Node\\Expression\\VariadicExpression' => $vendorDir . '/twig/twig/src/Node/Expression/VariadicExpression.php',
|
||||
'Twig\\Node\\FlushNode' => $vendorDir . '/twig/twig/src/Node/FlushNode.php',
|
||||
'Twig\\Node\\ForLoopNode' => $vendorDir . '/twig/twig/src/Node/ForLoopNode.php',
|
||||
@@ -3032,14 +3057,18 @@ return array(
|
||||
'Twig\\Node\\IncludeNode' => $vendorDir . '/twig/twig/src/Node/IncludeNode.php',
|
||||
'Twig\\Node\\MacroNode' => $vendorDir . '/twig/twig/src/Node/MacroNode.php',
|
||||
'Twig\\Node\\ModuleNode' => $vendorDir . '/twig/twig/src/Node/ModuleNode.php',
|
||||
'Twig\\Node\\NameDeprecation' => $vendorDir . '/twig/twig/src/Node/NameDeprecation.php',
|
||||
'Twig\\Node\\Node' => $vendorDir . '/twig/twig/src/Node/Node.php',
|
||||
'Twig\\Node\\NodeCaptureInterface' => $vendorDir . '/twig/twig/src/Node/NodeCaptureInterface.php',
|
||||
'Twig\\Node\\NodeOutputInterface' => $vendorDir . '/twig/twig/src/Node/NodeOutputInterface.php',
|
||||
'Twig\\Node\\Nodes' => $vendorDir . '/twig/twig/src/Node/Nodes.php',
|
||||
'Twig\\Node\\PrintNode' => $vendorDir . '/twig/twig/src/Node/PrintNode.php',
|
||||
'Twig\\Node\\SandboxNode' => $vendorDir . '/twig/twig/src/Node/SandboxNode.php',
|
||||
'Twig\\Node\\SetNode' => $vendorDir . '/twig/twig/src/Node/SetNode.php',
|
||||
'Twig\\Node\\TextNode' => $vendorDir . '/twig/twig/src/Node/TextNode.php',
|
||||
'Twig\\Node\\TypesNode' => $vendorDir . '/twig/twig/src/Node/TypesNode.php',
|
||||
'Twig\\Node\\WithNode' => $vendorDir . '/twig/twig/src/Node/WithNode.php',
|
||||
'Twig\\OperatorPrecedenceChange' => $vendorDir . '/twig/twig/src/OperatorPrecedenceChange.php',
|
||||
'Twig\\Parser' => $vendorDir . '/twig/twig/src/Parser.php',
|
||||
'Twig\\Profiler\\Dumper\\BaseDumper' => $vendorDir . '/twig/twig/src/Profiler/Dumper/BaseDumper.php',
|
||||
'Twig\\Profiler\\Dumper\\BlackfireDumper' => $vendorDir . '/twig/twig/src/Profiler/Dumper/BlackfireDumper.php',
|
||||
@@ -3052,6 +3081,7 @@ return array(
|
||||
'Twig\\RuntimeLoader\\ContainerRuntimeLoader' => $vendorDir . '/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php',
|
||||
'Twig\\RuntimeLoader\\FactoryRuntimeLoader' => $vendorDir . '/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php',
|
||||
'Twig\\RuntimeLoader\\RuntimeLoaderInterface' => $vendorDir . '/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php',
|
||||
'Twig\\Runtime\\EscaperRuntime' => $vendorDir . '/twig/twig/src/Runtime/EscaperRuntime.php',
|
||||
'Twig\\Sandbox\\SecurityError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityError.php',
|
||||
'Twig\\Sandbox\\SecurityNotAllowedFilterError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php',
|
||||
'Twig\\Sandbox\\SecurityNotAllowedFunctionError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php',
|
||||
@@ -3060,9 +3090,12 @@ return array(
|
||||
'Twig\\Sandbox\\SecurityNotAllowedTagError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php',
|
||||
'Twig\\Sandbox\\SecurityPolicy' => $vendorDir . '/twig/twig/src/Sandbox/SecurityPolicy.php',
|
||||
'Twig\\Sandbox\\SecurityPolicyInterface' => $vendorDir . '/twig/twig/src/Sandbox/SecurityPolicyInterface.php',
|
||||
'Twig\\Sandbox\\SourcePolicyInterface' => $vendorDir . '/twig/twig/src/Sandbox/SourcePolicyInterface.php',
|
||||
'Twig\\Source' => $vendorDir . '/twig/twig/src/Source.php',
|
||||
'Twig\\Template' => $vendorDir . '/twig/twig/src/Template.php',
|
||||
'Twig\\TemplateWrapper' => $vendorDir . '/twig/twig/src/TemplateWrapper.php',
|
||||
'Twig\\Test\\IntegrationTestCase' => $vendorDir . '/twig/twig/src/Test/IntegrationTestCase.php',
|
||||
'Twig\\Test\\NodeTestCase' => $vendorDir . '/twig/twig/src/Test/NodeTestCase.php',
|
||||
'Twig\\Token' => $vendorDir . '/twig/twig/src/Token.php',
|
||||
'Twig\\TokenParser\\AbstractTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/AbstractTokenParser.php',
|
||||
'Twig\\TokenParser\\ApplyTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ApplyTokenParser.php',
|
||||
@@ -3075,6 +3108,7 @@ return array(
|
||||
'Twig\\TokenParser\\FlushTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/FlushTokenParser.php',
|
||||
'Twig\\TokenParser\\ForTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ForTokenParser.php',
|
||||
'Twig\\TokenParser\\FromTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/FromTokenParser.php',
|
||||
'Twig\\TokenParser\\GuardTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/GuardTokenParser.php',
|
||||
'Twig\\TokenParser\\IfTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/IfTokenParser.php',
|
||||
'Twig\\TokenParser\\ImportTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ImportTokenParser.php',
|
||||
'Twig\\TokenParser\\IncludeTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/IncludeTokenParser.php',
|
||||
@@ -3082,13 +3116,17 @@ return array(
|
||||
'Twig\\TokenParser\\SandboxTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/SandboxTokenParser.php',
|
||||
'Twig\\TokenParser\\SetTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/SetTokenParser.php',
|
||||
'Twig\\TokenParser\\TokenParserInterface' => $vendorDir . '/twig/twig/src/TokenParser/TokenParserInterface.php',
|
||||
'Twig\\TokenParser\\TypesTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/TypesTokenParser.php',
|
||||
'Twig\\TokenParser\\UseTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/UseTokenParser.php',
|
||||
'Twig\\TokenParser\\WithTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/WithTokenParser.php',
|
||||
'Twig\\TokenStream' => $vendorDir . '/twig/twig/src/TokenStream.php',
|
||||
'Twig\\TwigCallableInterface' => $vendorDir . '/twig/twig/src/TwigCallableInterface.php',
|
||||
'Twig\\TwigFilter' => $vendorDir . '/twig/twig/src/TwigFilter.php',
|
||||
'Twig\\TwigFunction' => $vendorDir . '/twig/twig/src/TwigFunction.php',
|
||||
'Twig\\TwigTest' => $vendorDir . '/twig/twig/src/TwigTest.php',
|
||||
'Twig\\Util\\CallableArgumentsExtractor' => $vendorDir . '/twig/twig/src/Util/CallableArgumentsExtractor.php',
|
||||
'Twig\\Util\\DeprecationCollector' => $vendorDir . '/twig/twig/src/Util/DeprecationCollector.php',
|
||||
'Twig\\Util\\ReflectionCallable' => $vendorDir . '/twig/twig/src/Util/ReflectionCallable.php',
|
||||
'Twig\\Util\\TemplateDirIterator' => $vendorDir . '/twig/twig/src/Util/TemplateDirIterator.php',
|
||||
'UIExtKeyWidget' => $baseDir . '/application/ui.extkeywidget.class.inc.php',
|
||||
'UIHTMLEditorWidget' => $baseDir . '/application/ui.htmleditorwidget.class.inc.php',
|
||||
|
||||
@@ -12,6 +12,11 @@ return array(
|
||||
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
|
||||
'662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php',
|
||||
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
|
||||
'23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php',
|
||||
'89efb1254ef2d1c5d80096acd12c4098' => $vendorDir . '/twig/twig/src/Resources/core.php',
|
||||
'ffecb95d45175fd40f75be8a23b34f90' => $vendorDir . '/twig/twig/src/Resources/debug.php',
|
||||
'c7baa00073ee9c61edf148c51917cfb4' => $vendorDir . '/twig/twig/src/Resources/escaper.php',
|
||||
'f844ccf1d25df8663951193c3fc307c8' => $vendorDir . '/twig/twig/src/Resources/string_loader.php',
|
||||
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
|
||||
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
|
||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||
|
||||
@@ -11,6 +11,7 @@ return array(
|
||||
'TheNetworg\\OAuth2\\Client\\' => array($vendorDir . '/thenetworg/oauth2-azure/src'),
|
||||
'Symfony\\Runtime\\Symfony\\Component\\' => array($vendorDir . '/symfony/runtime/Internal'),
|
||||
'Symfony\\Polyfill\\Php83\\' => array($vendorDir . '/symfony/polyfill-php83'),
|
||||
'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'),
|
||||
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
|
||||
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),
|
||||
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
|
||||
|
||||
@@ -13,6 +13,11 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
|
||||
'662a729f963d39afe703c9d9b7ab4a8c' => __DIR__ . '/..' . '/symfony/polyfill-php83/bootstrap.php',
|
||||
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
|
||||
'23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php',
|
||||
'89efb1254ef2d1c5d80096acd12c4098' => __DIR__ . '/..' . '/twig/twig/src/Resources/core.php',
|
||||
'ffecb95d45175fd40f75be8a23b34f90' => __DIR__ . '/..' . '/twig/twig/src/Resources/debug.php',
|
||||
'c7baa00073ee9c61edf148c51917cfb4' => __DIR__ . '/..' . '/twig/twig/src/Resources/escaper.php',
|
||||
'f844ccf1d25df8663951193c3fc307c8' => __DIR__ . '/..' . '/twig/twig/src/Resources/string_loader.php',
|
||||
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
|
||||
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
|
||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||
@@ -39,6 +44,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
array (
|
||||
'Symfony\\Runtime\\Symfony\\Component\\' => 34,
|
||||
'Symfony\\Polyfill\\Php83\\' => 23,
|
||||
'Symfony\\Polyfill\\Php81\\' => 23,
|
||||
'Symfony\\Polyfill\\Php80\\' => 23,
|
||||
'Symfony\\Polyfill\\Php72\\' => 23,
|
||||
'Symfony\\Polyfill\\Mbstring\\' => 26,
|
||||
@@ -132,6 +138,10 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-php83',
|
||||
),
|
||||
'Symfony\\Polyfill\\Php81\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-php81',
|
||||
),
|
||||
'Symfony\\Polyfill\\Php80\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
|
||||
@@ -558,6 +568,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'CSVBulkExport' => __DIR__ . '/../..' . '/core/csvbulkexport.class.inc.php',
|
||||
'CSVParser' => __DIR__ . '/../..' . '/core/csvparser.class.inc.php',
|
||||
'CSVParserException' => __DIR__ . '/../..' . '/application/exceptions/CSVParserException.php',
|
||||
'CURLStringFile' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php',
|
||||
'CellChangeSpec' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
|
||||
'CellStatus_Ambiguous' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
|
||||
'CellStatus_Issue' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
|
||||
@@ -1883,6 +1894,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'RestResultWithObjects' => __DIR__ . '/../..' . '/core/restservices.class.inc.php',
|
||||
'RestResultWithRelations' => __DIR__ . '/../..' . '/core/restservices.class.inc.php',
|
||||
'RestUtils' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||
'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php',
|
||||
'RotatingLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php',
|
||||
'RowStatus' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
|
||||
'RowStatus_Disappeared' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
|
||||
@@ -3249,6 +3261,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Symfony\\Polyfill\\Php72\\Php72' => __DIR__ . '/..' . '/symfony/polyfill-php72/Php72.php',
|
||||
'Symfony\\Polyfill\\Php80\\Php80' => __DIR__ . '/..' . '/symfony/polyfill-php80/Php80.php',
|
||||
'Symfony\\Polyfill\\Php80\\PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/PhpToken.php',
|
||||
'Symfony\\Polyfill\\Php81\\Php81' => __DIR__ . '/..' . '/symfony/polyfill-php81/Php81.php',
|
||||
'Symfony\\Polyfill\\Php83\\Php83' => __DIR__ . '/..' . '/symfony/polyfill-php83/Php83.php',
|
||||
'Symfony\\Runtime\\Symfony\\Component\\Console\\ApplicationRuntime' => __DIR__ . '/..' . '/symfony/runtime/Internal/Console/ApplicationRuntime.php',
|
||||
'Symfony\\Runtime\\Symfony\\Component\\Console\\Command\\CommandRuntime' => __DIR__ . '/..' . '/symfony/runtime/Internal/Console/Command/CommandRuntime.php',
|
||||
@@ -3296,10 +3309,17 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'TriggerOnStateLeave' => __DIR__ . '/../..' . '/core/trigger.class.inc.php',
|
||||
'TriggerOnThresholdReached' => __DIR__ . '/../..' . '/core/trigger.class.inc.php',
|
||||
'TrueExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php',
|
||||
'Twig\\AbstractTwigCallable' => __DIR__ . '/..' . '/twig/twig/src/AbstractTwigCallable.php',
|
||||
'Twig\\Attribute\\FirstClassTwigCallableReady' => __DIR__ . '/..' . '/twig/twig/src/Attribute/FirstClassTwigCallableReady.php',
|
||||
'Twig\\Attribute\\YieldReady' => __DIR__ . '/..' . '/twig/twig/src/Attribute/YieldReady.php',
|
||||
'Twig\\Cache\\CacheInterface' => __DIR__ . '/..' . '/twig/twig/src/Cache/CacheInterface.php',
|
||||
'Twig\\Cache\\ChainCache' => __DIR__ . '/..' . '/twig/twig/src/Cache/ChainCache.php',
|
||||
'Twig\\Cache\\FilesystemCache' => __DIR__ . '/..' . '/twig/twig/src/Cache/FilesystemCache.php',
|
||||
'Twig\\Cache\\NullCache' => __DIR__ . '/..' . '/twig/twig/src/Cache/NullCache.php',
|
||||
'Twig\\Cache\\ReadOnlyFilesystemCache' => __DIR__ . '/..' . '/twig/twig/src/Cache/ReadOnlyFilesystemCache.php',
|
||||
'Twig\\Cache\\RemovableCacheInterface' => __DIR__ . '/..' . '/twig/twig/src/Cache/RemovableCacheInterface.php',
|
||||
'Twig\\Compiler' => __DIR__ . '/..' . '/twig/twig/src/Compiler.php',
|
||||
'Twig\\DeprecatedCallableInfo' => __DIR__ . '/..' . '/twig/twig/src/DeprecatedCallableInfo.php',
|
||||
'Twig\\Environment' => __DIR__ . '/..' . '/twig/twig/src/Environment.php',
|
||||
'Twig\\Error\\Error' => __DIR__ . '/..' . '/twig/twig/src/Error/Error.php',
|
||||
'Twig\\Error\\LoaderError' => __DIR__ . '/..' . '/twig/twig/src/Error/LoaderError.php',
|
||||
@@ -3319,6 +3339,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Twig\\Extension\\SandboxExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/SandboxExtension.php',
|
||||
'Twig\\Extension\\StagingExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/StagingExtension.php',
|
||||
'Twig\\Extension\\StringLoaderExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/StringLoaderExtension.php',
|
||||
'Twig\\Extension\\YieldNotReadyExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/YieldNotReadyExtension.php',
|
||||
'Twig\\FileExtensionEscapingStrategy' => __DIR__ . '/..' . '/twig/twig/src/FileExtensionEscapingStrategy.php',
|
||||
'Twig\\Lexer' => __DIR__ . '/..' . '/twig/twig/src/Lexer.php',
|
||||
'Twig\\Loader\\ArrayLoader' => __DIR__ . '/..' . '/twig/twig/src/Loader/ArrayLoader.php',
|
||||
@@ -3329,21 +3350,23 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Twig\\NodeTraverser' => __DIR__ . '/..' . '/twig/twig/src/NodeTraverser.php',
|
||||
'Twig\\NodeVisitor\\AbstractNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php',
|
||||
'Twig\\NodeVisitor\\EscaperNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php',
|
||||
'Twig\\NodeVisitor\\MacroAutoImportNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php',
|
||||
'Twig\\NodeVisitor\\NodeVisitorInterface' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/NodeVisitorInterface.php',
|
||||
'Twig\\NodeVisitor\\OptimizerNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php',
|
||||
'Twig\\NodeVisitor\\SafeAnalysisNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php',
|
||||
'Twig\\NodeVisitor\\SandboxNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php',
|
||||
'Twig\\NodeVisitor\\YieldNotReadyNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/YieldNotReadyNodeVisitor.php',
|
||||
'Twig\\Node\\AutoEscapeNode' => __DIR__ . '/..' . '/twig/twig/src/Node/AutoEscapeNode.php',
|
||||
'Twig\\Node\\BlockNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BlockNode.php',
|
||||
'Twig\\Node\\BlockReferenceNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BlockReferenceNode.php',
|
||||
'Twig\\Node\\BodyNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BodyNode.php',
|
||||
'Twig\\Node\\CaptureNode' => __DIR__ . '/..' . '/twig/twig/src/Node/CaptureNode.php',
|
||||
'Twig\\Node\\CheckSecurityCallNode' => __DIR__ . '/..' . '/twig/twig/src/Node/CheckSecurityCallNode.php',
|
||||
'Twig\\Node\\CheckSecurityNode' => __DIR__ . '/..' . '/twig/twig/src/Node/CheckSecurityNode.php',
|
||||
'Twig\\Node\\CheckToStringNode' => __DIR__ . '/..' . '/twig/twig/src/Node/CheckToStringNode.php',
|
||||
'Twig\\Node\\DeprecatedNode' => __DIR__ . '/..' . '/twig/twig/src/Node/DeprecatedNode.php',
|
||||
'Twig\\Node\\DoNode' => __DIR__ . '/..' . '/twig/twig/src/Node/DoNode.php',
|
||||
'Twig\\Node\\EmbedNode' => __DIR__ . '/..' . '/twig/twig/src/Node/EmbedNode.php',
|
||||
'Twig\\Node\\EmptyNode' => __DIR__ . '/..' . '/twig/twig/src/Node/EmptyNode.php',
|
||||
'Twig\\Node\\Expression\\AbstractExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/AbstractExpression.php',
|
||||
'Twig\\Node\\Expression\\ArrayExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ArrayExpression.php',
|
||||
'Twig\\Node\\Expression\\ArrowFunctionExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ArrowFunctionExpression.php',
|
||||
@@ -3377,15 +3400,20 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Twig\\Node\\Expression\\Binary\\SpaceshipBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php',
|
||||
'Twig\\Node\\Expression\\Binary\\StartsWithBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php',
|
||||
'Twig\\Node\\Expression\\Binary\\SubBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/SubBinary.php',
|
||||
'Twig\\Node\\Expression\\Binary\\XorBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/XorBinary.php',
|
||||
'Twig\\Node\\Expression\\BlockReferenceExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/BlockReferenceExpression.php',
|
||||
'Twig\\Node\\Expression\\CallExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/CallExpression.php',
|
||||
'Twig\\Node\\Expression\\ConditionalExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ConditionalExpression.php',
|
||||
'Twig\\Node\\Expression\\ConstantExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ConstantExpression.php',
|
||||
'Twig\\Node\\Expression\\FilterExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/FilterExpression.php',
|
||||
'Twig\\Node\\Expression\\Filter\\DefaultFilter' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Filter/DefaultFilter.php',
|
||||
'Twig\\Node\\Expression\\Filter\\RawFilter' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Filter/RawFilter.php',
|
||||
'Twig\\Node\\Expression\\FunctionExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/FunctionExpression.php',
|
||||
'Twig\\Node\\Expression\\FunctionNode\\EnumCasesFunction' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/FunctionNode/EnumCasesFunction.php',
|
||||
'Twig\\Node\\Expression\\FunctionNode\\EnumFunction' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/FunctionNode/EnumFunction.php',
|
||||
'Twig\\Node\\Expression\\GetAttrExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/GetAttrExpression.php',
|
||||
'Twig\\Node\\Expression\\InlinePrint' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/InlinePrint.php',
|
||||
'Twig\\Node\\Expression\\MacroReferenceExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/MacroReferenceExpression.php',
|
||||
'Twig\\Node\\Expression\\MethodCallExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/MethodCallExpression.php',
|
||||
'Twig\\Node\\Expression\\NameExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/NameExpression.php',
|
||||
'Twig\\Node\\Expression\\NullCoalesceExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/NullCoalesceExpression.php',
|
||||
@@ -3403,6 +3431,13 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Twig\\Node\\Expression\\Unary\\NegUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/NegUnary.php',
|
||||
'Twig\\Node\\Expression\\Unary\\NotUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/NotUnary.php',
|
||||
'Twig\\Node\\Expression\\Unary\\PosUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/PosUnary.php',
|
||||
'Twig\\Node\\Expression\\Unary\\SpreadUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/SpreadUnary.php',
|
||||
'Twig\\Node\\Expression\\Unary\\StringCastUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/StringCastUnary.php',
|
||||
'Twig\\Node\\Expression\\Variable\\AssignContextVariable' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Variable/AssignContextVariable.php',
|
||||
'Twig\\Node\\Expression\\Variable\\AssignTemplateVariable' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Variable/AssignTemplateVariable.php',
|
||||
'Twig\\Node\\Expression\\Variable\\ContextVariable' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Variable/ContextVariable.php',
|
||||
'Twig\\Node\\Expression\\Variable\\LocalVariable' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Variable/LocalVariable.php',
|
||||
'Twig\\Node\\Expression\\Variable\\TemplateVariable' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Variable/TemplateVariable.php',
|
||||
'Twig\\Node\\Expression\\VariadicExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/VariadicExpression.php',
|
||||
'Twig\\Node\\FlushNode' => __DIR__ . '/..' . '/twig/twig/src/Node/FlushNode.php',
|
||||
'Twig\\Node\\ForLoopNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ForLoopNode.php',
|
||||
@@ -3412,14 +3447,18 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Twig\\Node\\IncludeNode' => __DIR__ . '/..' . '/twig/twig/src/Node/IncludeNode.php',
|
||||
'Twig\\Node\\MacroNode' => __DIR__ . '/..' . '/twig/twig/src/Node/MacroNode.php',
|
||||
'Twig\\Node\\ModuleNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ModuleNode.php',
|
||||
'Twig\\Node\\NameDeprecation' => __DIR__ . '/..' . '/twig/twig/src/Node/NameDeprecation.php',
|
||||
'Twig\\Node\\Node' => __DIR__ . '/..' . '/twig/twig/src/Node/Node.php',
|
||||
'Twig\\Node\\NodeCaptureInterface' => __DIR__ . '/..' . '/twig/twig/src/Node/NodeCaptureInterface.php',
|
||||
'Twig\\Node\\NodeOutputInterface' => __DIR__ . '/..' . '/twig/twig/src/Node/NodeOutputInterface.php',
|
||||
'Twig\\Node\\Nodes' => __DIR__ . '/..' . '/twig/twig/src/Node/Nodes.php',
|
||||
'Twig\\Node\\PrintNode' => __DIR__ . '/..' . '/twig/twig/src/Node/PrintNode.php',
|
||||
'Twig\\Node\\SandboxNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SandboxNode.php',
|
||||
'Twig\\Node\\SetNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SetNode.php',
|
||||
'Twig\\Node\\TextNode' => __DIR__ . '/..' . '/twig/twig/src/Node/TextNode.php',
|
||||
'Twig\\Node\\TypesNode' => __DIR__ . '/..' . '/twig/twig/src/Node/TypesNode.php',
|
||||
'Twig\\Node\\WithNode' => __DIR__ . '/..' . '/twig/twig/src/Node/WithNode.php',
|
||||
'Twig\\OperatorPrecedenceChange' => __DIR__ . '/..' . '/twig/twig/src/OperatorPrecedenceChange.php',
|
||||
'Twig\\Parser' => __DIR__ . '/..' . '/twig/twig/src/Parser.php',
|
||||
'Twig\\Profiler\\Dumper\\BaseDumper' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Dumper/BaseDumper.php',
|
||||
'Twig\\Profiler\\Dumper\\BlackfireDumper' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Dumper/BlackfireDumper.php',
|
||||
@@ -3432,6 +3471,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Twig\\RuntimeLoader\\ContainerRuntimeLoader' => __DIR__ . '/..' . '/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php',
|
||||
'Twig\\RuntimeLoader\\FactoryRuntimeLoader' => __DIR__ . '/..' . '/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php',
|
||||
'Twig\\RuntimeLoader\\RuntimeLoaderInterface' => __DIR__ . '/..' . '/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php',
|
||||
'Twig\\Runtime\\EscaperRuntime' => __DIR__ . '/..' . '/twig/twig/src/Runtime/EscaperRuntime.php',
|
||||
'Twig\\Sandbox\\SecurityError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityError.php',
|
||||
'Twig\\Sandbox\\SecurityNotAllowedFilterError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php',
|
||||
'Twig\\Sandbox\\SecurityNotAllowedFunctionError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php',
|
||||
@@ -3440,9 +3480,12 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Twig\\Sandbox\\SecurityNotAllowedTagError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php',
|
||||
'Twig\\Sandbox\\SecurityPolicy' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityPolicy.php',
|
||||
'Twig\\Sandbox\\SecurityPolicyInterface' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityPolicyInterface.php',
|
||||
'Twig\\Sandbox\\SourcePolicyInterface' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SourcePolicyInterface.php',
|
||||
'Twig\\Source' => __DIR__ . '/..' . '/twig/twig/src/Source.php',
|
||||
'Twig\\Template' => __DIR__ . '/..' . '/twig/twig/src/Template.php',
|
||||
'Twig\\TemplateWrapper' => __DIR__ . '/..' . '/twig/twig/src/TemplateWrapper.php',
|
||||
'Twig\\Test\\IntegrationTestCase' => __DIR__ . '/..' . '/twig/twig/src/Test/IntegrationTestCase.php',
|
||||
'Twig\\Test\\NodeTestCase' => __DIR__ . '/..' . '/twig/twig/src/Test/NodeTestCase.php',
|
||||
'Twig\\Token' => __DIR__ . '/..' . '/twig/twig/src/Token.php',
|
||||
'Twig\\TokenParser\\AbstractTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/AbstractTokenParser.php',
|
||||
'Twig\\TokenParser\\ApplyTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ApplyTokenParser.php',
|
||||
@@ -3455,6 +3498,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Twig\\TokenParser\\FlushTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/FlushTokenParser.php',
|
||||
'Twig\\TokenParser\\ForTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ForTokenParser.php',
|
||||
'Twig\\TokenParser\\FromTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/FromTokenParser.php',
|
||||
'Twig\\TokenParser\\GuardTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/GuardTokenParser.php',
|
||||
'Twig\\TokenParser\\IfTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/IfTokenParser.php',
|
||||
'Twig\\TokenParser\\ImportTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ImportTokenParser.php',
|
||||
'Twig\\TokenParser\\IncludeTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/IncludeTokenParser.php',
|
||||
@@ -3462,13 +3506,17 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Twig\\TokenParser\\SandboxTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/SandboxTokenParser.php',
|
||||
'Twig\\TokenParser\\SetTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/SetTokenParser.php',
|
||||
'Twig\\TokenParser\\TokenParserInterface' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/TokenParserInterface.php',
|
||||
'Twig\\TokenParser\\TypesTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/TypesTokenParser.php',
|
||||
'Twig\\TokenParser\\UseTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/UseTokenParser.php',
|
||||
'Twig\\TokenParser\\WithTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/WithTokenParser.php',
|
||||
'Twig\\TokenStream' => __DIR__ . '/..' . '/twig/twig/src/TokenStream.php',
|
||||
'Twig\\TwigCallableInterface' => __DIR__ . '/..' . '/twig/twig/src/TwigCallableInterface.php',
|
||||
'Twig\\TwigFilter' => __DIR__ . '/..' . '/twig/twig/src/TwigFilter.php',
|
||||
'Twig\\TwigFunction' => __DIR__ . '/..' . '/twig/twig/src/TwigFunction.php',
|
||||
'Twig\\TwigTest' => __DIR__ . '/..' . '/twig/twig/src/TwigTest.php',
|
||||
'Twig\\Util\\CallableArgumentsExtractor' => __DIR__ . '/..' . '/twig/twig/src/Util/CallableArgumentsExtractor.php',
|
||||
'Twig\\Util\\DeprecationCollector' => __DIR__ . '/..' . '/twig/twig/src/Util/DeprecationCollector.php',
|
||||
'Twig\\Util\\ReflectionCallable' => __DIR__ . '/..' . '/twig/twig/src/Util/ReflectionCallable.php',
|
||||
'Twig\\Util\\TemplateDirIterator' => __DIR__ . '/..' . '/twig/twig/src/Util/TemplateDirIterator.php',
|
||||
'UIExtKeyWidget' => __DIR__ . '/../..' . '/application/ui.extkeywidget.class.inc.php',
|
||||
'UIHTMLEditorWidget' => __DIR__ . '/../..' . '/application/ui.htmleditorwidget.class.inc.php',
|
||||
|
||||
@@ -4162,6 +4162,85 @@
|
||||
],
|
||||
"install-path": "../symfony/polyfill-php80"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php81",
|
||||
"version": "v1.31.0",
|
||||
"version_normalized": "1.31.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php81.git",
|
||||
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
|
||||
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"time": "2024-09-09T11:45:10+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php81\\": ""
|
||||
},
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"install-path": "../symfony/polyfill-php81"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php83",
|
||||
"version": "v1.28.0",
|
||||
@@ -5398,33 +5477,41 @@
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
"version": "v3.8.0",
|
||||
"version_normalized": "3.8.0.0",
|
||||
"version": "v3.16.0",
|
||||
"version_normalized": "3.16.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twigphp/Twig.git",
|
||||
"reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d"
|
||||
"reference": "475ad2dc97d65d8631393e721e7e44fb544f0561"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d",
|
||||
"reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/475ad2dc97d65d8631393e721e7e44fb544f0561",
|
||||
"reference": "475ad2dc97d65d8631393e721e7e44fb544f0561",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"php": ">=8.0.2",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/polyfill-ctype": "^1.8",
|
||||
"symfony/polyfill-mbstring": "^1.3",
|
||||
"symfony/polyfill-php80": "^1.22"
|
||||
"symfony/polyfill-php81": "^1.29"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"psr/container": "^1.0|^2.0",
|
||||
"symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0"
|
||||
"symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
|
||||
},
|
||||
"time": "2023-11-21T18:54:41+00:00",
|
||||
"time": "2024-11-29T08:27:05+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/Resources/core.php",
|
||||
"src/Resources/debug.php",
|
||||
"src/Resources/escaper.php",
|
||||
"src/Resources/string_loader.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Twig\\": "src/"
|
||||
}
|
||||
@@ -5457,7 +5544,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/twigphp/Twig/issues",
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.8.0"
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.16.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
'name' => 'combodo/itop',
|
||||
'pretty_version' => 'dev-develop',
|
||||
'version' => 'dev-develop',
|
||||
'reference' => '5ae2fdee94b925808451355b98b941351e0b4fcd',
|
||||
'reference' => '1bf53bae2a481c76bba07e2f6d3447b4008efacd',
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
@@ -22,7 +22,7 @@
|
||||
'combodo/itop' => array(
|
||||
'pretty_version' => 'dev-develop',
|
||||
'version' => 'dev-develop',
|
||||
'reference' => '5ae2fdee94b925808451355b98b941351e0b4fcd',
|
||||
'reference' => '1bf53bae2a481c76bba07e2f6d3447b4008efacd',
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
@@ -587,6 +587,15 @@
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/polyfill-php81' => array(
|
||||
'pretty_version' => 'v1.31.0',
|
||||
'version' => '1.31.0.0',
|
||||
'reference' => '4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/polyfill-php81',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/polyfill-php83' => array(
|
||||
'pretty_version' => 'v1.28.0',
|
||||
'version' => '1.28.0.0',
|
||||
@@ -729,9 +738,9 @@
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'twig/twig' => array(
|
||||
'pretty_version' => 'v3.8.0',
|
||||
'version' => '3.8.0.0',
|
||||
'reference' => '9d15f0ac07f44dc4217883ec6ae02fd555c6f71d',
|
||||
'pretty_version' => 'v3.16.0',
|
||||
'version' => '3.16.0.0',
|
||||
'reference' => '475ad2dc97d65d8631393e721e7e44fb544f0561',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../twig/twig',
|
||||
'aliases' => array(),
|
||||
|
||||
@@ -1,3 +1,202 @@
|
||||
# 3.16.0 (2024-XX-XX)
|
||||
|
||||
* Deprecate `InlinePrint`
|
||||
* Fix having macro variables starting with an underscore
|
||||
* Deprecate not passing a `Source` instance to `TokenStream`
|
||||
* Deprecate returning `null` from `TwigFilter::getSafe()` and `TwigFunction::getSafe()`, return `[]` instead
|
||||
|
||||
# 3.15.0 (2024-11-17)
|
||||
|
||||
* [BC BREAK] Add support for accessing class constants with the dot operator;
|
||||
this can be a BC break if you don't use UPPERCASE constant names
|
||||
* Add Spanish inflector support for the `plural` and `singular` filters in the String extension
|
||||
* Deprecate `TempNameExpression` in favor of `LocalVariable`
|
||||
* Deprecate `NameExpression` in favor of `ContextVariable`
|
||||
* Deprecate `AssignNameExpression` in favor of `AssignContextVariable`
|
||||
* Remove `MacroAutoImportNodeVisitor`
|
||||
* Deprecate `MethodCallExpression` in favor of `MacroReferenceExpression`
|
||||
* Fix support for the "is defined" test on `_self.xxx` (auto-imported) macros
|
||||
* Fix support for the "is defined" test on inherited macros
|
||||
* Add named arguments support for the dot operator arguments (`foo.bar(some: arg)`)
|
||||
* Add named arguments support for macros
|
||||
* Add a new `guard` tag that allows to test if some Twig callables are available at compilation time
|
||||
* Allow arrow functions everywhere
|
||||
* Deprecate passing a string or an array to Twig callable arguments accepting arrow functions (pass a `\Closure`)
|
||||
* Add support for triggering deprecations for future operator precedence changes
|
||||
* Deprecate using the `not` unary operator in an expression with ``*``, ``/``, ``//``, or ``%`` without using explicit parentheses to clarify precedence
|
||||
* Deprecate using the `??` binary operator without explicit parentheses
|
||||
* Deprecate using the `~` binary operator in an expression with `+` or `-` without using parentheses to clarify precedence
|
||||
* Deprecate not passing `AbstractExpression` args to most constructor arguments for classes extending `AbstractExpression`
|
||||
* Fix `power` expressions with a negative number in parenthesis (`(-1) ** 2`)
|
||||
* Deprecate instantiating `Node` directly. Use `EmptyNode` or `Nodes` instead.
|
||||
* Add support for inline comments
|
||||
* Add `Profile::getStartTime()` and `Profile::getEndTime()`
|
||||
* Fix "ignore missing" when used on an "embed" tag
|
||||
* Fix the possibility to override an aliased block (via use)
|
||||
* Add template cache hot reload
|
||||
* Allow Twig callable argument names to be free-form (snake-case or camelCase) independently of the PHP callable signature
|
||||
They were automatically converted to snake-cased before
|
||||
* Deprecate the `attribute` function; use the `.` notation and wrap the name with parenthesis instead
|
||||
* Add support for argument unpackaging
|
||||
* Add JSON support for the file extension escaping strategy
|
||||
* Support Markup instances (and any other \Stringable) as dynamic mapping keys
|
||||
* Deprecate the `sandbox` tag
|
||||
* Improve the way one can deprecate a Twig callable (use `deprecation_info` instead of the other callable options)
|
||||
* Add the `enum` function
|
||||
* Add support for logical `xor` operator
|
||||
|
||||
# 3.14.2 (2024-11-07)
|
||||
|
||||
* Fix an infinite recursion in the sandbox code
|
||||
|
||||
# 3.14.1 (2024-11-06)
|
||||
|
||||
* [BC BREAK] Fix a security issue in the sandbox mode allowing an attacker to call attributes on Array-like objects
|
||||
They are now checked via the property policy
|
||||
* Fix a security issue in the sandbox mode allowing an attacker to be able to call `toString()`
|
||||
under some circumstances on an object even if the `__toString()` method is not allowed by the security policy
|
||||
|
||||
# 3.14.0 (2024-09-09)
|
||||
|
||||
* Fix a security issue when an included sandboxed template has been loaded before without the sandbox context
|
||||
* Add the possibility to reset globals via `Environment::resetGlobals()`
|
||||
* Deprecate `Environment::mergeGlobals()`
|
||||
|
||||
# 3.13.0 (2024-09-07)
|
||||
|
||||
* Add the `types` tag (experimental)
|
||||
* Deprecate the `Twig\Test\NodeTestCase::getTests()` data provider, override `provideTests()` instead.
|
||||
* Mark `Twig\Test\NodeTestCase::getEnvironment()` as final, override `createEnvironment()` instead.
|
||||
* Deprecate `Twig\Test\NodeTestCase::getVariableGetter()`, call `createVariableGetter()` instead.
|
||||
* Deprecate `Twig\Test\NodeTestCase::getAttributeGetter()`, call `createAttributeGetter()` instead.
|
||||
* Deprecate not overriding `Twig\Test\IntegrationTestCase::getFixturesDirectory()`, this method will be abstract in 4.0
|
||||
* Marked `Twig\Test\IntegrationTestCase::getTests()` and `getLegacyTests()` as final
|
||||
|
||||
# 3.12.0 (2024-08-29)
|
||||
|
||||
* Deprecate the fact that the `extends` and `use` tags are always allowed in a sandboxed template.
|
||||
This behavior will change in 4.0 where these tags will need to be explicitly allowed like any other tag.
|
||||
* Deprecate the "tag" constructor argument of the "Twig\Node\Node" class as the tag is now automatically set by the Parser when needed
|
||||
* Fix precedence of two-word tests when the first word is a valid test
|
||||
* Deprecate the `spaceless` filter
|
||||
* Deprecate some internal methods from `Parser`: `getBlockStack()`, `hasBlock()`, `getBlock()`, `hasMacro()`, `hasTraits()`, `getParent()`
|
||||
* Deprecate passing `null` to `Twig\Parser::setParent()`
|
||||
* Update `Node::__toString()` to include the node tag if set
|
||||
* Add support for integers in methods of `Twig\Node\Node` that take a Node name
|
||||
* Deprecate not passing a `BodyNode` instance as the body of a `ModuleNode` or `MacroNode` constructor
|
||||
* Deprecate returning "null" from "TokenParserInterface::parse()".
|
||||
* Deprecate `OptimizerNodeVisitor::OPTIMIZE_TEXT_NODES`
|
||||
* Fix performance regression when `use_yield` is `false` (which is the default)
|
||||
* Improve compatibility when `use_yield` is `false` (as extensions still using `echo` will work as is)
|
||||
* Accept colons (`:`) in addition to equals (`=`) to separate argument names and values in named arguments
|
||||
* Add the `html_cva` function (in the HTML extra package)
|
||||
* Add support for named arguments to the `block` and `attribute` functions
|
||||
* Throw a SyntaxError exception at compile time when a Twig callable has not the minimum number of required arguments
|
||||
* Add a `CallableArgumentsExtractor` class
|
||||
* Deprecate passing a name to `FunctionExpression`, `FilterExpression`, and `TestExpression`;
|
||||
pass a `TwigFunction`, `TwigFilter`, or `TestFilter` instead
|
||||
* Deprecate all Twig callable attributes on `FunctionExpression`, `FilterExpression`, and `TestExpression`
|
||||
* Deprecate the `filter` node of `FilterExpression`
|
||||
* Add the notion of Twig callables (functions, filters, and tests)
|
||||
* Bump minimum PHP version to 8.0
|
||||
* Fix integration tests when a test has more than one data/expect section and deprecations
|
||||
* Add the `enum_cases` function
|
||||
|
||||
# 3.11.2 (2024-11-06)
|
||||
|
||||
* [BC BREAK] Fix a security issue in the sandbox mode allowing an attacker to call attributes on Array-like objects
|
||||
They are now checked via the property policy
|
||||
* Fix a security issue in the sandbox mode allowing an attacker to be able to call `toString()`
|
||||
under some circumstances on an object even if the `__toString()` method is not allowed by the security policy
|
||||
|
||||
# 3.11.1 (2024-09-10)
|
||||
|
||||
* Fix a security issue when an included sandboxed template has been loaded before without the sandbox context
|
||||
|
||||
# 3.11.0 (2024-08-08)
|
||||
|
||||
* Deprecate `OptimizerNodeVisitor::OPTIMIZE_RAW_FILTER`
|
||||
* Add `Twig\Cache\ChainCache` and `Twig\Cache\ReadOnlyFilesystemCache`
|
||||
* Add the possibility to deprecate attributes and nodes on `Node`
|
||||
* Add the possibility to add a package and a version to the `deprecated` tag
|
||||
* Add the possibility to add a package for filter/function/test deprecations
|
||||
* Mark `ConstantExpression` as being `@final`
|
||||
* Add the `find` filter
|
||||
* Fix optimizer mode validation in `OptimizerNodeVisitor`
|
||||
* Add the possibility to yield from a generator in `PrintNode`
|
||||
* Add the `shuffle` filter
|
||||
* Add the `singular` and `plural` filters in `StringExtension`
|
||||
* Deprecate the second argument of `Twig\Node\Expression\CallExpression::compileArguments()`
|
||||
* Deprecate `Twig\ExpressionParser\parseHashExpression()` in favor of
|
||||
`Twig\ExpressionParser::parseMappingExpression()`
|
||||
* Deprecate `Twig\ExpressionParser\parseArrayExpression()` in favor of
|
||||
`Twig\ExpressionParser::parseSequenceExpression()`
|
||||
* Add `sequence` and `mapping` tests
|
||||
* Deprecate `Twig\Node\Expression\NameExpression::isSimple()` and
|
||||
`Twig\Node\Expression\NameExpression::isSpecial()`
|
||||
|
||||
# 3.10.3 (2024-05-16)
|
||||
|
||||
* Fix missing ; in generated code
|
||||
|
||||
# 3.10.2 (2024-05-14)
|
||||
|
||||
* Fix support for the deprecated escaper signature
|
||||
|
||||
# 3.10.1 (2024-05-12)
|
||||
|
||||
* Fix BC break on escaper extension
|
||||
* Fix constant return type
|
||||
|
||||
# 3.10.0 (2024-05-11)
|
||||
|
||||
* Make `CoreExtension::formatDate`, `CoreExtension::convertDate`, and
|
||||
`CoreExtension::formatNumber` part of the public API
|
||||
* Add `needs_charset` option for filters and functions
|
||||
* Extract the escaping logic from the `EscaperExtension` class to a new
|
||||
`EscaperRuntime` class.
|
||||
|
||||
The following methods from ``Twig\\Extension\\EscaperExtension`` are
|
||||
deprecated: ``setEscaper()``, ``getEscapers()``, ``setSafeClasses``,
|
||||
``addSafeClasses()``. Use the same methods on the
|
||||
``Twig\\Runtime\\EscaperRuntime`` class instead.
|
||||
* Fix capturing output from extensions that still use echo
|
||||
* Fix a PHP warning in the Lexer on malformed templates
|
||||
* Fix blocks not available under some circumstances
|
||||
* Synchronize source context in templates when setting a Node on a Node
|
||||
|
||||
# 3.9.3 (2024-04-18)
|
||||
|
||||
* Add missing `twig_escape_filter_is_safe` deprecated function
|
||||
* Fix yield usage with CaptureNode
|
||||
* Add missing unwrap call when using a TemplateWrapper instance internally
|
||||
* Ensure Lexer is initialized early on
|
||||
|
||||
# 3.9.2 (2024-04-17)
|
||||
|
||||
* Fix usage of display_end hook
|
||||
|
||||
# 3.9.1 (2024-04-17)
|
||||
|
||||
* Fix missing `$blocks` variable in `CaptureNode`
|
||||
|
||||
# 3.9.0 (2024-04-16)
|
||||
|
||||
* Add support for PHP 8.4
|
||||
* Deprecate AbstractNodeVisitor
|
||||
* Deprecate passing Template to Environment::resolveTemplate(), Environment::load(), and Template::loadTemplate()
|
||||
* Add a new "yield" mode for output generation;
|
||||
Node implementations that use "echo" or "print" should use "yield" instead;
|
||||
all Node implementations should be flagged with `#[YieldReady]` once they've been made ready for "yield";
|
||||
the "use_yield" Environment option can be turned on when all nodes have been made `#[YieldReady]`;
|
||||
"yield" will be the only strategy supported in the next major version
|
||||
* Add return type for Symfony 7 compatibility
|
||||
* Fix premature loop exit in Security Policy lookup of allowed methods/properties
|
||||
* Deprecate all internal extension functions in favor of methods on the extension classes
|
||||
* Mark all extension functions as @internal
|
||||
* Add SourcePolicyInterface to selectively enable the Sandbox based on a template's Source
|
||||
* Throw a proper Twig exception when using cycle on an empty array
|
||||
|
||||
# 3.8.0 (2023-11-21)
|
||||
|
||||
* Catch errors thrown during template rendering
|
||||
@@ -186,7 +385,7 @@
|
||||
* removed Parser::isReservedMacroName()
|
||||
* removed SanboxedPrintNode
|
||||
* removed Node::setTemplateName()
|
||||
* made classes maked as "@final" final
|
||||
* made classes marked as "@final" final
|
||||
* removed InitRuntimeInterface, ExistsLoaderInterface, and SourceContextLoaderInterface
|
||||
* removed the "spaceless" tag
|
||||
* removed Twig\Environment::getBaseTemplateClass() and Twig\Environment::setBaseTemplateClass()
|
||||
|
||||
@@ -11,7 +11,7 @@ Sponsors
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<a href="https://blackfire.io/docs/introduction?utm_source=twig&utm_medium=github_readme&utm_campaign=logo">
|
||||
<a href="https://docs.blackfire.io/introduction?utm_source=twig&utm_medium=github_readme&utm_campaign=logo">
|
||||
<img src="https://static.blackfire.io/assets/intemporals/logo/png/blackfire-io_secondary_horizontal_transparent.png?1" width="255px" alt="Blackfire.io">
|
||||
</a>
|
||||
|
||||
|
||||
@@ -24,16 +24,24 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/polyfill-php80": "^1.22",
|
||||
"php": ">=8.0.2",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/polyfill-mbstring": "^1.3",
|
||||
"symfony/polyfill-ctype": "^1.8"
|
||||
"symfony/polyfill-ctype": "^1.8",
|
||||
"symfony/polyfill-php81": "^1.29"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0",
|
||||
"psr/container": "^1.0|^2.0"
|
||||
"symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0",
|
||||
"psr/container": "^1.0|^2.0",
|
||||
"phpstan/phpstan": "^2.0"
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/Resources/core.php",
|
||||
"src/Resources/debug.php",
|
||||
"src/Resources/escaper.php",
|
||||
"src/Resources/string_loader.php"
|
||||
],
|
||||
"psr-4" : {
|
||||
"Twig\\" : "src/"
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Twig\Cache;
|
||||
*
|
||||
* @author Andrew Tch <andrew@noop.lv>
|
||||
*/
|
||||
class FilesystemCache implements CacheInterface
|
||||
class FilesystemCache implements CacheInterface, RemovableCacheInterface
|
||||
{
|
||||
public const FORCE_BYTECODE_INVALIDATION = 1;
|
||||
|
||||
@@ -50,11 +50,11 @@ class FilesystemCache implements CacheInterface
|
||||
if (false === @mkdir($dir, 0777, true)) {
|
||||
clearstatcache(true, $dir);
|
||||
if (!is_dir($dir)) {
|
||||
throw new \RuntimeException(sprintf('Unable to create the cache directory (%s).', $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));
|
||||
throw new \RuntimeException(\sprintf('Unable to write in the cache directory (%s).', $dir));
|
||||
}
|
||||
|
||||
$tmpFile = tempnam($dir, basename($key));
|
||||
@@ -73,7 +73,15 @@ class FilesystemCache implements CacheInterface
|
||||
return;
|
||||
}
|
||||
|
||||
throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $key));
|
||||
throw new \RuntimeException(\sprintf('Failed to write cache file "%s".', $key));
|
||||
}
|
||||
|
||||
public function remove(string $name, string $cls): void
|
||||
{
|
||||
$key = $this->generateKey($name, $cls);
|
||||
if (!@unlink($key) && file_exists($key)) {
|
||||
throw new \RuntimeException(\sprintf('Failed to delete cache file "%s".', $key));
|
||||
}
|
||||
}
|
||||
|
||||
public function getTimestamp(string $key): int
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Twig\Cache;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class NullCache implements CacheInterface
|
||||
final class NullCache implements CacheInterface, RemovableCacheInterface
|
||||
{
|
||||
public function generateKey(string $name, string $className): string
|
||||
{
|
||||
@@ -35,4 +35,8 @@ final class NullCache implements CacheInterface
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function remove(string $name, string $cls): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,15 +22,16 @@ class Compiler
|
||||
private $lastLine;
|
||||
private $source;
|
||||
private $indentation;
|
||||
private $env;
|
||||
private $debugInfo = [];
|
||||
private $sourceOffset;
|
||||
private $sourceLine;
|
||||
private $varNameSalt = 0;
|
||||
private $didUseEcho = false;
|
||||
private $didUseEchoStack = [];
|
||||
|
||||
public function __construct(Environment $env)
|
||||
{
|
||||
$this->env = $env;
|
||||
public function __construct(
|
||||
private Environment $env,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getEnvironment(): Environment
|
||||
@@ -66,9 +67,20 @@ class Compiler
|
||||
public function compile(Node $node, int $indentation = 0)
|
||||
{
|
||||
$this->reset($indentation);
|
||||
$node->compile($this);
|
||||
$this->didUseEchoStack[] = $this->didUseEcho;
|
||||
|
||||
return $this;
|
||||
try {
|
||||
$this->didUseEcho = false;
|
||||
$node->compile($this);
|
||||
|
||||
if ($this->didUseEcho) {
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[\Twig\Attribute\YieldReady].', $this->didUseEcho, \get_class($node));
|
||||
}
|
||||
|
||||
return $this;
|
||||
} finally {
|
||||
$this->didUseEcho = array_pop($this->didUseEchoStack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,13 +88,24 @@ class Compiler
|
||||
*/
|
||||
public function subcompile(Node $node, bool $raw = true)
|
||||
{
|
||||
if (false === $raw) {
|
||||
if (!$raw) {
|
||||
$this->source .= str_repeat(' ', $this->indentation * 4);
|
||||
}
|
||||
|
||||
$node->compile($this);
|
||||
$this->didUseEchoStack[] = $this->didUseEcho;
|
||||
|
||||
return $this;
|
||||
try {
|
||||
$this->didUseEcho = false;
|
||||
$node->compile($this);
|
||||
|
||||
if ($this->didUseEcho) {
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using "%s" is deprecated, use "yield" instead in "%s", then flag the class with #[\Twig\Attribute\YieldReady].', $this->didUseEcho, \get_class($node));
|
||||
}
|
||||
|
||||
return $this;
|
||||
} finally {
|
||||
$this->didUseEcho = array_pop($this->didUseEchoStack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,6 +115,7 @@ class Compiler
|
||||
*/
|
||||
public function raw(string $string)
|
||||
{
|
||||
$this->checkForEcho($string);
|
||||
$this->source .= $string;
|
||||
|
||||
return $this;
|
||||
@@ -105,6 +129,7 @@ class Compiler
|
||||
public function write(...$strings)
|
||||
{
|
||||
foreach ($strings as $string) {
|
||||
$this->checkForEcho($string);
|
||||
$this->source .= str_repeat(' ', $this->indentation * 4).$string;
|
||||
}
|
||||
|
||||
@@ -118,7 +143,7 @@ class Compiler
|
||||
*/
|
||||
public function string(string $value)
|
||||
{
|
||||
$this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\"));
|
||||
$this->source .= \sprintf('"%s"', addcslashes($value, "\0\t\"\$\\"));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -170,7 +195,7 @@ class Compiler
|
||||
public function addDebugInfo(Node $node)
|
||||
{
|
||||
if ($node->getTemplateLine() != $this->lastLine) {
|
||||
$this->write(sprintf("// line %d\n", $node->getTemplateLine()));
|
||||
$this->write(\sprintf("// line %d\n", $node->getTemplateLine()));
|
||||
|
||||
$this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset);
|
||||
$this->sourceOffset = \strlen($this->source);
|
||||
@@ -218,6 +243,15 @@ class Compiler
|
||||
|
||||
public function getVarName(): string
|
||||
{
|
||||
return sprintf('__internal_compile_%d', $this->varNameSalt++);
|
||||
return \sprintf('_v%d', $this->varNameSalt++);
|
||||
}
|
||||
|
||||
private function checkForEcho(string $string): void
|
||||
{
|
||||
if ($this->didUseEcho) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->didUseEcho = preg_match('/^\s*+(echo|print)\b/', $string, $m) ? $m[1] : false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Twig;
|
||||
use Twig\Cache\CacheInterface;
|
||||
use Twig\Cache\FilesystemCache;
|
||||
use Twig\Cache\NullCache;
|
||||
use Twig\Cache\RemovableCacheInterface;
|
||||
use Twig\Error\Error;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
@@ -22,6 +23,7 @@ use Twig\Extension\CoreExtension;
|
||||
use Twig\Extension\EscaperExtension;
|
||||
use Twig\Extension\ExtensionInterface;
|
||||
use Twig\Extension\OptimizerExtension;
|
||||
use Twig\Extension\YieldNotReadyExtension;
|
||||
use Twig\Loader\ArrayLoader;
|
||||
use Twig\Loader\ChainLoader;
|
||||
use Twig\Loader\LoaderInterface;
|
||||
@@ -30,6 +32,8 @@ use Twig\Node\Expression\Unary\AbstractUnary;
|
||||
use Twig\Node\ModuleNode;
|
||||
use Twig\Node\Node;
|
||||
use Twig\NodeVisitor\NodeVisitorInterface;
|
||||
use Twig\Runtime\EscaperRuntime;
|
||||
use Twig\RuntimeLoader\FactoryRuntimeLoader;
|
||||
use Twig\RuntimeLoader\RuntimeLoaderInterface;
|
||||
use Twig\TokenParser\TokenParserInterface;
|
||||
|
||||
@@ -40,10 +44,10 @@ use Twig\TokenParser\TokenParserInterface;
|
||||
*/
|
||||
class Environment
|
||||
{
|
||||
public const VERSION = '3.8.0';
|
||||
public const VERSION_ID = 30800;
|
||||
public const VERSION = '3.16.0';
|
||||
public const VERSION_ID = 31600;
|
||||
public const MAJOR_VERSION = 3;
|
||||
public const MINOR_VERSION = 8;
|
||||
public const MINOR_VERSION = 16;
|
||||
public const RELEASE_VERSION = 0;
|
||||
public const EXTRA_VERSION = '';
|
||||
|
||||
@@ -60,12 +64,15 @@ class Environment
|
||||
private $resolvedGlobals;
|
||||
private $loadedTemplates;
|
||||
private $strictVariables;
|
||||
private $templateClassPrefix = '__TwigTemplate_';
|
||||
private $originalCache;
|
||||
private $extensionSet;
|
||||
private $runtimeLoaders = [];
|
||||
private $runtimes = [];
|
||||
private $optionsHash;
|
||||
/** @var bool */
|
||||
private $useYield;
|
||||
private $defaultRuntimeLoader;
|
||||
private array $hotCache = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -97,8 +104,12 @@ class Environment
|
||||
* * 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).
|
||||
*
|
||||
* * use_yield: true: forces templates to exclusively use "yield" instead of "echo" (all extensions must be yield ready)
|
||||
* false (default): allows templates to use a mix of "yield" and "echo" calls to allow for a progressive migration
|
||||
* Switch to "true" when possible as this will be the only supported mode in Twig 4.0
|
||||
*/
|
||||
public function __construct(LoaderInterface $loader, $options = [])
|
||||
public function __construct(LoaderInterface $loader, array $options = [])
|
||||
{
|
||||
$this->setLoader($loader);
|
||||
|
||||
@@ -110,20 +121,38 @@ class Environment
|
||||
'cache' => false,
|
||||
'auto_reload' => null,
|
||||
'optimizations' => -1,
|
||||
'use_yield' => false,
|
||||
], $options);
|
||||
|
||||
$this->useYield = (bool) $options['use_yield'];
|
||||
$this->debug = (bool) $options['debug'];
|
||||
$this->setCharset($options['charset'] ?? 'UTF-8');
|
||||
$this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
|
||||
$this->strictVariables = (bool) $options['strict_variables'];
|
||||
$this->setCache($options['cache']);
|
||||
$this->extensionSet = new ExtensionSet();
|
||||
$this->defaultRuntimeLoader = new FactoryRuntimeLoader([
|
||||
EscaperRuntime::class => function () { return new EscaperRuntime($this->charset); },
|
||||
]);
|
||||
|
||||
$this->addExtension(new CoreExtension());
|
||||
$this->addExtension(new EscaperExtension($options['autoescape']));
|
||||
$escaperExt = new EscaperExtension($options['autoescape']);
|
||||
$escaperExt->setEnvironment($this, false);
|
||||
$this->addExtension($escaperExt);
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
$this->addExtension(new YieldNotReadyExtension($this->useYield));
|
||||
}
|
||||
$this->addExtension(new OptimizerExtension($options['optimizations']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function useYield(): bool
|
||||
{
|
||||
return $this->useYield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables debugging mode.
|
||||
*/
|
||||
@@ -206,6 +235,18 @@ class Environment
|
||||
return $this->strictVariables;
|
||||
}
|
||||
|
||||
public function removeCache(string $name): void
|
||||
{
|
||||
$cls = $this->getTemplateClass($name);
|
||||
$this->hotCache[$name] = $cls.'_'.bin2hex(random_bytes(16));
|
||||
|
||||
if ($this->cache instanceof RemovableCacheInterface) {
|
||||
$this->cache->remove($name, $cls);
|
||||
} else {
|
||||
throw new \LogicException(\sprintf('The "%s" cache class does not support removing template cache as it does not implement the "RemovableCacheInterface" interface.', \get_class($this->cache)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current cache implementation.
|
||||
*
|
||||
@@ -249,7 +290,6 @@ class Environment
|
||||
*
|
||||
* * 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.
|
||||
@@ -259,11 +299,11 @@ class Environment
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getTemplateClass(string $name, int $index = null): string
|
||||
public function getTemplateClass(string $name, ?int $index = null): string
|
||||
{
|
||||
$key = $this->getLoader()->getCacheKey($name).$this->optionsHash;
|
||||
$key = ($this->hotCache[$name] ?? $this->getLoader()->getCacheKey($name)).$this->optionsHash;
|
||||
|
||||
return $this->templateClassPrefix.hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $key).(null === $index ? '' : '___'.$index);
|
||||
return '__TwigTemplate_'.hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $key).(null === $index ? '' : '___'.$index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -308,6 +348,11 @@ class Environment
|
||||
if ($name instanceof TemplateWrapper) {
|
||||
return $name;
|
||||
}
|
||||
if ($name instanceof Template) {
|
||||
trigger_deprecation('twig/twig', '3.9', 'Passing a "%s" instance to "%s" is deprecated.', self::class, __METHOD__);
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name));
|
||||
}
|
||||
@@ -318,8 +363,8 @@ class Environment
|
||||
* 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
|
||||
* @param string $name The template name
|
||||
* @param int|null $index The index if it is an embedded template
|
||||
*
|
||||
* @throws LoaderError When the template cannot be found
|
||||
* @throws RuntimeError When a previously generated cache is corrupted
|
||||
@@ -327,7 +372,7 @@ class Environment
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function loadTemplate(string $cls, string $name, int $index = null): Template
|
||||
public function loadTemplate(string $cls, string $name, ?int $index = null): Template
|
||||
{
|
||||
$mainCls = $cls;
|
||||
if (null !== $index) {
|
||||
@@ -348,8 +393,10 @@ class Environment
|
||||
if (!class_exists($cls, false)) {
|
||||
$source = $this->getLoader()->getSourceContext($name);
|
||||
$content = $this->compileSource($source);
|
||||
$this->cache->write($key, $content);
|
||||
$this->cache->load($key);
|
||||
if (!isset($this->hotCache[$name])) {
|
||||
$this->cache->write($key, $content);
|
||||
$this->cache->load($key);
|
||||
}
|
||||
|
||||
if (!class_exists($mainCls, false)) {
|
||||
/* Last line of defense if either $this->bcWriteCacheFile was used,
|
||||
@@ -361,7 +408,7 @@ class Environment
|
||||
}
|
||||
|
||||
if (!class_exists($cls, false)) {
|
||||
throw new RuntimeError(sprintf('Failed to load Twig template "%s", index "%s": cache might be corrupted.', $name, $index), -1, $source);
|
||||
throw new RuntimeError(\sprintf('Failed to load Twig template "%s", index "%s": cache might be corrupted.', $name, $index), -1, $source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,19 +423,19 @@ class Environment
|
||||
*
|
||||
* This method should not be used as a generic way to load templates.
|
||||
*
|
||||
* @param string $template The template source
|
||||
* @param string $name An optional name of the template to be used in error messages
|
||||
* @param string $template The template source
|
||||
* @param string|null $name An optional name of the template to be used in error messages
|
||||
*
|
||||
* @throws LoaderError When the template cannot be found
|
||||
* @throws SyntaxError When an error occurred during compilation
|
||||
*/
|
||||
public function createTemplate(string $template, string $name = null): TemplateWrapper
|
||||
public function createTemplate(string $template, ?string $name = null): TemplateWrapper
|
||||
{
|
||||
$hash = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $template, false);
|
||||
if (null !== $name) {
|
||||
$name = sprintf('%s (string template %s)', $name, $hash);
|
||||
$name = \sprintf('%s (string template %s)', $name, $hash);
|
||||
} else {
|
||||
$name = sprintf('__string_template__%s', $hash);
|
||||
$name = \sprintf('__string_template__%s', $hash);
|
||||
}
|
||||
|
||||
$loader = new ChainLoader([
|
||||
@@ -421,10 +468,10 @@ class Environment
|
||||
/**
|
||||
* Tries to load a template consecutively from an array.
|
||||
*
|
||||
* Similar to load() but it also accepts instances of \Twig\Template and
|
||||
* \Twig\TemplateWrapper, and an array of templates where each is tried to be loaded.
|
||||
* Similar to load() but it also accepts instances of \Twig\TemplateWrapper
|
||||
* and an array of templates where each is tried to be loaded.
|
||||
*
|
||||
* @param string|TemplateWrapper|array $names A template or an array of templates to try consecutively
|
||||
* @param string|TemplateWrapper|array<string|TemplateWrapper> $names A template or an array of templates to try consecutively
|
||||
*
|
||||
* @throws LoaderError When none of the templates can be found
|
||||
* @throws SyntaxError When an error occurred during compilation
|
||||
@@ -438,7 +485,9 @@ class Environment
|
||||
$count = \count($names);
|
||||
foreach ($names as $name) {
|
||||
if ($name instanceof Template) {
|
||||
return $name;
|
||||
trigger_deprecation('twig/twig', '3.9', 'Passing a "%s" instance to "%s" is deprecated.', Template::class, __METHOD__);
|
||||
|
||||
return new TemplateWrapper($this, $name);
|
||||
}
|
||||
if ($name instanceof TemplateWrapper) {
|
||||
return $name;
|
||||
@@ -451,7 +500,7 @@ class Environment
|
||||
return $this->load($name);
|
||||
}
|
||||
|
||||
throw new LoaderError(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
|
||||
throw new LoaderError(\sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
|
||||
}
|
||||
|
||||
public function setLexer(Lexer $lexer)
|
||||
@@ -520,7 +569,7 @@ class Environment
|
||||
$e->setSourceContext($source);
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
throw new SyntaxError(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e);
|
||||
throw new SyntaxError(\sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,7 +585,7 @@ class Environment
|
||||
|
||||
public function setCharset(string $charset)
|
||||
{
|
||||
if ('UTF8' === $charset = null === $charset ? null : strtoupper($charset)) {
|
||||
if ('UTF8' === $charset = strtoupper($charset ?: '')) {
|
||||
// iconv on Windows requires "UTF-8" instead of "UTF8"
|
||||
$charset = 'UTF-8';
|
||||
}
|
||||
@@ -594,7 +643,11 @@ class Environment
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeError(sprintf('Unable to load the "%s" runtime.', $class));
|
||||
if (null !== $runtime = $this->defaultRuntimeLoader->load($class)) {
|
||||
return $this->runtimes[$class] = $runtime;
|
||||
}
|
||||
|
||||
throw new RuntimeError(\sprintf('Unable to load the "%s" runtime.', $class));
|
||||
}
|
||||
|
||||
public function addExtension(ExtensionInterface $extension)
|
||||
@@ -765,7 +818,7 @@ class Environment
|
||||
public function addGlobal(string $name, $value)
|
||||
{
|
||||
if ($this->extensionSet->isInitialized() && !\array_key_exists($name, $this->getGlobals())) {
|
||||
throw new \LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
|
||||
throw new \LogicException(\sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
|
||||
}
|
||||
|
||||
if (null !== $this->resolvedGlobals) {
|
||||
@@ -776,8 +829,6 @@ class Environment
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getGlobals(): array
|
||||
@@ -793,17 +844,20 @@ class Environment
|
||||
return array_merge($this->extensionSet->getGlobals(), $this->globals);
|
||||
}
|
||||
|
||||
public function resetGlobals(): void
|
||||
{
|
||||
$this->resolvedGlobals = null;
|
||||
$this->extensionSet->resetGlobals();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.14
|
||||
*/
|
||||
public function mergeGlobals(array $context): array
|
||||
{
|
||||
// we don't use array_merge as the context being generally
|
||||
// bigger than globals, this code is faster.
|
||||
foreach ($this->getGlobals() as $key => $value) {
|
||||
if (!\array_key_exists($key, $context)) {
|
||||
$context[$key] = $value;
|
||||
}
|
||||
}
|
||||
trigger_deprecation('twig/twig', '3.14', 'The "%s" method is deprecated.', __METHOD__);
|
||||
|
||||
return $context;
|
||||
return $context + $this->getGlobals();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -835,6 +889,7 @@ class Environment
|
||||
self::VERSION,
|
||||
(int) $this->debug,
|
||||
(int) $this->strictVariables,
|
||||
$this->useYield ? '1' : '0',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class Error extends \Exception
|
||||
* @param int $lineno The template line where the error occurred
|
||||
* @param Source|null $source The source context where the error occurred
|
||||
*/
|
||||
public function __construct(string $message, int $lineno = -1, Source $source = null, \Throwable $previous = null)
|
||||
public function __construct(string $message, int $lineno = -1, ?Source $source = null, ?\Throwable $previous = null)
|
||||
{
|
||||
parent::__construct('', 0, $previous);
|
||||
|
||||
@@ -93,7 +93,7 @@ class Error extends \Exception
|
||||
return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null;
|
||||
}
|
||||
|
||||
public function setSourceContext(Source $source = null): void
|
||||
public function setSourceContext(?Source $source = null): void
|
||||
{
|
||||
if (null === $source) {
|
||||
$this->sourceCode = $this->name = $this->sourcePath = null;
|
||||
@@ -142,16 +142,16 @@ class Error extends \Exception
|
||||
}
|
||||
|
||||
if ($this->name) {
|
||||
if (\is_string($this->name) || (\is_object($this->name) && method_exists($this->name, '__toString'))) {
|
||||
$name = sprintf('"%s"', $this->name);
|
||||
if (\is_string($this->name) || $this->name instanceof \Stringable) {
|
||||
$name = \sprintf('"%s"', $this->name);
|
||||
} else {
|
||||
$name = json_encode($this->name);
|
||||
}
|
||||
$this->message .= sprintf(' in %s', $name);
|
||||
$this->message .= \sprintf(' in %s', $name);
|
||||
}
|
||||
|
||||
if ($this->lineno && $this->lineno >= 0) {
|
||||
$this->message .= sprintf(' at line %d', $this->lineno);
|
||||
$this->message .= \sprintf(' at line %d', $this->lineno);
|
||||
}
|
||||
|
||||
if ($dot) {
|
||||
|
||||
@@ -41,6 +41,6 @@ class SyntaxError extends Error
|
||||
|
||||
asort($alternatives);
|
||||
|
||||
$this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', array_keys($alternatives))));
|
||||
$this->appendMessage(\sprintf(' Did you mean "%s"?', implode('", "', array_keys($alternatives))));
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,6 @@ abstract class AbstractExtension implements ExtensionInterface
|
||||
|
||||
public function getOperators()
|
||||
{
|
||||
return [];
|
||||
return [[], []];
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,11 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension {
|
||||
namespace Twig\Extension;
|
||||
|
||||
use Twig\Environment;
|
||||
use Twig\Template;
|
||||
use Twig\TemplateWrapper;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
final class DebugExtension extends AbstractExtension
|
||||
@@ -18,47 +22,41 @@ final class DebugExtension extends AbstractExtension
|
||||
{
|
||||
// dump is safe if var_dump is overridden by xdebug
|
||||
$isDumpOutputHtmlSafe = \extension_loaded('xdebug')
|
||||
// false means that it was not set (and the default is on) or it explicitly enabled
|
||||
&& (false === \ini_get('xdebug.overload_var_dump') || \ini_get('xdebug.overload_var_dump'))
|
||||
// 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
|
||||
// Xdebug overloads var_dump in develop mode when html_errors is enabled
|
||||
&& str_contains(\ini_get('xdebug.mode'), 'develop')
|
||||
&& (false === \ini_get('html_errors') || \ini_get('html_errors'))
|
||||
|| 'cli' === \PHP_SAPI
|
||||
;
|
||||
|
||||
return [
|
||||
new TwigFunction('dump', 'twig_var_dump', ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]),
|
||||
new TwigFunction('dump', [self::class, 'dump'], ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
use Twig\Environment;
|
||||
use Twig\Template;
|
||||
use Twig\TemplateWrapper;
|
||||
|
||||
function twig_var_dump(Environment $env, $context, ...$vars)
|
||||
{
|
||||
if (!$env->isDebug()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
if (!$vars) {
|
||||
$vars = [];
|
||||
foreach ($context as $key => $value) {
|
||||
if (!$value instanceof Template && !$value instanceof TemplateWrapper) {
|
||||
$vars[$key] = $value;
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function dump(Environment $env, $context, ...$vars)
|
||||
{
|
||||
if (!$env->isDebug()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var_dump($vars);
|
||||
} else {
|
||||
var_dump(...$vars);
|
||||
}
|
||||
ob_start();
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
if (!$vars) {
|
||||
$vars = [];
|
||||
foreach ($context as $key => $value) {
|
||||
if (!$value instanceof Template && !$value instanceof TemplateWrapper) {
|
||||
$vars[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
var_dump($vars);
|
||||
} else {
|
||||
var_dump(...$vars);
|
||||
}
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,22 +9,24 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension {
|
||||
namespace Twig\Extension;
|
||||
|
||||
use Twig\Environment;
|
||||
use Twig\FileExtensionEscapingStrategy;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\Filter\RawFilter;
|
||||
use Twig\Node\Node;
|
||||
use Twig\NodeVisitor\EscaperNodeVisitor;
|
||||
use Twig\Runtime\EscaperRuntime;
|
||||
use Twig\TokenParser\AutoEscapeTokenParser;
|
||||
use Twig\TwigFilter;
|
||||
|
||||
final class EscaperExtension extends AbstractExtension
|
||||
{
|
||||
private $defaultStrategy;
|
||||
private $environment;
|
||||
private $escapers = [];
|
||||
|
||||
/** @internal */
|
||||
public $safeClasses = [];
|
||||
|
||||
/** @internal */
|
||||
public $safeLookup = [];
|
||||
private $escaper;
|
||||
private $defaultStrategy;
|
||||
|
||||
/**
|
||||
* @param string|false|callable $defaultStrategy An escaping strategy
|
||||
@@ -49,19 +51,43 @@ final class EscaperExtension extends AbstractExtension
|
||||
public function getFilters(): array
|
||||
{
|
||||
return [
|
||||
new TwigFilter('escape', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
|
||||
new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
|
||||
new TwigFilter('raw', 'twig_raw_filter', ['is_safe' => ['all']]),
|
||||
new TwigFilter('escape', [EscaperRuntime::class, 'escape'], ['is_safe_callback' => [self::class, 'escapeFilterIsSafe']]),
|
||||
new TwigFilter('e', [EscaperRuntime::class, 'escape'], ['is_safe_callback' => [self::class, 'escapeFilterIsSafe']]),
|
||||
new TwigFilter('raw', null, ['is_safe' => ['all'], 'node_class' => RawFilter::class]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.10
|
||||
*/
|
||||
public function setEnvironment(Environment $environment): void
|
||||
{
|
||||
$triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true;
|
||||
if ($triggerDeprecation) {
|
||||
trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated and not needed if you are using methods from "Twig\Runtime\EscaperRuntime".', __METHOD__);
|
||||
}
|
||||
|
||||
$this->environment = $environment;
|
||||
$this->escaper = $environment->getRuntime(EscaperRuntime::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.10
|
||||
*/
|
||||
public function setEscaperRuntime(EscaperRuntime $escaper)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated and not needed if you are using methods from "Twig\Runtime\EscaperRuntime".', __METHOD__);
|
||||
|
||||
$this->escaper = $escaper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default strategy to use when not defined by the user.
|
||||
*
|
||||
* The strategy can be a valid PHP callback that takes the template
|
||||
* name as an argument and returns the strategy to use.
|
||||
*
|
||||
* @param string|false|callable $defaultStrategy An escaping strategy
|
||||
* @param string|false|callable(string $templateName): string $defaultStrategy An escaping strategy
|
||||
*/
|
||||
public function setDefaultStrategy($defaultStrategy): void
|
||||
{
|
||||
@@ -93,324 +119,82 @@ final class EscaperExtension extends AbstractExtension
|
||||
/**
|
||||
* 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
|
||||
* @param string $strategy The strategy name that should be used as a strategy in the escape call
|
||||
* @param callable(Environment, string, string): string $callable A valid PHP callable
|
||||
*
|
||||
* @deprecated since Twig 3.10
|
||||
*/
|
||||
public function setEscaper($strategy, callable $callable)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::setEscaper()" method instead (be warned that Environment is not passed anymore to the callable).', __METHOD__);
|
||||
|
||||
if (!isset($this->environment)) {
|
||||
throw new \LogicException(\sprintf('You must call "setEnvironment()" before calling "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
$this->escapers[$strategy] = $callable;
|
||||
$callable = function ($string, $charset) use ($callable) {
|
||||
return $callable($this->environment, $string, $charset);
|
||||
};
|
||||
|
||||
$this->escaper->setEscaper($strategy, $callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all defined escapers.
|
||||
*
|
||||
* @return callable[] An array of escapers
|
||||
* @return array<string, callable(Environment, string, string): string> An array of escapers
|
||||
*
|
||||
* @deprecated since Twig 3.10
|
||||
*/
|
||||
public function getEscapers()
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::getEscaper()" method instead.', __METHOD__);
|
||||
|
||||
return $this->escapers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.10
|
||||
*/
|
||||
public function setSafeClasses(array $safeClasses = [])
|
||||
{
|
||||
$this->safeClasses = [];
|
||||
$this->safeLookup = [];
|
||||
foreach ($safeClasses as $class => $strategies) {
|
||||
$this->addSafeClass($class, $strategies);
|
||||
trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::setSafeClasses()" method instead.', __METHOD__);
|
||||
|
||||
if (!isset($this->escaper)) {
|
||||
throw new \LogicException(\sprintf('You must call "setEnvironment()" before calling "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
$this->escaper->setSafeClasses($safeClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.10
|
||||
*/
|
||||
public function addSafeClass(string $class, array $strategies)
|
||||
{
|
||||
$class = ltrim($class, '\\');
|
||||
if (!isset($this->safeClasses[$class])) {
|
||||
$this->safeClasses[$class] = [];
|
||||
}
|
||||
$this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies);
|
||||
trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\Runtime\EscaperRuntime::addSafeClass()" method instead.', __METHOD__);
|
||||
|
||||
foreach ($strategies as $strategy) {
|
||||
$this->safeLookup[$strategy][$class] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
use Twig\Environment;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Extension\EscaperExtension;
|
||||
use Twig\Markup;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* Marks a variable as being safe.
|
||||
*
|
||||
* @param string $string A PHP variable
|
||||
*/
|
||||
function twig_raw_filter($string)
|
||||
{
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string.
|
||||
*
|
||||
* @param mixed $string The value to be escaped
|
||||
* @param string $strategy The escaping strategy
|
||||
* @param string $charset The charset
|
||||
* @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
|
||||
{
|
||||
if ($autoescape && $string instanceof Markup) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
if (!\is_string($string)) {
|
||||
if (\is_object($string) && method_exists($string, '__toString')) {
|
||||
if ($autoescape) {
|
||||
$c = \get_class($string);
|
||||
$ext = $env->getExtension(EscaperExtension::class);
|
||||
if (!isset($ext->safeClasses[$c])) {
|
||||
$ext->safeClasses[$c] = [];
|
||||
foreach (class_parents($string) + class_implements($string) as $class) {
|
||||
if (isset($ext->safeClasses[$class])) {
|
||||
$ext->safeClasses[$c] = array_unique(array_merge($ext->safeClasses[$c], $ext->safeClasses[$class]));
|
||||
foreach ($ext->safeClasses[$class] as $s) {
|
||||
$ext->safeLookup[$s][$c] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($ext->safeLookup[$strategy][$c]) || isset($ext->safeLookup['all'][$c])) {
|
||||
return (string) $string;
|
||||
}
|
||||
}
|
||||
|
||||
$string = (string) $string;
|
||||
} elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) {
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
if ('' === $string) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (null === $charset) {
|
||||
$charset = $env->getCharset();
|
||||
}
|
||||
|
||||
switch ($strategy) {
|
||||
case 'html':
|
||||
// see https://www.php.net/htmlspecialchars
|
||||
|
||||
// Using a static variable to avoid initializing the array
|
||||
// each time the function is called. Moving the declaration on the
|
||||
// top of the function slow downs other escaping strategies.
|
||||
static $htmlspecialcharsCharsets = [
|
||||
'ISO-8859-1' => true, 'ISO8859-1' => true,
|
||||
'ISO-8859-15' => true, 'ISO8859-15' => true,
|
||||
'utf-8' => true, 'UTF-8' => true,
|
||||
'CP866' => true, 'IBM866' => true, '866' => true,
|
||||
'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true,
|
||||
'1251' => true,
|
||||
'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true,
|
||||
'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true,
|
||||
'BIG5' => true, '950' => true,
|
||||
'GB2312' => true, '936' => true,
|
||||
'BIG5-HKSCS' => true,
|
||||
'SHIFT_JIS' => true, 'SJIS' => true, '932' => true,
|
||||
'EUC-JP' => true, 'EUCJP' => true,
|
||||
'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true,
|
||||
];
|
||||
|
||||
if (isset($htmlspecialcharsCharsets[$charset])) {
|
||||
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
|
||||
}
|
||||
|
||||
if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) {
|
||||
// cache the lowercase variant for future iterations
|
||||
$htmlspecialcharsCharsets[$charset] = true;
|
||||
|
||||
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
|
||||
}
|
||||
|
||||
$string = twig_convert_encoding($string, 'UTF-8', $charset);
|
||||
$string = htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8');
|
||||
|
||||
return iconv('UTF-8', $charset, $string);
|
||||
|
||||
case 'js':
|
||||
// escape all non-alphanumeric characters
|
||||
// into their \x or \uHHHH representations
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = twig_convert_encoding($string, 'UTF-8', $charset);
|
||||
}
|
||||
|
||||
if (!preg_match('//u', $string)) {
|
||||
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
|
||||
}
|
||||
|
||||
$string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) {
|
||||
$char = $matches[0];
|
||||
|
||||
/*
|
||||
* A few characters have short escape sequences in JSON and JavaScript.
|
||||
* Escape sequences supported only by JavaScript, not JSON, are omitted.
|
||||
* \" is also supported but omitted, because the resulting string is not HTML safe.
|
||||
*/
|
||||
static $shortMap = [
|
||||
'\\' => '\\\\',
|
||||
'/' => '\\/',
|
||||
"\x08" => '\b',
|
||||
"\x0C" => '\f',
|
||||
"\x0A" => '\n',
|
||||
"\x0D" => '\r',
|
||||
"\x09" => '\t',
|
||||
];
|
||||
|
||||
if (isset($shortMap[$char])) {
|
||||
return $shortMap[$char];
|
||||
}
|
||||
|
||||
$codepoint = mb_ord($char, 'UTF-8');
|
||||
if (0x10000 > $codepoint) {
|
||||
return sprintf('\u%04X', $codepoint);
|
||||
}
|
||||
|
||||
// Split characters outside the BMP into surrogate pairs
|
||||
// https://tools.ietf.org/html/rfc2781.html#section-2.1
|
||||
$u = $codepoint - 0x10000;
|
||||
$high = 0xD800 | ($u >> 10);
|
||||
$low = 0xDC00 | ($u & 0x3FF);
|
||||
|
||||
return sprintf('\u%04X\u%04X', $high, $low);
|
||||
}, $string);
|
||||
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = iconv('UTF-8', $charset, $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
|
||||
case 'css':
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = twig_convert_encoding($string, 'UTF-8', $charset);
|
||||
}
|
||||
|
||||
if (!preg_match('//u', $string)) {
|
||||
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
|
||||
}
|
||||
|
||||
$string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) {
|
||||
$char = $matches[0];
|
||||
|
||||
return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8'));
|
||||
}, $string);
|
||||
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = iconv('UTF-8', $charset, $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
|
||||
case 'html_attr':
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = twig_convert_encoding($string, 'UTF-8', $charset);
|
||||
}
|
||||
|
||||
if (!preg_match('//u', $string)) {
|
||||
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
|
||||
}
|
||||
|
||||
$string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) {
|
||||
/**
|
||||
* This function is adapted from code coming from Zend Framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com)
|
||||
* @license https://framework.zend.com/license/new-bsd New BSD License
|
||||
*/
|
||||
$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 && "\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 (1 === \strlen($chr)) {
|
||||
/*
|
||||
* While HTML supports far more named entities, the lowest common denominator
|
||||
* has become HTML5's XML Serialisation which is restricted to the those named
|
||||
* entities that XML supports. Using HTML entities would result in this error:
|
||||
* XML Parsing Error: undefined entity
|
||||
*/
|
||||
static $entityMap = [
|
||||
34 => '"', /* quotation mark */
|
||||
38 => '&', /* ampersand */
|
||||
60 => '<', /* less-than sign */
|
||||
62 => '>', /* greater-than sign */
|
||||
];
|
||||
|
||||
if (isset($entityMap[$ord])) {
|
||||
return $entityMap[$ord];
|
||||
}
|
||||
|
||||
return sprintf('&#x%02X;', $ord);
|
||||
}
|
||||
|
||||
/*
|
||||
* Per OWASP recommendations, we'll use hex entities for any other
|
||||
* characters where a named entity does not exist.
|
||||
*/
|
||||
return sprintf('&#x%04X;', mb_ord($chr, 'UTF-8'));
|
||||
}, $string);
|
||||
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = iconv('UTF-8', $charset, $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
|
||||
case 'url':
|
||||
return rawurlencode($string);
|
||||
|
||||
default:
|
||||
$escapers = $env->getExtension(EscaperExtension::class)->getEscapers();
|
||||
if (\array_key_exists($strategy, $escapers)) {
|
||||
return $escapers[$strategy]($env, $string, $charset);
|
||||
}
|
||||
|
||||
$validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers)));
|
||||
|
||||
throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function twig_escape_filter_is_safe(Node $filterArgs)
|
||||
{
|
||||
foreach ($filterArgs as $arg) {
|
||||
if ($arg instanceof ConstantExpression) {
|
||||
return [$arg->getAttribute('value')];
|
||||
if (!isset($this->escaper)) {
|
||||
throw new \LogicException(\sprintf('You must call "setEnvironment()" before calling "%s()".', __METHOD__));
|
||||
}
|
||||
|
||||
return [];
|
||||
$this->escaper->addSafeClass($class, $strategies);
|
||||
}
|
||||
|
||||
return ['html'];
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function escapeFilterIsSafe(Node $filterArgs)
|
||||
{
|
||||
foreach ($filterArgs as $arg) {
|
||||
if ($arg instanceof ConstantExpression) {
|
||||
return [$arg->getAttribute('value')];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return ['html'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
namespace Twig\Extension;
|
||||
|
||||
use Twig\ExpressionParser;
|
||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||
use Twig\Node\Expression\Unary\AbstractUnary;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\NodeVisitor\NodeVisitorInterface;
|
||||
use Twig\OperatorPrecedenceChange;
|
||||
use Twig\TokenParser\TokenParserInterface;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
@@ -68,8 +68,8 @@ interface ExtensionInterface
|
||||
* @return array<array> First array of unary operators, second array of binary operators
|
||||
*
|
||||
* @psalm-return array{
|
||||
* array<string, array{precedence: int, class: class-string<AbstractUnary>}>,
|
||||
* array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}>
|
||||
* array<string, array{precedence: int, precedence_change?: OperatorPrecedenceChange, class: class-string<AbstractExpression>}>,
|
||||
* array<string, array{precedence: int, precedence_change?: OperatorPrecedenceChange, class?: class-string<AbstractExpression>, associativity: ExpressionParser::OPERATOR_*}>
|
||||
* }
|
||||
*/
|
||||
public function getOperators();
|
||||
|
||||
@@ -12,10 +12,7 @@
|
||||
namespace Twig\Extension;
|
||||
|
||||
/**
|
||||
* Enables usage of the deprecated Twig\Extension\AbstractExtension::getGlobals() method.
|
||||
*
|
||||
* Explicitly implement this interface if you really need to implement the
|
||||
* deprecated getGlobals() method in your extensions.
|
||||
* Allows Twig extensions to add globals to the context.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
|
||||
@@ -15,11 +15,9 @@ use Twig\NodeVisitor\OptimizerNodeVisitor;
|
||||
|
||||
final class OptimizerExtension extends AbstractExtension
|
||||
{
|
||||
private $optimizers;
|
||||
|
||||
public function __construct(int $optimizers = -1)
|
||||
{
|
||||
$this->optimizers = $optimizers;
|
||||
public function __construct(
|
||||
private int $optimizers = -1,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getNodeVisitors(): array
|
||||
|
||||
@@ -15,6 +15,7 @@ use Twig\NodeVisitor\SandboxNodeVisitor;
|
||||
use Twig\Sandbox\SecurityNotAllowedMethodError;
|
||||
use Twig\Sandbox\SecurityNotAllowedPropertyError;
|
||||
use Twig\Sandbox\SecurityPolicyInterface;
|
||||
use Twig\Sandbox\SourcePolicyInterface;
|
||||
use Twig\Source;
|
||||
use Twig\TokenParser\SandboxTokenParser;
|
||||
|
||||
@@ -23,11 +24,13 @@ final class SandboxExtension extends AbstractExtension
|
||||
private $sandboxedGlobally;
|
||||
private $sandboxed;
|
||||
private $policy;
|
||||
private $sourcePolicy;
|
||||
|
||||
public function __construct(SecurityPolicyInterface $policy, $sandboxed = false)
|
||||
public function __construct(SecurityPolicyInterface $policy, $sandboxed = false, ?SourcePolicyInterface $sourcePolicy = null)
|
||||
{
|
||||
$this->policy = $policy;
|
||||
$this->sandboxedGlobally = $sandboxed;
|
||||
$this->sourcePolicy = $sourcePolicy;
|
||||
}
|
||||
|
||||
public function getTokenParsers(): array
|
||||
@@ -50,9 +53,9 @@ final class SandboxExtension extends AbstractExtension
|
||||
$this->sandboxed = false;
|
||||
}
|
||||
|
||||
public function isSandboxed(): bool
|
||||
public function isSandboxed(?Source $source = null): bool
|
||||
{
|
||||
return $this->sandboxedGlobally || $this->sandboxed;
|
||||
return $this->sandboxedGlobally || $this->sandboxed || $this->isSourceSandboxed($source);
|
||||
}
|
||||
|
||||
public function isSandboxedGlobally(): bool
|
||||
@@ -60,6 +63,15 @@ final class SandboxExtension extends AbstractExtension
|
||||
return $this->sandboxedGlobally;
|
||||
}
|
||||
|
||||
private function isSourceSandboxed(?Source $source): bool
|
||||
{
|
||||
if (null === $source || null === $this->sourcePolicy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->sourcePolicy->enableSandbox($source);
|
||||
}
|
||||
|
||||
public function setSecurityPolicy(SecurityPolicyInterface $policy)
|
||||
{
|
||||
$this->policy = $policy;
|
||||
@@ -70,16 +82,16 @@ final class SandboxExtension extends AbstractExtension
|
||||
return $this->policy;
|
||||
}
|
||||
|
||||
public function checkSecurity($tags, $filters, $functions): void
|
||||
public function checkSecurity($tags, $filters, $functions, ?Source $source = null): void
|
||||
{
|
||||
if ($this->isSandboxed()) {
|
||||
if ($this->isSandboxed($source)) {
|
||||
$this->policy->checkSecurity($tags, $filters, $functions);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkMethodAllowed($obj, $method, int $lineno = -1, Source $source = null): void
|
||||
public function checkMethodAllowed($obj, $method, int $lineno = -1, ?Source $source = null): void
|
||||
{
|
||||
if ($this->isSandboxed()) {
|
||||
if ($this->isSandboxed($source)) {
|
||||
try {
|
||||
$this->policy->checkMethodAllowed($obj, $method);
|
||||
} catch (SecurityNotAllowedMethodError $e) {
|
||||
@@ -91,9 +103,9 @@ final class SandboxExtension extends AbstractExtension
|
||||
}
|
||||
}
|
||||
|
||||
public function checkPropertyAllowed($obj, $property, int $lineno = -1, Source $source = null): void
|
||||
public function checkPropertyAllowed($obj, $property, int $lineno = -1, ?Source $source = null): void
|
||||
{
|
||||
if ($this->isSandboxed()) {
|
||||
if ($this->isSandboxed($source)) {
|
||||
try {
|
||||
$this->policy->checkPropertyAllowed($obj, $property);
|
||||
} catch (SecurityNotAllowedPropertyError $e) {
|
||||
@@ -105,9 +117,15 @@ final class SandboxExtension extends AbstractExtension
|
||||
}
|
||||
}
|
||||
|
||||
public function ensureToStringAllowed($obj, int $lineno = -1, Source $source = null)
|
||||
public function ensureToStringAllowed($obj, int $lineno = -1, ?Source $source = null)
|
||||
{
|
||||
if ($this->isSandboxed() && \is_object($obj) && method_exists($obj, '__toString')) {
|
||||
if (\is_array($obj)) {
|
||||
$this->ensureToStringAllowedForArray($obj, $lineno, $source);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
if ($obj instanceof \Stringable && $this->isSandboxed($source)) {
|
||||
try {
|
||||
$this->policy->checkMethodAllowed($obj, '__toString');
|
||||
} catch (SecurityNotAllowedMethodError $e) {
|
||||
@@ -120,4 +138,28 @@ final class SandboxExtension extends AbstractExtension
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
private function ensureToStringAllowedForArray(array $obj, int $lineno, ?Source $source, array &$stack = []): void
|
||||
{
|
||||
foreach ($obj as $k => $v) {
|
||||
if (!$v) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!\is_array($v)) {
|
||||
$this->ensureToStringAllowed($v, $lineno, $source);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($r = \ReflectionReference::fromArrayElement($obj, $k)) {
|
||||
if (isset($stack[$r->getId()])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stack[$r->getId()] = true;
|
||||
}
|
||||
|
||||
$this->ensureToStringAllowedForArray($v, $lineno, $source, $stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ final class StagingExtension extends AbstractExtension
|
||||
public function addFunction(TwigFunction $function): void
|
||||
{
|
||||
if (isset($this->functions[$function->getName()])) {
|
||||
throw new \LogicException(sprintf('Function "%s" is already registered.', $function->getName()));
|
||||
throw new \LogicException(\sprintf('Function "%s" is already registered.', $function->getName()));
|
||||
}
|
||||
|
||||
$this->functions[$function->getName()] = $function;
|
||||
@@ -49,7 +49,7 @@ final class StagingExtension extends AbstractExtension
|
||||
public function addFilter(TwigFilter $filter): void
|
||||
{
|
||||
if (isset($this->filters[$filter->getName()])) {
|
||||
throw new \LogicException(sprintf('Filter "%s" is already registered.', $filter->getName()));
|
||||
throw new \LogicException(\sprintf('Filter "%s" is already registered.', $filter->getName()));
|
||||
}
|
||||
|
||||
$this->filters[$filter->getName()] = $filter;
|
||||
@@ -73,7 +73,7 @@ final class StagingExtension extends AbstractExtension
|
||||
public function addTokenParser(TokenParserInterface $parser): void
|
||||
{
|
||||
if (isset($this->tokenParsers[$parser->getTag()])) {
|
||||
throw new \LogicException(sprintf('Tag "%s" is already registered.', $parser->getTag()));
|
||||
throw new \LogicException(\sprintf('Tag "%s" is already registered.', $parser->getTag()));
|
||||
}
|
||||
|
||||
$this->tokenParsers[$parser->getTag()] = $parser;
|
||||
@@ -87,7 +87,7 @@ final class StagingExtension extends AbstractExtension
|
||||
public function addTest(TwigTest $test): void
|
||||
{
|
||||
if (isset($this->tests[$test->getName()])) {
|
||||
throw new \LogicException(sprintf('Test "%s" is already registered.', $test->getName()));
|
||||
throw new \LogicException(\sprintf('Test "%s" is already registered.', $test->getName()));
|
||||
}
|
||||
|
||||
$this->tests[$test->getName()] = $test;
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension {
|
||||
namespace Twig\Extension;
|
||||
|
||||
use Twig\Environment;
|
||||
use Twig\TemplateWrapper;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
final class StringLoaderExtension extends AbstractExtension
|
||||
@@ -17,26 +20,21 @@ final class StringLoaderExtension extends AbstractExtension
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('template_from_string', 'twig_template_from_string', ['needs_environment' => true]),
|
||||
new TwigFunction('template_from_string', [self::class, 'templateFromString'], ['needs_environment' => true]),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
use Twig\Environment;
|
||||
use Twig\TemplateWrapper;
|
||||
|
||||
/**
|
||||
* Loads a template from a string.
|
||||
*
|
||||
* {{ include(template_from_string("Hello {{ name }}")) }}
|
||||
*
|
||||
* @param string $template A template as a string or object implementing __toString()
|
||||
* @param string $name An optional name of the template to be used in error messages
|
||||
*/
|
||||
function twig_template_from_string(Environment $env, $template, string $name = null): TemplateWrapper
|
||||
{
|
||||
return $env->createTemplate((string) $template, $name);
|
||||
}
|
||||
/**
|
||||
* Loads a template from a string.
|
||||
*
|
||||
* {{ include(template_from_string("Hello {{ name }}")) }}
|
||||
*
|
||||
* @param string|null $name An optional name of the template to be used in error messages
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function templateFromString(Environment $env, string|\Stringable $template, ?string $name = null): TemplateWrapper
|
||||
{
|
||||
return $env->createTemplate((string) $template, $name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ use Twig\Error\RuntimeError;
|
||||
use Twig\Extension\ExtensionInterface;
|
||||
use Twig\Extension\GlobalsInterface;
|
||||
use Twig\Extension\StagingExtension;
|
||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||
use Twig\Node\Expression\Unary\AbstractUnary;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\NodeVisitor\NodeVisitorInterface;
|
||||
use Twig\TokenParser\TokenParserInterface;
|
||||
|
||||
@@ -35,15 +34,21 @@ final class ExtensionSet
|
||||
private $visitors;
|
||||
/** @var array<string, TwigFilter> */
|
||||
private $filters;
|
||||
/** @var array<string, TwigFilter> */
|
||||
private $dynamicFilters;
|
||||
/** @var array<string, TwigTest> */
|
||||
private $tests;
|
||||
/** @var array<string, TwigTest> */
|
||||
private $dynamicTests;
|
||||
/** @var array<string, TwigFunction> */
|
||||
private $functions;
|
||||
/** @var array<string, array{precedence: int, class: class-string<AbstractUnary>}> */
|
||||
/** @var array<string, TwigFunction> */
|
||||
private $dynamicFunctions;
|
||||
/** @var array<string, array{precedence: int, class: class-string<AbstractExpression>}> */
|
||||
private $unaryOperators;
|
||||
/** @var array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}> */
|
||||
/** @var array<string, array{precedence: int, class?: class-string<AbstractExpression>, associativity: ExpressionParser::OPERATOR_*}> */
|
||||
private $binaryOperators;
|
||||
/** @var array<string, mixed> */
|
||||
/** @var array<string, mixed>|null */
|
||||
private $globals;
|
||||
private $functionCallbacks = [];
|
||||
private $filterCallbacks = [];
|
||||
@@ -70,7 +75,7 @@ final class ExtensionSet
|
||||
$class = ltrim($class, '\\');
|
||||
|
||||
if (!isset($this->extensions[$class])) {
|
||||
throw new RuntimeError(sprintf('The "%s" extension is not enabled.', $class));
|
||||
throw new RuntimeError(\sprintf('The "%s" extension is not enabled.', $class));
|
||||
}
|
||||
|
||||
return $this->extensions[$class];
|
||||
@@ -125,11 +130,11 @@ final class ExtensionSet
|
||||
$class = \get_class($extension);
|
||||
|
||||
if ($this->initialized) {
|
||||
throw new \LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class));
|
||||
throw new \LogicException(\sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class));
|
||||
}
|
||||
|
||||
if (isset($this->extensions[$class])) {
|
||||
throw new \LogicException(sprintf('Unable to register extension "%s" as it is already registered.', $class));
|
||||
throw new \LogicException(\sprintf('Unable to register extension "%s" as it is already registered.', $class));
|
||||
}
|
||||
|
||||
$this->extensions[$class] = $extension;
|
||||
@@ -138,7 +143,7 @@ final class ExtensionSet
|
||||
public function addFunction(TwigFunction $function): void
|
||||
{
|
||||
if ($this->initialized) {
|
||||
throw new \LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $function->getName()));
|
||||
throw new \LogicException(\sprintf('Unable to add function "%s" as extensions have already been initialized.', $function->getName()));
|
||||
}
|
||||
|
||||
$this->staging->addFunction($function);
|
||||
@@ -166,14 +171,11 @@ final class ExtensionSet
|
||||
return $this->functions[$name];
|
||||
}
|
||||
|
||||
foreach ($this->functions as $pattern => $function) {
|
||||
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
|
||||
|
||||
if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) {
|
||||
foreach ($this->dynamicFunctions as $pattern => $function) {
|
||||
if (preg_match($pattern, $name, $matches)) {
|
||||
array_shift($matches);
|
||||
$function->setArguments($matches);
|
||||
|
||||
return $function;
|
||||
return $function->withDynamicArguments($name, $function->getName(), $matches);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +196,7 @@ final class ExtensionSet
|
||||
public function addFilter(TwigFilter $filter): void
|
||||
{
|
||||
if ($this->initialized) {
|
||||
throw new \LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $filter->getName()));
|
||||
throw new \LogicException(\sprintf('Unable to add filter "%s" as extensions have already been initialized.', $filter->getName()));
|
||||
}
|
||||
|
||||
$this->staging->addFilter($filter);
|
||||
@@ -222,14 +224,11 @@ final class ExtensionSet
|
||||
return $this->filters[$name];
|
||||
}
|
||||
|
||||
foreach ($this->filters as $pattern => $filter) {
|
||||
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
|
||||
|
||||
if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) {
|
||||
foreach ($this->dynamicFilters as $pattern => $filter) {
|
||||
if (preg_match($pattern, $name, $matches)) {
|
||||
array_shift($matches);
|
||||
$filter->setArguments($matches);
|
||||
|
||||
return $filter;
|
||||
return $filter->withDynamicArguments($name, $filter->getName(), $matches);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,12 +327,7 @@ final class ExtensionSet
|
||||
continue;
|
||||
}
|
||||
|
||||
$extGlobals = $extension->getGlobals();
|
||||
if (!\is_array($extGlobals)) {
|
||||
throw new \UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', \get_class($extension)));
|
||||
}
|
||||
|
||||
$globals = array_merge($globals, $extGlobals);
|
||||
$globals = array_merge($globals, $extension->getGlobals());
|
||||
}
|
||||
|
||||
if ($this->initialized) {
|
||||
@@ -343,10 +337,15 @@ final class ExtensionSet
|
||||
return $globals;
|
||||
}
|
||||
|
||||
public function resetGlobals(): void
|
||||
{
|
||||
$this->globals = null;
|
||||
}
|
||||
|
||||
public function addTest(TwigTest $test): void
|
||||
{
|
||||
if ($this->initialized) {
|
||||
throw new \LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $test->getName()));
|
||||
throw new \LogicException(\sprintf('Unable to add test "%s" as extensions have already been initialized.', $test->getName()));
|
||||
}
|
||||
|
||||
$this->staging->addTest($test);
|
||||
@@ -374,16 +373,11 @@ final class ExtensionSet
|
||||
return $this->tests[$name];
|
||||
}
|
||||
|
||||
foreach ($this->tests as $pattern => $test) {
|
||||
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
|
||||
foreach ($this->dynamicTests as $pattern => $test) {
|
||||
if (preg_match($pattern, $name, $matches)) {
|
||||
array_shift($matches);
|
||||
|
||||
if ($count) {
|
||||
if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
|
||||
array_shift($matches);
|
||||
$test->setArguments($matches);
|
||||
|
||||
return $test;
|
||||
}
|
||||
return $test->withDynamicArguments($name, $test->getName(), $matches);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +385,7 @@ final class ExtensionSet
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{precedence: int, class: class-string<AbstractUnary>}>
|
||||
* @return array<string, array{precedence: int, class: class-string<AbstractExpression>}>
|
||||
*/
|
||||
public function getUnaryOperators(): array
|
||||
{
|
||||
@@ -403,7 +397,7 @@ final class ExtensionSet
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}>
|
||||
* @return array<string, array{precedence: int, class?: class-string<AbstractExpression>, associativity: ExpressionParser::OPERATOR_*}>
|
||||
*/
|
||||
public function getBinaryOperators(): array
|
||||
{
|
||||
@@ -420,6 +414,9 @@ final class ExtensionSet
|
||||
$this->filters = [];
|
||||
$this->functions = [];
|
||||
$this->tests = [];
|
||||
$this->dynamicFilters = [];
|
||||
$this->dynamicFunctions = [];
|
||||
$this->dynamicTests = [];
|
||||
$this->visitors = [];
|
||||
$this->unaryOperators = [];
|
||||
$this->binaryOperators = [];
|
||||
@@ -436,17 +433,26 @@ final class ExtensionSet
|
||||
{
|
||||
// filters
|
||||
foreach ($extension->getFilters() as $filter) {
|
||||
$this->filters[$filter->getName()] = $filter;
|
||||
$this->filters[$name = $filter->getName()] = $filter;
|
||||
if (str_contains($name, '*')) {
|
||||
$this->dynamicFilters['#^'.str_replace('\\*', '(.*?)', preg_quote($name, '#')).'$#'] = $filter;
|
||||
}
|
||||
}
|
||||
|
||||
// functions
|
||||
foreach ($extension->getFunctions() as $function) {
|
||||
$this->functions[$function->getName()] = $function;
|
||||
$this->functions[$name = $function->getName()] = $function;
|
||||
if (str_contains($name, '*')) {
|
||||
$this->dynamicFunctions['#^'.str_replace('\\*', '(.*?)', preg_quote($name, '#')).'$#'] = $function;
|
||||
}
|
||||
}
|
||||
|
||||
// tests
|
||||
foreach ($extension->getTests() as $test) {
|
||||
$this->tests[$test->getName()] = $test;
|
||||
$this->tests[$name = $test->getName()] = $test;
|
||||
if (str_contains($name, '*')) {
|
||||
$this->dynamicTests['#^'.str_replace('\\*', '(.*?)', preg_quote($name, '#')).'$#'] = $test;
|
||||
}
|
||||
}
|
||||
|
||||
// token parsers
|
||||
@@ -466,11 +472,11 @@ final class ExtensionSet
|
||||
// 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)));
|
||||
throw new \InvalidArgumentException(\sprintf('"%s::getOperators()" must return an array with operators, got "%s".', \get_class($extension), get_debug_type($operators).(\is_resource($operators) ? '' : '#'.$operators)));
|
||||
}
|
||||
|
||||
if (2 !== \count($operators)) {
|
||||
throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators)));
|
||||
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]);
|
||||
|
||||
@@ -45,6 +45,7 @@ class FileExtensionEscapingStrategy
|
||||
|
||||
switch ($extension) {
|
||||
case 'js':
|
||||
case 'json':
|
||||
return 'js';
|
||||
|
||||
case 'css':
|
||||
|
||||
@@ -48,8 +48,17 @@ class Lexer
|
||||
public const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
|
||||
public const REGEX_DQ_STRING_DELIM = '/"/A';
|
||||
public const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As';
|
||||
public const REGEX_INLINE_COMMENT = '/#[^\n]*/A';
|
||||
public const PUNCTUATION = '()[]{}?:.,|';
|
||||
|
||||
private const SPECIAL_CHARS = [
|
||||
'f' => "\f",
|
||||
'n' => "\n",
|
||||
'r' => "\r",
|
||||
't' => "\t",
|
||||
'v' => "\v",
|
||||
];
|
||||
|
||||
public function __construct(Environment $env, array $options = [])
|
||||
{
|
||||
$this->env = $env;
|
||||
@@ -71,8 +80,6 @@ class Lexer
|
||||
return;
|
||||
}
|
||||
|
||||
$this->isInitialized = true;
|
||||
|
||||
// when PHP 7.3 is the min version, we will be able to remove the '#' part in preg_quote as it's part of the default
|
||||
$this->regexes = [
|
||||
// }}
|
||||
@@ -160,6 +167,8 @@ class Lexer
|
||||
'interpolation_start' => '{'.preg_quote($this->options['interpolation'][0], '#').'\s*}A',
|
||||
'interpolation_end' => '{\s*'.preg_quote($this->options['interpolation'][1], '#').'}A',
|
||||
];
|
||||
|
||||
$this->isInitialized = true;
|
||||
}
|
||||
|
||||
public function tokenize(Source $source): TokenStream
|
||||
@@ -207,11 +216,11 @@ class Lexer
|
||||
}
|
||||
}
|
||||
|
||||
$this->pushToken(/* Token::EOF_TYPE */ -1);
|
||||
$this->pushToken(Token::EOF_TYPE);
|
||||
|
||||
if (!empty($this->brackets)) {
|
||||
list($expect, $lineno) = array_pop($this->brackets);
|
||||
throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
|
||||
if ($this->brackets) {
|
||||
[$expect, $lineno] = array_pop($this->brackets);
|
||||
throw new SyntaxError(\sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
|
||||
}
|
||||
|
||||
return new TokenStream($this->tokens, $this->source);
|
||||
@@ -221,7 +230,7 @@ class Lexer
|
||||
{
|
||||
// if no matches are left we return the rest of the template as simple text token
|
||||
if ($this->position == \count($this->positions[0]) - 1) {
|
||||
$this->pushToken(/* Token::TEXT_TYPE */ 0, substr($this->code, $this->cursor));
|
||||
$this->pushToken(Token::TEXT_TYPE, substr($this->code, $this->cursor));
|
||||
$this->cursor = $this->end;
|
||||
|
||||
return;
|
||||
@@ -250,7 +259,7 @@ class Lexer
|
||||
$text = rtrim($text, " \t\0\x0B");
|
||||
}
|
||||
}
|
||||
$this->pushToken(/* Token::TEXT_TYPE */ 0, $text);
|
||||
$this->pushToken(Token::TEXT_TYPE, $text);
|
||||
$this->moveCursor($textContent.$position[0]);
|
||||
|
||||
switch ($this->positions[1][$this->position][0]) {
|
||||
@@ -268,14 +277,14 @@ class Lexer
|
||||
$this->moveCursor($match[0]);
|
||||
$this->lineno = (int) $match[1];
|
||||
} else {
|
||||
$this->pushToken(/* Token::BLOCK_START_TYPE */ 1);
|
||||
$this->pushToken(Token::BLOCK_START_TYPE);
|
||||
$this->pushState(self::STATE_BLOCK);
|
||||
$this->currentVarBlockLine = $this->lineno;
|
||||
}
|
||||
break;
|
||||
|
||||
case $this->options['tag_variable'][0]:
|
||||
$this->pushToken(/* Token::VAR_START_TYPE */ 2);
|
||||
$this->pushToken(Token::VAR_START_TYPE);
|
||||
$this->pushState(self::STATE_VAR);
|
||||
$this->currentVarBlockLine = $this->lineno;
|
||||
break;
|
||||
@@ -284,8 +293,8 @@ class Lexer
|
||||
|
||||
private function lexBlock(): void
|
||||
{
|
||||
if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) {
|
||||
$this->pushToken(/* Token::BLOCK_END_TYPE */ 3);
|
||||
if (!$this->brackets && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) {
|
||||
$this->pushToken(Token::BLOCK_END_TYPE);
|
||||
$this->moveCursor($match[0]);
|
||||
$this->popState();
|
||||
} else {
|
||||
@@ -295,8 +304,8 @@ class Lexer
|
||||
|
||||
private function lexVar(): void
|
||||
{
|
||||
if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) {
|
||||
$this->pushToken(/* Token::VAR_END_TYPE */ 4);
|
||||
if (!$this->brackets && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) {
|
||||
$this->pushToken(Token::VAR_END_TYPE);
|
||||
$this->moveCursor($match[0]);
|
||||
$this->popState();
|
||||
} else {
|
||||
@@ -311,7 +320,7 @@ class Lexer
|
||||
$this->moveCursor($match[0]);
|
||||
|
||||
if ($this->cursor >= $this->end) {
|
||||
throw new SyntaxError(sprintf('Unclosed "%s".', self::STATE_BLOCK === $this->state ? 'block' : 'variable'), $this->currentVarBlockLine, $this->source);
|
||||
throw new SyntaxError(\sprintf('Unclosed "%s".', self::STATE_BLOCK === $this->state ? 'block' : 'variable'), $this->currentVarBlockLine, $this->source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,18 +330,18 @@ class Lexer
|
||||
$this->moveCursor('...');
|
||||
}
|
||||
// arrow function
|
||||
elseif ('=' === $this->code[$this->cursor] && '>' === $this->code[$this->cursor + 1]) {
|
||||
elseif ('=' === $this->code[$this->cursor] && ($this->cursor + 1 < $this->end) && '>' === $this->code[$this->cursor + 1]) {
|
||||
$this->pushToken(Token::ARROW_TYPE, '=>');
|
||||
$this->moveCursor('=>');
|
||||
}
|
||||
// operators
|
||||
elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) {
|
||||
$this->pushToken(/* Token::OPERATOR_TYPE */ 8, preg_replace('/\s+/', ' ', $match[0]));
|
||||
$this->pushToken(Token::OPERATOR_TYPE, preg_replace('/\s+/', ' ', $match[0]));
|
||||
$this->moveCursor($match[0]);
|
||||
}
|
||||
// names
|
||||
elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) {
|
||||
$this->pushToken(/* Token::NAME_TYPE */ 5, $match[0]);
|
||||
$this->pushToken(Token::NAME_TYPE, $match[0]);
|
||||
$this->moveCursor($match[0]);
|
||||
}
|
||||
// numbers
|
||||
@@ -341,7 +350,7 @@ class Lexer
|
||||
if (ctype_digit($match[0]) && $number <= \PHP_INT_MAX) {
|
||||
$number = (int) $match[0]; // integers lower than the maximum
|
||||
}
|
||||
$this->pushToken(/* Token::NUMBER_TYPE */ 6, $number);
|
||||
$this->pushToken(Token::NUMBER_TYPE, $number);
|
||||
$this->moveCursor($match[0]);
|
||||
}
|
||||
// punctuation
|
||||
@@ -352,22 +361,22 @@ class Lexer
|
||||
}
|
||||
// closing bracket
|
||||
elseif (str_contains(')]}', $this->code[$this->cursor])) {
|
||||
if (empty($this->brackets)) {
|
||||
throw new SyntaxError(sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source);
|
||||
if (!$this->brackets) {
|
||||
throw new SyntaxError(\sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source);
|
||||
}
|
||||
|
||||
list($expect, $lineno) = array_pop($this->brackets);
|
||||
[$expect, $lineno] = array_pop($this->brackets);
|
||||
if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) {
|
||||
throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
|
||||
throw new SyntaxError(\sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
|
||||
}
|
||||
}
|
||||
|
||||
$this->pushToken(/* Token::PUNCTUATION_TYPE */ 9, $this->code[$this->cursor]);
|
||||
$this->pushToken(Token::PUNCTUATION_TYPE, $this->code[$this->cursor]);
|
||||
++$this->cursor;
|
||||
}
|
||||
// strings
|
||||
elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) {
|
||||
$this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes(substr($match[0], 1, -1)));
|
||||
$this->pushToken(Token::STRING_TYPE, $this->stripcslashes(substr($match[0], 1, -1), substr($match[0], 0, 1)));
|
||||
$this->moveCursor($match[0]);
|
||||
}
|
||||
// opening double quoted string
|
||||
@@ -376,12 +385,73 @@ class Lexer
|
||||
$this->pushState(self::STATE_STRING);
|
||||
$this->moveCursor($match[0]);
|
||||
}
|
||||
// inline comment
|
||||
elseif (preg_match(self::REGEX_INLINE_COMMENT, $this->code, $match, 0, $this->cursor)) {
|
||||
$this->moveCursor($match[0]);
|
||||
}
|
||||
// unlexable
|
||||
else {
|
||||
throw new SyntaxError(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source);
|
||||
throw new SyntaxError(\sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source);
|
||||
}
|
||||
}
|
||||
|
||||
private function stripcslashes(string $str, string $quoteType): string
|
||||
{
|
||||
$result = '';
|
||||
$length = \strlen($str);
|
||||
|
||||
$i = 0;
|
||||
while ($i < $length) {
|
||||
if (false === $pos = strpos($str, '\\', $i)) {
|
||||
$result .= substr($str, $i);
|
||||
break;
|
||||
}
|
||||
|
||||
$result .= substr($str, $i, $pos - $i);
|
||||
$i = $pos + 1;
|
||||
|
||||
if ($i >= $length) {
|
||||
$result .= '\\';
|
||||
break;
|
||||
}
|
||||
|
||||
$nextChar = $str[$i];
|
||||
|
||||
if (isset(self::SPECIAL_CHARS[$nextChar])) {
|
||||
$result .= self::SPECIAL_CHARS[$nextChar];
|
||||
} elseif ('\\' === $nextChar) {
|
||||
$result .= $nextChar;
|
||||
} elseif ("'" === $nextChar || '"' === $nextChar) {
|
||||
if ($nextChar !== $quoteType) {
|
||||
trigger_deprecation('twig/twig', '3.12', 'Character "%s" should not be escaped; the "\" character is ignored in Twig 3 but will not be in Twig 4. Please remove the extra "\" character at position %d in "%s" at line %d.', $nextChar, $i + 1, $this->source->getName(), $this->lineno);
|
||||
}
|
||||
$result .= $nextChar;
|
||||
} elseif ('#' === $nextChar && $i + 1 < $length && '{' === $str[$i + 1]) {
|
||||
$result .= '#{';
|
||||
++$i;
|
||||
} elseif ('x' === $nextChar && $i + 1 < $length && ctype_xdigit($str[$i + 1])) {
|
||||
$hex = $str[++$i];
|
||||
if ($i + 1 < $length && ctype_xdigit($str[$i + 1])) {
|
||||
$hex .= $str[++$i];
|
||||
}
|
||||
$result .= \chr(hexdec($hex));
|
||||
} elseif (ctype_digit($nextChar) && $nextChar < '8') {
|
||||
$octal = $nextChar;
|
||||
while ($i + 1 < $length && ctype_digit($str[$i + 1]) && $str[$i + 1] < '8' && \strlen($octal) < 3) {
|
||||
$octal .= $str[++$i];
|
||||
}
|
||||
$result .= \chr(octdec($octal));
|
||||
} else {
|
||||
trigger_deprecation('twig/twig', '3.12', 'Character "%s" should not be escaped; the "\" character is ignored in Twig 3 but will not be in Twig 4. Please remove the extra "\" character at position %d in "%s" at line %d.', $nextChar, $i + 1, $this->source->getName(), $this->lineno);
|
||||
$result .= $nextChar;
|
||||
}
|
||||
|
||||
++$i;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function lexRawData(): void
|
||||
{
|
||||
if (!preg_match($this->regexes['lex_raw_data'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) {
|
||||
@@ -403,7 +473,7 @@ class Lexer
|
||||
}
|
||||
}
|
||||
|
||||
$this->pushToken(/* Token::TEXT_TYPE */ 0, $text);
|
||||
$this->pushToken(Token::TEXT_TYPE, $text);
|
||||
}
|
||||
|
||||
private function lexComment(): void
|
||||
@@ -419,23 +489,23 @@ class Lexer
|
||||
{
|
||||
if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) {
|
||||
$this->brackets[] = [$this->options['interpolation'][0], $this->lineno];
|
||||
$this->pushToken(/* Token::INTERPOLATION_START_TYPE */ 10);
|
||||
$this->pushToken(Token::INTERPOLATION_START_TYPE);
|
||||
$this->moveCursor($match[0]);
|
||||
$this->pushState(self::STATE_INTERPOLATION);
|
||||
} elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && '' !== $match[0]) {
|
||||
$this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes($match[0]));
|
||||
$this->pushToken(Token::STRING_TYPE, $this->stripcslashes($match[0], '"'));
|
||||
$this->moveCursor($match[0]);
|
||||
} elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {
|
||||
list($expect, $lineno) = array_pop($this->brackets);
|
||||
[$expect, $lineno] = array_pop($this->brackets);
|
||||
if ('"' != $this->code[$this->cursor]) {
|
||||
throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
|
||||
throw new SyntaxError(\sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
|
||||
}
|
||||
|
||||
$this->popState();
|
||||
++$this->cursor;
|
||||
} else {
|
||||
// unlexable
|
||||
throw new SyntaxError(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source);
|
||||
throw new SyntaxError(\sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,7 +514,7 @@ class Lexer
|
||||
$bracket = end($this->brackets);
|
||||
if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, 0, $this->cursor)) {
|
||||
array_pop($this->brackets);
|
||||
$this->pushToken(/* Token::INTERPOLATION_END_TYPE */ 11);
|
||||
$this->pushToken(Token::INTERPOLATION_END_TYPE);
|
||||
$this->moveCursor($match[0]);
|
||||
$this->popState();
|
||||
} else {
|
||||
@@ -455,7 +525,7 @@ class Lexer
|
||||
private function pushToken($type, $value = ''): void
|
||||
{
|
||||
// do not push empty text tokens
|
||||
if (/* Token::TEXT_TYPE */ 0 === $type && '' === $value) {
|
||||
if (Token::TEXT_TYPE === $type && '' === $value) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,14 +28,12 @@ use Twig\Source;
|
||||
*/
|
||||
final class ArrayLoader implements LoaderInterface
|
||||
{
|
||||
private $templates = [];
|
||||
|
||||
/**
|
||||
* @param array $templates An array of templates (keys are the names, and values are the source code)
|
||||
*/
|
||||
public function __construct(array $templates = [])
|
||||
{
|
||||
$this->templates = $templates;
|
||||
public function __construct(
|
||||
private array $templates = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function setTemplate(string $name, string $template): void
|
||||
@@ -46,7 +44,7 @@ final class ArrayLoader implements LoaderInterface
|
||||
public function getSourceContext(string $name): Source
|
||||
{
|
||||
if (!isset($this->templates[$name])) {
|
||||
throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
|
||||
throw new LoaderError(\sprintf('Template "%s" is not defined.', $name));
|
||||
}
|
||||
|
||||
return new Source($this->templates[$name], $name);
|
||||
@@ -60,7 +58,7 @@ final class ArrayLoader implements LoaderInterface
|
||||
public function getCacheKey(string $name): string
|
||||
{
|
||||
if (!isset($this->templates[$name])) {
|
||||
throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
|
||||
throw new LoaderError(\sprintf('Template "%s" is not defined.', $name));
|
||||
}
|
||||
|
||||
return $name.':'.$this->templates[$name];
|
||||
@@ -69,7 +67,7 @@ final class ArrayLoader implements LoaderInterface
|
||||
public function isFresh(string $name, int $time): bool
|
||||
{
|
||||
if (!isset($this->templates[$name])) {
|
||||
throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
|
||||
throw new LoaderError(\sprintf('Template "%s" is not defined.', $name));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -21,22 +21,28 @@ use Twig\Source;
|
||||
*/
|
||||
final class ChainLoader implements LoaderInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $hasSourceCache = [];
|
||||
private $loaders = [];
|
||||
|
||||
/**
|
||||
* @param LoaderInterface[] $loaders
|
||||
* @param iterable<LoaderInterface> $loaders
|
||||
*/
|
||||
public function __construct(array $loaders = [])
|
||||
{
|
||||
foreach ($loaders as $loader) {
|
||||
$this->addLoader($loader);
|
||||
}
|
||||
public function __construct(
|
||||
private iterable $loaders = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function addLoader(LoaderInterface $loader): void
|
||||
{
|
||||
$this->loaders[] = $loader;
|
||||
$current = $this->loaders;
|
||||
|
||||
$this->loaders = (static function () use ($current, $loader): \Generator {
|
||||
yield from $current;
|
||||
yield $loader;
|
||||
})();
|
||||
|
||||
$this->hasSourceCache = [];
|
||||
}
|
||||
|
||||
@@ -45,13 +51,18 @@ final class ChainLoader implements LoaderInterface
|
||||
*/
|
||||
public function getLoaders(): array
|
||||
{
|
||||
if (!\is_array($this->loaders)) {
|
||||
$this->loaders = iterator_to_array($this->loaders, false);
|
||||
}
|
||||
|
||||
return $this->loaders;
|
||||
}
|
||||
|
||||
public function getSourceContext(string $name): Source
|
||||
{
|
||||
$exceptions = [];
|
||||
foreach ($this->loaders as $loader) {
|
||||
|
||||
foreach ($this->getLoaders() as $loader) {
|
||||
if (!$loader->exists($name)) {
|
||||
continue;
|
||||
}
|
||||
@@ -63,7 +74,7 @@ final class ChainLoader implements LoaderInterface
|
||||
}
|
||||
}
|
||||
|
||||
throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));
|
||||
throw new LoaderError(\sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));
|
||||
}
|
||||
|
||||
public function exists(string $name): bool
|
||||
@@ -72,7 +83,7 @@ final class ChainLoader implements LoaderInterface
|
||||
return $this->hasSourceCache[$name];
|
||||
}
|
||||
|
||||
foreach ($this->loaders as $loader) {
|
||||
foreach ($this->getLoaders() as $loader) {
|
||||
if ($loader->exists($name)) {
|
||||
return $this->hasSourceCache[$name] = true;
|
||||
}
|
||||
@@ -84,7 +95,8 @@ final class ChainLoader implements LoaderInterface
|
||||
public function getCacheKey(string $name): string
|
||||
{
|
||||
$exceptions = [];
|
||||
foreach ($this->loaders as $loader) {
|
||||
|
||||
foreach ($this->getLoaders() as $loader) {
|
||||
if (!$loader->exists($name)) {
|
||||
continue;
|
||||
}
|
||||
@@ -96,13 +108,14 @@ final class ChainLoader implements LoaderInterface
|
||||
}
|
||||
}
|
||||
|
||||
throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));
|
||||
throw new LoaderError(\sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));
|
||||
}
|
||||
|
||||
public function isFresh(string $name, int $time): bool
|
||||
{
|
||||
$exceptions = [];
|
||||
foreach ($this->loaders as $loader) {
|
||||
|
||||
foreach ($this->getLoaders() as $loader) {
|
||||
if (!$loader->exists($name)) {
|
||||
continue;
|
||||
}
|
||||
@@ -114,6 +127,6 @@ final class ChainLoader implements LoaderInterface
|
||||
}
|
||||
}
|
||||
|
||||
throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));
|
||||
throw new LoaderError(\sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ class FilesystemLoader implements LoaderInterface
|
||||
/** Identifier of the main namespace. */
|
||||
public const MAIN_NAMESPACE = '__main__';
|
||||
|
||||
/**
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
protected $paths = [];
|
||||
protected $cache = [];
|
||||
protected $errorCache = [];
|
||||
@@ -31,10 +34,10 @@ class FilesystemLoader implements LoaderInterface
|
||||
private $rootPath;
|
||||
|
||||
/**
|
||||
* @param string|array $paths A path or an array of paths where to look for templates
|
||||
* @param string|string[] $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 = [], string $rootPath = null)
|
||||
public function __construct($paths = [], ?string $rootPath = null)
|
||||
{
|
||||
$this->rootPath = ($rootPath ?? getcwd()).\DIRECTORY_SEPARATOR;
|
||||
if (null !== $rootPath && false !== ($realPath = realpath($rootPath))) {
|
||||
@@ -48,6 +51,8 @@ class FilesystemLoader implements LoaderInterface
|
||||
|
||||
/**
|
||||
* Returns the paths to the templates.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getPaths(string $namespace = self::MAIN_NAMESPACE): array
|
||||
{
|
||||
@@ -58,6 +63,8 @@ class FilesystemLoader implements LoaderInterface
|
||||
* Returns the path namespaces.
|
||||
*
|
||||
* The main namespace is always defined.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getNamespaces(): array
|
||||
{
|
||||
@@ -65,7 +72,7 @@ class FilesystemLoader implements LoaderInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $paths A path or an array of paths where to look for templates
|
||||
* @param string|string[] $paths A path or an array of paths where to look for templates
|
||||
*/
|
||||
public function setPaths($paths, string $namespace = self::MAIN_NAMESPACE): void
|
||||
{
|
||||
@@ -89,7 +96,7 @@ class FilesystemLoader implements LoaderInterface
|
||||
|
||||
$checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path;
|
||||
if (!is_dir($checkPath)) {
|
||||
throw new LoaderError(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath));
|
||||
throw new LoaderError(\sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath));
|
||||
}
|
||||
|
||||
$this->paths[$namespace][] = rtrim($path, '/\\');
|
||||
@@ -105,7 +112,7 @@ class FilesystemLoader implements LoaderInterface
|
||||
|
||||
$checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path;
|
||||
if (!is_dir($checkPath)) {
|
||||
throw new LoaderError(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath));
|
||||
throw new LoaderError(\sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath));
|
||||
}
|
||||
|
||||
$path = rtrim($path, '/\\');
|
||||
@@ -183,7 +190,7 @@ class FilesystemLoader implements LoaderInterface
|
||||
}
|
||||
|
||||
try {
|
||||
list($namespace, $shortname) = $this->parseName($name);
|
||||
[$namespace, $shortname] = $this->parseName($name);
|
||||
|
||||
$this->validateName($shortname);
|
||||
} catch (LoaderError $e) {
|
||||
@@ -195,7 +202,7 @@ class FilesystemLoader implements LoaderInterface
|
||||
}
|
||||
|
||||
if (!isset($this->paths[$namespace])) {
|
||||
$this->errorCache[$name] = 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 null;
|
||||
@@ -218,7 +225,7 @@ class FilesystemLoader implements LoaderInterface
|
||||
}
|
||||
}
|
||||
|
||||
$this->errorCache[$name] = 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 null;
|
||||
@@ -236,7 +243,7 @@ class FilesystemLoader implements LoaderInterface
|
||||
{
|
||||
if (isset($name[0]) && '@' == $name[0]) {
|
||||
if (false === $pos = strpos($name, '/')) {
|
||||
throw new LoaderError(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
|
||||
throw new LoaderError(\sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
|
||||
}
|
||||
|
||||
$namespace = substr($name, 1, $pos - 1);
|
||||
@@ -265,7 +272,7 @@ class FilesystemLoader implements LoaderInterface
|
||||
}
|
||||
|
||||
if ($level < 0) {
|
||||
throw new LoaderError(sprintf('Looks like you try to load a template outside configured directories (%s).', $name));
|
||||
throw new LoaderError(\sprintf('Looks like you try to load a template outside configured directories (%s).', $name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ namespace Twig;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Markup implements \Countable, \JsonSerializable
|
||||
class Markup implements \Countable, \JsonSerializable, \Stringable
|
||||
{
|
||||
private $content;
|
||||
private $charset;
|
||||
private ?string $charset;
|
||||
|
||||
public function __construct($content, $charset)
|
||||
{
|
||||
@@ -32,6 +32,11 @@ class Markup implements \Countable, \JsonSerializable
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function getCharset(): string
|
||||
{
|
||||
return $this->charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
@@ -24,11 +25,12 @@ use Twig\Compiler;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class AutoEscapeNode extends Node
|
||||
{
|
||||
public function __construct($value, Node $body, int $lineno, string $tag = 'autoescape')
|
||||
public function __construct($value, Node $body, int $lineno)
|
||||
{
|
||||
parent::__construct(['body' => $body], ['value' => $value], $lineno, $tag);
|
||||
parent::__construct(['body' => $body], ['value' => $value], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
@@ -19,24 +20,29 @@ use Twig\Compiler;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class BlockNode extends Node
|
||||
{
|
||||
public function __construct(string $name, Node $body, int $lineno, string $tag = null)
|
||||
public function __construct(string $name, Node $body, int $lineno)
|
||||
{
|
||||
parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag);
|
||||
parent::__construct(['body' => $body], ['name' => $name], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write(sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n")
|
||||
->write("/**\n")
|
||||
->write(" * @return iterable<null|scalar|\Stringable>\n")
|
||||
->write(" */\n")
|
||||
->write(\sprintf("public function block_%s(array \$context, array \$blocks = []): iterable\n", $this->getAttribute('name')), "{\n")
|
||||
->indent()
|
||||
->write("\$macros = \$this->macros;\n")
|
||||
;
|
||||
|
||||
$compiler
|
||||
->subcompile($this->getNode('body'))
|
||||
->write("yield from [];\n")
|
||||
->outdent()
|
||||
->write("}\n\n")
|
||||
;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
@@ -19,18 +20,19 @@ use Twig\Compiler;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class BlockReferenceNode extends Node implements NodeOutputInterface
|
||||
{
|
||||
public function __construct(string $name, int $lineno, string $tag = null)
|
||||
public function __construct(string $name, int $lineno)
|
||||
{
|
||||
parent::__construct([], ['name' => $name], $lineno, $tag);
|
||||
parent::__construct([], ['name' => $name], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write(sprintf("\$this->displayBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name')))
|
||||
->write(\sprintf("yield from \$this->unwrap()->yieldBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name')))
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,14 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
|
||||
/**
|
||||
* Represents a body node.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class BodyNode extends Node
|
||||
{
|
||||
}
|
||||
|
||||
@@ -11,17 +11,19 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class CheckSecurityCallNode extends Node
|
||||
{
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
$compiler
|
||||
->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n")
|
||||
->write("\$this->sandbox = \$this->extensions[SandboxExtension::class];\n")
|
||||
->write("\$this->checkSecurity();\n")
|
||||
;
|
||||
}
|
||||
|
||||
@@ -11,17 +11,24 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class CheckSecurityNode extends Node
|
||||
{
|
||||
private $usedFilters;
|
||||
private $usedTags;
|
||||
private $usedFunctions;
|
||||
|
||||
/**
|
||||
* @param array<string, int> $usedFilters
|
||||
* @param array<string, int> $usedTags
|
||||
* @param array<string, int> $usedFunctions
|
||||
*/
|
||||
public function __construct(array $usedFilters, array $usedTags, array $usedFunctions)
|
||||
{
|
||||
$this->usedFilters = $usedFilters;
|
||||
@@ -33,32 +40,22 @@ class CheckSecurityNode extends Node
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$tags = $filters = $functions = [];
|
||||
foreach (['tags', 'filters', 'functions'] as $type) {
|
||||
foreach ($this->{'used'.ucfirst($type)} as $name => $node) {
|
||||
if ($node instanceof Node) {
|
||||
${$type}[$name] = $node->getTemplateLine();
|
||||
} else {
|
||||
${$type}[$node] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$compiler
|
||||
->write("\n")
|
||||
->write("public function checkSecurity()\n")
|
||||
->write("{\n")
|
||||
->indent()
|
||||
->write('static $tags = ')->repr(array_filter($tags))->raw(";\n")
|
||||
->write('static $filters = ')->repr(array_filter($filters))->raw(";\n")
|
||||
->write('static $functions = ')->repr(array_filter($functions))->raw(";\n\n")
|
||||
->write('static $tags = ')->repr(array_filter($this->usedTags))->raw(";\n")
|
||||
->write('static $filters = ')->repr(array_filter($this->usedFilters))->raw(";\n")
|
||||
->write('static $functions = ')->repr(array_filter($this->usedFunctions))->raw(";\n\n")
|
||||
->write("try {\n")
|
||||
->indent()
|
||||
->write("\$this->sandbox->checkSecurity(\n")
|
||||
->indent()
|
||||
->write(!$tags ? "[],\n" : "['".implode("', '", array_keys($tags))."'],\n")
|
||||
->write(!$filters ? "[],\n" : "['".implode("', '", array_keys($filters))."'],\n")
|
||||
->write(!$functions ? "[]\n" : "['".implode("', '", array_keys($functions))."']\n")
|
||||
->write(!$this->usedTags ? "[],\n" : "['".implode("', '", array_keys($this->usedTags))."'],\n")
|
||||
->write(!$this->usedFilters ? "[],\n" : "['".implode("', '", array_keys($this->usedFilters))."'],\n")
|
||||
->write(!$this->usedFunctions ? "[],\n" : "['".implode("', '", array_keys($this->usedFunctions))."'],\n")
|
||||
->write("\$this->source\n")
|
||||
->outdent()
|
||||
->write(");\n")
|
||||
->outdent()
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
|
||||
@@ -24,11 +25,12 @@ use Twig\Node\Expression\AbstractExpression;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class CheckToStringNode extends AbstractExpression
|
||||
{
|
||||
public function __construct(AbstractExpression $expr)
|
||||
{
|
||||
parent::__construct(['expr' => $expr], [], $expr->getTemplateLine(), $expr->getNodeTag());
|
||||
parent::__construct(['expr' => $expr], [], $expr->getTemplateLine());
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
@@ -20,11 +21,12 @@ use Twig\Node\Expression\ConstantExpression;
|
||||
*
|
||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class DeprecatedNode extends Node
|
||||
{
|
||||
public function __construct(AbstractExpression $expr, int $lineno, string $tag = null)
|
||||
public function __construct(AbstractExpression $expr, int $lineno)
|
||||
{
|
||||
parent::__construct(['expr' => $expr], [], $lineno, $tag);
|
||||
parent::__construct(['expr' => $expr], [], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
@@ -33,21 +35,39 @@ class DeprecatedNode extends Node
|
||||
|
||||
$expr = $this->getNode('expr');
|
||||
|
||||
if ($expr instanceof ConstantExpression) {
|
||||
$compiler->write('@trigger_error(')
|
||||
->subcompile($expr);
|
||||
} else {
|
||||
if (!$expr instanceof ConstantExpression) {
|
||||
$varName = $compiler->getVarName();
|
||||
$compiler->write(sprintf('$%s = ', $varName))
|
||||
$compiler
|
||||
->write(\sprintf('$%s = ', $varName))
|
||||
->subcompile($expr)
|
||||
->raw(";\n")
|
||||
->write(sprintf('@trigger_error($%s', $varName));
|
||||
;
|
||||
}
|
||||
|
||||
$compiler->write('trigger_deprecation(');
|
||||
if ($this->hasNode('package')) {
|
||||
$compiler->subcompile($this->getNode('package'));
|
||||
} else {
|
||||
$compiler->raw("''");
|
||||
}
|
||||
$compiler->raw(', ');
|
||||
if ($this->hasNode('version')) {
|
||||
$compiler->subcompile($this->getNode('version'));
|
||||
} else {
|
||||
$compiler->raw("''");
|
||||
}
|
||||
$compiler->raw(', ');
|
||||
|
||||
if ($expr instanceof ConstantExpression) {
|
||||
$compiler->subcompile($expr);
|
||||
} else {
|
||||
$compiler->write(\sprintf('$%s', $varName));
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('.')
|
||||
->string(sprintf(' ("%s" at line %d).', $this->getTemplateName(), $this->getTemplateLine()))
|
||||
->raw(", E_USER_DEPRECATED);\n")
|
||||
->string(\sprintf(' in "%s" at line %d.', $this->getTemplateName(), $this->getTemplateLine()))
|
||||
->raw(");\n")
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
|
||||
@@ -19,11 +20,12 @@ use Twig\Node\Expression\AbstractExpression;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class DoNode extends Node
|
||||
{
|
||||
public function __construct(AbstractExpression $expr, int $lineno, string $tag = null)
|
||||
public function __construct(AbstractExpression $expr, int $lineno)
|
||||
{
|
||||
parent::__construct(['expr' => $expr], [], $lineno, $tag);
|
||||
parent::__construct(['expr' => $expr], [], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
@@ -20,21 +21,22 @@ use Twig\Node\Expression\ConstantExpression;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class EmbedNode extends IncludeNode
|
||||
{
|
||||
// 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(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null)
|
||||
public function __construct(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno)
|
||||
{
|
||||
parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag);
|
||||
parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno);
|
||||
|
||||
$this->setAttribute('name', $name);
|
||||
$this->setAttribute('index', $index);
|
||||
}
|
||||
|
||||
protected function addGetTemplate(Compiler $compiler): void
|
||||
protected function addGetTemplate(Compiler $compiler, string $template = ''): void
|
||||
{
|
||||
$compiler
|
||||
->write('$this->loadTemplate(')
|
||||
->raw('$this->loadTemplate(')
|
||||
->string($this->getAttribute('name'))
|
||||
->raw(', ')
|
||||
->repr($this->getTemplateName())
|
||||
@@ -44,5 +46,11 @@ class EmbedNode extends IncludeNode
|
||||
->string($this->getAttribute('index'))
|
||||
->raw(')')
|
||||
;
|
||||
if ($this->getAttribute('ignore_missing')) {
|
||||
$compiler
|
||||
->raw(";\n")
|
||||
->write(\sprintf("\$%s->getParent(\$context);\n", $template))
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,4 +21,23 @@ use Twig\Node\Node;
|
||||
*/
|
||||
abstract class AbstractExpression extends Node
|
||||
{
|
||||
public function isGenerator(): bool
|
||||
{
|
||||
return $this->hasAttribute('is_generator') && $this->getAttribute('is_generator');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setExplicitParentheses(): self
|
||||
{
|
||||
$this->setAttribute('with_parentheses', true);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasExplicitParentheses(): bool
|
||||
{
|
||||
return $this->hasAttribute('with_parentheses') && $this->getAttribute('with_parentheses');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\Unary\StringCastUnary;
|
||||
|
||||
class ArrayExpression extends AbstractExpression
|
||||
{
|
||||
@@ -55,7 +56,7 @@ class ArrayExpression extends AbstractExpression
|
||||
return false;
|
||||
}
|
||||
|
||||
public function addElement(AbstractExpression $value, AbstractExpression $key = null): void
|
||||
public function addElement(AbstractExpression $value, ?AbstractExpression $key = null): void
|
||||
{
|
||||
if (null === $key) {
|
||||
$key = new ConstantExpression(++$this->index, $value->getTemplateLine());
|
||||
@@ -70,7 +71,7 @@ class ArrayExpression extends AbstractExpression
|
||||
$needsArrayMergeSpread = \PHP_VERSION_ID < 80100 && $this->hasSpreadItem($keyValuePairs);
|
||||
|
||||
if ($needsArrayMergeSpread) {
|
||||
$compiler->raw('twig_array_merge(');
|
||||
$compiler->raw('CoreExtension::merge(');
|
||||
}
|
||||
$compiler->raw('[');
|
||||
$first = true;
|
||||
@@ -97,7 +98,17 @@ class ArrayExpression extends AbstractExpression
|
||||
$compiler->raw('...')->subcompile($pair['value']);
|
||||
++$nextIndex;
|
||||
} else {
|
||||
$key = $pair['key'] instanceof ConstantExpression ? $pair['key']->getAttribute('value') : null;
|
||||
$key = null;
|
||||
if ($pair['key'] instanceof NameExpression) {
|
||||
$pair['key'] = new StringCastUnary($pair['key'], $pair['key']->getTemplateLine());
|
||||
}
|
||||
if ($pair['key'] instanceof TempNameExpression) {
|
||||
$key = $pair['key']->getAttribute('name');
|
||||
$pair['key'] = new ConstantExpression($key, $pair['key']->getTemplateLine());
|
||||
}
|
||||
if ($pair['key'] instanceof ConstantExpression) {
|
||||
$key = $pair['key']->getAttribute('value');
|
||||
}
|
||||
|
||||
if ($nextIndex !== $key) {
|
||||
if (\is_int($key)) {
|
||||
|
||||
@@ -21,9 +21,9 @@ use Twig\Node\Node;
|
||||
*/
|
||||
class ArrowFunctionExpression extends AbstractExpression
|
||||
{
|
||||
public function __construct(AbstractExpression $expr, Node $names, $lineno, $tag = null)
|
||||
public function __construct(AbstractExpression $expr, Node $names, $lineno)
|
||||
{
|
||||
parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno, $tag);
|
||||
parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
|
||||
@@ -13,9 +13,25 @@
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\Variable\AssignContextVariable;
|
||||
|
||||
class AssignNameExpression extends NameExpression
|
||||
{
|
||||
public function __construct(string $name, int $lineno)
|
||||
{
|
||||
if (self::class === static::class) {
|
||||
trigger_deprecation('twig/twig', '3.15', 'The "%s" class is deprecated, use "%s" instead.', self::class, AssignContextVariable::class);
|
||||
}
|
||||
|
||||
// All names supported by ExpressionParser::parsePrimaryExpression() should be excluded
|
||||
if (\in_array(strtolower($name), ['true', 'false', 'none', 'null'])) {
|
||||
throw new SyntaxError(\sprintf('You cannot assign a value to "%s".', $name), $lineno);
|
||||
}
|
||||
|
||||
parent::__construct($name, $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
|
||||
@@ -18,8 +18,19 @@ use Twig\Node\Node;
|
||||
|
||||
abstract class AbstractBinary extends AbstractExpression
|
||||
{
|
||||
/**
|
||||
* @param AbstractExpression $left
|
||||
* @param AbstractExpression $right
|
||||
*/
|
||||
public function __construct(Node $left, Node $right, int $lineno)
|
||||
{
|
||||
if (!$left instanceof AbstractExpression) {
|
||||
trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "left" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, get_class($left));
|
||||
}
|
||||
if (!$right instanceof AbstractExpression) {
|
||||
trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "right" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, get_class($right));
|
||||
}
|
||||
|
||||
parent::__construct(['left' => $left, 'right' => $right], [], $lineno);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,11 @@ class EndsWithBinary extends AbstractBinary
|
||||
$left = $compiler->getVarName();
|
||||
$right = $compiler->getVarName();
|
||||
$compiler
|
||||
->raw(sprintf('(is_string($%s = ', $left))
|
||||
->raw(\sprintf('(is_string($%s = ', $left))
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(sprintf(') && is_string($%s = ', $right))
|
||||
->raw(\sprintf(') && is_string($%s = ', $right))
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(sprintf(') && str_ends_with($%1$s, $%2$s))', $left, $right))
|
||||
->raw(\sprintf(') && str_ends_with($%1$s, $%2$s))', $left, $right))
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class EqualBinary extends AbstractBinary
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('(0 === twig_compare(')
|
||||
->raw('(0 === CoreExtension::compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
|
||||
@@ -24,7 +24,7 @@ class GreaterBinary extends AbstractBinary
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('(1 === twig_compare(')
|
||||
->raw('(1 === CoreExtension::compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
|
||||
@@ -24,7 +24,7 @@ class GreaterEqualBinary extends AbstractBinary
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('(0 <= twig_compare(')
|
||||
->raw('(0 <= CoreExtension::compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
|
||||
@@ -18,7 +18,7 @@ class HasEveryBinary extends AbstractBinary
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('twig_array_every($this->env, ')
|
||||
->raw('CoreExtension::arrayEvery($this->env, ')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
|
||||
@@ -18,7 +18,7 @@ class HasSomeBinary extends AbstractBinary
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('twig_array_some($this->env, ')
|
||||
->raw('CoreExtension::arraySome($this->env, ')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
|
||||
@@ -18,7 +18,7 @@ class InBinary extends AbstractBinary
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('twig_in_filter(')
|
||||
->raw('CoreExtension::inFilter(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
|
||||
@@ -24,7 +24,7 @@ class LessBinary extends AbstractBinary
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('(-1 === twig_compare(')
|
||||
->raw('(-1 === CoreExtension::compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
|
||||
@@ -24,7 +24,7 @@ class LessEqualBinary extends AbstractBinary
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('(0 >= twig_compare(')
|
||||
->raw('(0 >= CoreExtension::compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
|
||||
@@ -12,13 +12,31 @@
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Node;
|
||||
|
||||
class MatchesBinary extends AbstractBinary
|
||||
{
|
||||
public function __construct(Node $left, Node $right, int $lineno)
|
||||
{
|
||||
if ($right instanceof ConstantExpression) {
|
||||
$regexp = $right->getAttribute('value');
|
||||
set_error_handler(static fn ($t, $m) => throw new SyntaxError(\sprintf('Regexp "%s" passed to "matches" is not valid: %s.', $regexp, substr($m, 14)), $lineno));
|
||||
try {
|
||||
preg_match($regexp, '');
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct($left, $right, $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('twig_matches(')
|
||||
->raw('CoreExtension::matches(')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('left'))
|
||||
|
||||
@@ -24,7 +24,7 @@ class NotEqualBinary extends AbstractBinary
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('(0 !== twig_compare(')
|
||||
->raw('(0 !== CoreExtension::compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
|
||||
@@ -18,7 +18,7 @@ class NotInBinary extends AbstractBinary
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('!twig_in_filter(')
|
||||
->raw('!CoreExtension::inFilter(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
|
||||
@@ -20,11 +20,11 @@ class StartsWithBinary extends AbstractBinary
|
||||
$left = $compiler->getVarName();
|
||||
$right = $compiler->getVarName();
|
||||
$compiler
|
||||
->raw(sprintf('(is_string($%s = ', $left))
|
||||
->raw(\sprintf('(is_string($%s = ', $left))
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(sprintf(') && is_string($%s = ', $right))
|
||||
->raw(\sprintf(') && is_string($%s = ', $right))
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(sprintf(') && str_starts_with($%1$s, $%2$s))', $left, $right))
|
||||
->raw(\sprintf(') && str_starts_with($%1$s, $%2$s))', $left, $right))
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,14 +22,21 @@ use Twig\Node\Node;
|
||||
*/
|
||||
class BlockReferenceExpression extends AbstractExpression
|
||||
{
|
||||
public function __construct(Node $name, ?Node $template, int $lineno, string $tag = null)
|
||||
/**
|
||||
* @param AbstractExpression $name
|
||||
*/
|
||||
public function __construct(Node $name, ?Node $template, int $lineno)
|
||||
{
|
||||
if (!$name instanceof AbstractExpression) {
|
||||
trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "node" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, get_class($name));
|
||||
}
|
||||
|
||||
$nodes = ['name' => $name];
|
||||
if (null !== $template) {
|
||||
$nodes['template'] = $template;
|
||||
}
|
||||
|
||||
parent::__construct($nodes, ['is_defined_test' => false, 'output' => false], $lineno, $tag);
|
||||
parent::__construct($nodes, ['is_defined_test' => false, 'output' => false], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
@@ -40,8 +47,9 @@ class BlockReferenceExpression extends AbstractExpression
|
||||
if ($this->getAttribute('output')) {
|
||||
$compiler->addDebugInfo($this);
|
||||
|
||||
$compiler->write('yield from ');
|
||||
$this
|
||||
->compileTemplateCall($compiler, 'displayBlock')
|
||||
->compileTemplateCall($compiler, 'yieldBlock')
|
||||
->raw(";\n");
|
||||
} else {
|
||||
$this->compileTemplateCall($compiler, 'renderBlock');
|
||||
@@ -65,7 +73,7 @@ class BlockReferenceExpression extends AbstractExpression
|
||||
;
|
||||
}
|
||||
|
||||
$compiler->raw(sprintf('->%s', $method));
|
||||
$compiler->raw(\sprintf('->unwrap()->%s', $method));
|
||||
|
||||
return $this->compileBlockArguments($compiler);
|
||||
}
|
||||
|
||||
@@ -15,40 +15,49 @@ use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Extension\ExtensionInterface;
|
||||
use Twig\Node\Node;
|
||||
use Twig\TwigCallableInterface;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
use Twig\TwigTest;
|
||||
use Twig\Util\CallableArgumentsExtractor;
|
||||
use Twig\Util\ReflectionCallable;
|
||||
|
||||
abstract class CallExpression extends AbstractExpression
|
||||
{
|
||||
private $reflector;
|
||||
private $reflector = null;
|
||||
|
||||
protected function compileCallable(Compiler $compiler)
|
||||
{
|
||||
$callable = $this->getAttribute('callable');
|
||||
$twigCallable = $this->getTwigCallable();
|
||||
$callable = $twigCallable->getCallable();
|
||||
|
||||
if (\is_string($callable) && !str_contains($callable, '::')) {
|
||||
$compiler->raw($callable);
|
||||
} else {
|
||||
[$r, $callable] = $this->reflectCallable($callable);
|
||||
$rc = $this->reflectCallable($twigCallable);
|
||||
$r = $rc->getReflector();
|
||||
$callable = $rc->getCallable();
|
||||
|
||||
if (\is_string($callable)) {
|
||||
$compiler->raw($callable);
|
||||
} elseif (\is_array($callable) && \is_string($callable[0])) {
|
||||
if (!$r instanceof \ReflectionMethod || $r->isStatic()) {
|
||||
$compiler->raw(sprintf('%s::%s', $callable[0], $callable[1]));
|
||||
$compiler->raw(\sprintf('%s::%s', $callable[0], $callable[1]));
|
||||
} else {
|
||||
$compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1]));
|
||||
$compiler->raw(\sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1]));
|
||||
}
|
||||
} elseif (\is_array($callable) && $callable[0] instanceof ExtensionInterface) {
|
||||
$class = \get_class($callable[0]);
|
||||
if (!$compiler->getEnvironment()->hasExtension($class)) {
|
||||
// Compile a non-optimized call to trigger a \Twig\Error\RuntimeError, which cannot be a compile-time error
|
||||
$compiler->raw(sprintf('$this->env->getExtension(\'%s\')', $class));
|
||||
$compiler->raw(\sprintf('$this->env->getExtension(\'%s\')', $class));
|
||||
} else {
|
||||
$compiler->raw(sprintf('$this->extensions[\'%s\']', ltrim($class, '\\')));
|
||||
$compiler->raw(\sprintf('$this->extensions[\'%s\']', ltrim($class, '\\')));
|
||||
}
|
||||
|
||||
$compiler->raw(sprintf('->%s', $callable[1]));
|
||||
$compiler->raw(\sprintf('->%s', $callable[1]));
|
||||
} else {
|
||||
$compiler->raw(sprintf('$this->env->get%s(\'%s\')->getCallable()', ucfirst($this->getAttribute('type')), $this->getAttribute('name')));
|
||||
$compiler->raw(\sprintf('$this->env->get%s(\'%s\')->getCallable()', ucfirst($this->getAttribute('type')), $twigCallable->getDynamicName()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,16 +66,30 @@ abstract class CallExpression extends AbstractExpression
|
||||
|
||||
protected function compileArguments(Compiler $compiler, $isArray = false): void
|
||||
{
|
||||
if (\func_num_args() >= 2) {
|
||||
trigger_deprecation('twig/twig', '3.11', 'Passing a second argument to "%s()" is deprecated.', __METHOD__);
|
||||
}
|
||||
|
||||
$compiler->raw($isArray ? '[' : '(');
|
||||
|
||||
$first = true;
|
||||
|
||||
if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
|
||||
$twigCallable = $this->getAttribute('twig_callable');
|
||||
|
||||
if ($twigCallable->needsCharset()) {
|
||||
$compiler->raw('$this->env->getCharset()');
|
||||
$first = false;
|
||||
}
|
||||
|
||||
if ($twigCallable->needsEnvironment()) {
|
||||
if (!$first) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
$compiler->raw('$this->env');
|
||||
$first = false;
|
||||
}
|
||||
|
||||
if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
|
||||
if ($twigCallable->needsContext()) {
|
||||
if (!$first) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
@@ -74,14 +97,12 @@ abstract class CallExpression extends AbstractExpression
|
||||
$first = false;
|
||||
}
|
||||
|
||||
if ($this->hasAttribute('arguments')) {
|
||||
foreach ($this->getAttribute('arguments') as $argument) {
|
||||
if (!$first) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
$compiler->string($argument);
|
||||
$first = false;
|
||||
foreach ($twigCallable->getArguments() as $argument) {
|
||||
if (!$first) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
$compiler->string($argument);
|
||||
$first = false;
|
||||
}
|
||||
|
||||
if ($this->hasNode('node')) {
|
||||
@@ -93,8 +114,7 @@ abstract class CallExpression extends AbstractExpression
|
||||
}
|
||||
|
||||
if ($this->hasNode('arguments')) {
|
||||
$callable = $this->getAttribute('callable');
|
||||
$arguments = $this->getArguments($callable, $this->getNode('arguments'));
|
||||
$arguments = (new CallableArgumentsExtractor($this, $this->getTwigCallable()))->extractArguments($this->getNode('arguments'));
|
||||
foreach ($arguments as $node) {
|
||||
if (!$first) {
|
||||
$compiler->raw(', ');
|
||||
@@ -107,8 +127,13 @@ abstract class CallExpression extends AbstractExpression
|
||||
$compiler->raw($isArray ? ']' : ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.12, use Twig\Util\CallableArgumentsExtractor::getArguments() instead
|
||||
*/
|
||||
protected function getArguments($callable, $arguments)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.12', 'The "%s()" method is deprecated, use Twig\Util\CallableArgumentsExtractor::getArguments() instead.', __METHOD__);
|
||||
|
||||
$callType = $this->getAttribute('type');
|
||||
$callName = $this->getAttribute('name');
|
||||
|
||||
@@ -119,28 +144,28 @@ abstract class CallExpression extends AbstractExpression
|
||||
$named = true;
|
||||
$name = $this->normalizeName($name);
|
||||
} elseif ($named) {
|
||||
throw new SyntaxError(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());
|
||||
throw new SyntaxError(\sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());
|
||||
}
|
||||
|
||||
$parameters[$name] = $node;
|
||||
}
|
||||
|
||||
$isVariadic = $this->hasAttribute('is_variadic') && $this->getAttribute('is_variadic');
|
||||
$isVariadic = $this->getAttribute('twig_callable')->isVariadic();
|
||||
if (!$named && !$isVariadic) {
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
if (!$callable) {
|
||||
if ($named) {
|
||||
$message = sprintf('Named arguments are not supported for %s "%s".', $callType, $callName);
|
||||
$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);
|
||||
$message = \sprintf('Arbitrary positional arguments are not supported for %s "%s".', $callType, $callName);
|
||||
}
|
||||
|
||||
throw new \LogicException($message);
|
||||
}
|
||||
|
||||
list($callableParameters, $isPhpVariadic) = $this->getCallableParameters($callable, $isVariadic);
|
||||
[$callableParameters, $isPhpVariadic] = $this->getCallableParameters($callable, $isVariadic);
|
||||
$arguments = [];
|
||||
$names = [];
|
||||
$missingArguments = [];
|
||||
@@ -160,11 +185,11 @@ abstract class CallExpression extends AbstractExpression
|
||||
|
||||
if (\array_key_exists($name, $parameters)) {
|
||||
if (\array_key_exists($pos, $parameters)) {
|
||||
throw new SyntaxError(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());
|
||||
throw new SyntaxError(\sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());
|
||||
}
|
||||
|
||||
if (\count($missingArguments)) {
|
||||
throw new SyntaxError(sprintf(
|
||||
throw new SyntaxError(\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)
|
||||
), $this->getTemplateLine(), $this->getSourceContext());
|
||||
@@ -183,13 +208,13 @@ abstract class CallExpression extends AbstractExpression
|
||||
} elseif ($callableParameter->isDefaultValueAvailable()) {
|
||||
$optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), -1);
|
||||
} elseif ($callableParameter->isOptional()) {
|
||||
if (empty($parameters)) {
|
||||
if (!$parameters) {
|
||||
break;
|
||||
} else {
|
||||
$missingArguments[] = $name;
|
||||
}
|
||||
} else {
|
||||
throw new SyntaxError(sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());
|
||||
throw new SyntaxError(\sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +235,7 @@ abstract class CallExpression extends AbstractExpression
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($parameters)) {
|
||||
if ($parameters) {
|
||||
$unknownParameter = null;
|
||||
foreach ($parameters as $parameter) {
|
||||
if ($parameter instanceof Node) {
|
||||
@@ -220,7 +245,7 @@ abstract class CallExpression extends AbstractExpression
|
||||
}
|
||||
|
||||
throw new SyntaxError(
|
||||
sprintf(
|
||||
\sprintf(
|
||||
'Unknown argument%s "%s" for %s "%s(%s)".',
|
||||
\count($parameters) > 1 ? 's' : '', implode('", "', array_keys($parameters)), $callType, $callName, implode(', ', $names)
|
||||
),
|
||||
@@ -232,90 +257,106 @@ abstract class CallExpression extends AbstractExpression
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.12
|
||||
*/
|
||||
protected function normalizeName(string $name): string
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.12', 'The "%s()" method is deprecated.', __METHOD__);
|
||||
|
||||
return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name));
|
||||
}
|
||||
|
||||
// To be removed in 4.0
|
||||
private function getCallableParameters($callable, bool $isVariadic): array
|
||||
{
|
||||
[$r, , $callableName] = $this->reflectCallable($callable);
|
||||
$twigCallable = $this->getAttribute('twig_callable');
|
||||
$rc = $this->reflectCallable($twigCallable);
|
||||
$r = $rc->getReflector();
|
||||
$callableName = $rc->getName();
|
||||
|
||||
$parameters = $r->getParameters();
|
||||
if ($this->hasNode('node')) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
|
||||
if ($twigCallable->needsCharset()) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
|
||||
if ($twigCallable->needsEnvironment()) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) {
|
||||
foreach ($this->getAttribute('arguments') as $argument) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
if ($twigCallable->needsContext()) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
foreach ($twigCallable->getArguments() as $argument) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
|
||||
$isPhpVariadic = false;
|
||||
if ($isVariadic) {
|
||||
$argument = end($parameters);
|
||||
$isArray = $argument && $argument->hasType() && 'array' === $argument->getType()->getName();
|
||||
$isArray = $argument && $argument->hasType() && $argument->getType() instanceof \ReflectionNamedType && 'array' === $argument->getType()->getName();
|
||||
if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) {
|
||||
array_pop($parameters);
|
||||
} elseif ($argument && $argument->isVariadic()) {
|
||||
array_pop($parameters);
|
||||
$isPhpVariadic = true;
|
||||
} else {
|
||||
throw new \LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $this->getAttribute('name')));
|
||||
throw new \LogicException(\sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $twigCallable->getName()));
|
||||
}
|
||||
}
|
||||
|
||||
return [$parameters, $isPhpVariadic];
|
||||
}
|
||||
|
||||
private function reflectCallable($callable)
|
||||
private function reflectCallable(TwigCallableInterface $callable): ReflectionCallable
|
||||
{
|
||||
if (null !== $this->reflector) {
|
||||
return $this->reflector;
|
||||
if (!$this->reflector) {
|
||||
$this->reflector = new ReflectionCallable($callable);
|
||||
}
|
||||
|
||||
if (\is_string($callable) && false !== $pos = strpos($callable, '::')) {
|
||||
$callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)];
|
||||
}
|
||||
return $this->reflector;
|
||||
}
|
||||
|
||||
if (\is_array($callable) && method_exists($callable[0], $callable[1])) {
|
||||
$r = new \ReflectionMethod($callable[0], $callable[1]);
|
||||
/**
|
||||
* Overrides the Twig callable based on attributes (as potentially, attributes changed between the creation and the compilation of the node).
|
||||
*
|
||||
* To be removed in 4.0 and replace by $this->getAttribute('twig_callable').
|
||||
*/
|
||||
private function getTwigCallable(): TwigCallableInterface
|
||||
{
|
||||
$current = $this->getAttribute('twig_callable');
|
||||
|
||||
return $this->reflector = [$r, $callable, $r->class.'::'.$r->name];
|
||||
}
|
||||
$this->setAttribute('twig_callable', match ($this->getAttribute('type')) {
|
||||
'test' => (new TwigTest(
|
||||
$this->getAttribute('name'),
|
||||
$this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(),
|
||||
[
|
||||
'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(),
|
||||
],
|
||||
))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ? $this->getAttribute('arguments') : $current->getArguments()),
|
||||
'function' => (new TwigFunction(
|
||||
$this->hasAttribute('name') ? $this->getAttribute('name') : $current->getName(),
|
||||
$this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(),
|
||||
[
|
||||
'needs_environment' => $this->hasAttribute('needs_environment') ? $this->getAttribute('needs_environment') : $current->needsEnvironment(),
|
||||
'needs_context' => $this->hasAttribute('needs_context') ? $this->getAttribute('needs_context') : $current->needsContext(),
|
||||
'needs_charset' => $this->hasAttribute('needs_charset') ? $this->getAttribute('needs_charset') : $current->needsCharset(),
|
||||
'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(),
|
||||
],
|
||||
))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ? $this->getAttribute('arguments') : $current->getArguments()),
|
||||
'filter' => (new TwigFilter(
|
||||
$this->getAttribute('name'),
|
||||
$this->hasAttribute('callable') ? $this->getAttribute('callable') : $current->getCallable(),
|
||||
[
|
||||
'needs_environment' => $this->hasAttribute('needs_environment') ? $this->getAttribute('needs_environment') : $current->needsEnvironment(),
|
||||
'needs_context' => $this->hasAttribute('needs_context') ? $this->getAttribute('needs_context') : $current->needsContext(),
|
||||
'needs_charset' => $this->hasAttribute('needs_charset') ? $this->getAttribute('needs_charset') : $current->needsCharset(),
|
||||
'is_variadic' => $this->hasAttribute('is_variadic') ? $this->getAttribute('is_variadic') : $current->isVariadic(),
|
||||
],
|
||||
))->withDynamicArguments($this->getAttribute('name'), $this->hasAttribute('dynamic_name') ? $this->getAttribute('dynamic_name') : $current->getDynamicName(), $this->hasAttribute('arguments') ? $this->getAttribute('arguments') : $current->getArguments()),
|
||||
});
|
||||
|
||||
$checkVisibility = $callable instanceof \Closure;
|
||||
try {
|
||||
$closure = \Closure::fromCallable($callable);
|
||||
} catch (\TypeError $e) {
|
||||
throw new \LogicException(sprintf('Callback for %s "%s" is not callable in the current scope.', $this->getAttribute('type'), $this->getAttribute('name')), 0, $e);
|
||||
}
|
||||
$r = new \ReflectionFunction($closure);
|
||||
|
||||
if (str_contains($r->name, '{closure}')) {
|
||||
return $this->reflector = [$r, $callable, 'Closure'];
|
||||
}
|
||||
|
||||
if ($object = $r->getClosureThis()) {
|
||||
$callable = [$object, $r->name];
|
||||
$callableName = get_debug_type($object).'::'.$r->name;
|
||||
} elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) {
|
||||
$callableName = $class->name.'::'.$r->name;
|
||||
} elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) {
|
||||
$callableName = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name;
|
||||
} else {
|
||||
$callable = $callableName = $r->name;
|
||||
}
|
||||
|
||||
if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) {
|
||||
$callable = $r->getClosure();
|
||||
}
|
||||
|
||||
return $this->reflector = [$r, $callable, $callableName];
|
||||
return $this->getAttribute('twig_callable');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class ConstantExpression extends AbstractExpression
|
||||
{
|
||||
public function __construct($value, int $lineno)
|
||||
|
||||
@@ -11,7 +11,11 @@
|
||||
|
||||
namespace Twig\Node\Expression\Filter;
|
||||
|
||||
use Twig\Attribute\FirstClassTwigCallableReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Extension\CoreExtension;
|
||||
use Twig\Node\EmptyNode;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ConditionalExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\FilterExpression;
|
||||
@@ -19,6 +23,8 @@ use Twig\Node\Expression\GetAttrExpression;
|
||||
use Twig\Node\Expression\NameExpression;
|
||||
use Twig\Node\Expression\Test\DefinedTest;
|
||||
use Twig\Node\Node;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigTest;
|
||||
|
||||
/**
|
||||
* Returns the value or the default value when it is undefined or empty.
|
||||
@@ -29,20 +35,34 @@ use Twig\Node\Node;
|
||||
*/
|
||||
class DefaultFilter extends FilterExpression
|
||||
{
|
||||
public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null)
|
||||
/**
|
||||
* @param AbstractExpression $node
|
||||
*/
|
||||
#[FirstClassTwigCallableReady]
|
||||
public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno)
|
||||
{
|
||||
$default = new FilterExpression($node, new ConstantExpression('default', $node->getTemplateLine()), $arguments, $node->getTemplateLine());
|
||||
if (!$node instanceof AbstractExpression) {
|
||||
trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "node" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, get_class($node));
|
||||
}
|
||||
|
||||
if ('default' === $filterName->getAttribute('value') && ($node instanceof NameExpression || $node instanceof GetAttrExpression)) {
|
||||
$test = new DefinedTest(clone $node, 'defined', new Node(), $node->getTemplateLine());
|
||||
$false = \count($arguments) ? $arguments->getNode(0) : new ConstantExpression('', $node->getTemplateLine());
|
||||
if ($filter instanceof TwigFilter) {
|
||||
$name = $filter->getName();
|
||||
$default = new FilterExpression($node, $filter, $arguments, $node->getTemplateLine());
|
||||
} else {
|
||||
$name = $filter->getAttribute('value');
|
||||
$default = new FilterExpression($node, new TwigFilter('default', [CoreExtension::class, 'default']), $arguments, $node->getTemplateLine());
|
||||
}
|
||||
|
||||
if ('default' === $name && ($node instanceof NameExpression || $node instanceof GetAttrExpression)) {
|
||||
$test = new DefinedTest(clone $node, new TwigTest('defined'), new EmptyNode(), $node->getTemplateLine());
|
||||
$false = \count($arguments) ? $arguments->getNode('0') : new ConstantExpression('', $node->getTemplateLine());
|
||||
|
||||
$node = new ConditionalExpression($test, $default, $false, $node->getTemplateLine());
|
||||
} else {
|
||||
$node = $default;
|
||||
}
|
||||
|
||||
parent::__construct($node, $filterName, $arguments, $lineno, $tag);
|
||||
parent::__construct($node, $filter, $arguments, $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
|
||||
@@ -12,28 +12,68 @@
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Attribute\FirstClassTwigCallableReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\NameDeprecation;
|
||||
use Twig\Node\Node;
|
||||
use Twig\TwigFilter;
|
||||
|
||||
class FilterExpression extends CallExpression
|
||||
{
|
||||
public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null)
|
||||
/**
|
||||
* @param AbstractExpression $node
|
||||
*/
|
||||
#[FirstClassTwigCallableReady]
|
||||
public function __construct(Node $node, TwigFilter|ConstantExpression $filter, Node $arguments, int $lineno)
|
||||
{
|
||||
parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], [], $lineno, $tag);
|
||||
if (!$node instanceof AbstractExpression) {
|
||||
trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "node" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, get_class($node));
|
||||
}
|
||||
|
||||
if ($filter instanceof TwigFilter) {
|
||||
$name = $filter->getName();
|
||||
$filterName = new ConstantExpression($name, $lineno);
|
||||
} else {
|
||||
$name = $filter->getAttribute('value');
|
||||
$filterName = $filter;
|
||||
trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of "TwigFilter" when creating a "%s" filter of type "%s" is deprecated.', $name, static::class);
|
||||
}
|
||||
|
||||
parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], ['name' => $name, 'type' => 'filter'], $lineno);
|
||||
|
||||
if ($filter instanceof TwigFilter) {
|
||||
$this->setAttribute('twig_callable', $filter);
|
||||
}
|
||||
|
||||
$this->deprecateNode('filter', new NameDeprecation('twig/twig', '3.12'));
|
||||
|
||||
$this->deprecateAttribute('needs_charset', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('needs_environment', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('needs_context', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('arguments', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('callable', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('is_variadic', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('dynamic_name', new NameDeprecation('twig/twig', '3.12'));
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$name = $this->getNode('filter')->getAttribute('value');
|
||||
$filter = $compiler->getEnvironment()->getFilter($name);
|
||||
$name = $this->getNode('filter', false)->getAttribute('value');
|
||||
if ($name !== $this->getAttribute('name')) {
|
||||
trigger_deprecation('twig/twig', '3.11', 'Changing the value of a "filter" node in a NodeVisitor class is not supported anymore.');
|
||||
$this->removeAttribute('twig_callable');
|
||||
}
|
||||
if ('raw' === $name) {
|
||||
trigger_deprecation('twig/twig', '3.11', 'Creating the "raw" filter via "FilterExpression" is deprecated; use "RawFilter" instead.');
|
||||
|
||||
$this->setAttribute('name', $name);
|
||||
$this->setAttribute('type', 'filter');
|
||||
$this->setAttribute('needs_environment', $filter->needsEnvironment());
|
||||
$this->setAttribute('needs_context', $filter->needsContext());
|
||||
$this->setAttribute('arguments', $filter->getArguments());
|
||||
$this->setAttribute('callable', $filter->getCallable());
|
||||
$this->setAttribute('is_variadic', $filter->isVariadic());
|
||||
$compiler->subcompile($this->getNode('node'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->hasAttribute('twig_callable')) {
|
||||
$this->setAttribute('twig_callable', $compiler->getEnvironment()->getFilter($name));
|
||||
}
|
||||
|
||||
$this->compileCallable($compiler);
|
||||
}
|
||||
|
||||
@@ -11,32 +11,57 @@
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Attribute\FirstClassTwigCallableReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\NameDeprecation;
|
||||
use Twig\Node\Node;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
class FunctionExpression extends CallExpression
|
||||
{
|
||||
public function __construct(string $name, Node $arguments, int $lineno)
|
||||
#[FirstClassTwigCallableReady]
|
||||
public function __construct(TwigFunction|string $function, Node $arguments, int $lineno)
|
||||
{
|
||||
parent::__construct(['arguments' => $arguments], ['name' => $name, 'is_defined_test' => false], $lineno);
|
||||
if ($function instanceof TwigFunction) {
|
||||
$name = $function->getName();
|
||||
} else {
|
||||
$name = $function;
|
||||
trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of "TwigFunction" when creating a "%s" function of type "%s" is deprecated.', $name, static::class);
|
||||
}
|
||||
|
||||
parent::__construct(['arguments' => $arguments], ['name' => $name, 'type' => 'function', 'is_defined_test' => false], $lineno);
|
||||
|
||||
if ($function instanceof TwigFunction) {
|
||||
$this->setAttribute('twig_callable', $function);
|
||||
}
|
||||
|
||||
$this->deprecateAttribute('needs_charset', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('needs_environment', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('needs_context', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('arguments', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('callable', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('is_variadic', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('dynamic_name', new NameDeprecation('twig/twig', '3.12'));
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
$name = $this->getAttribute('name');
|
||||
$function = $compiler->getEnvironment()->getFunction($name);
|
||||
|
||||
$this->setAttribute('name', $name);
|
||||
$this->setAttribute('type', 'function');
|
||||
$this->setAttribute('needs_environment', $function->needsEnvironment());
|
||||
$this->setAttribute('needs_context', $function->needsContext());
|
||||
$this->setAttribute('arguments', $function->getArguments());
|
||||
$callable = $function->getCallable();
|
||||
if ('constant' === $name && $this->getAttribute('is_defined_test')) {
|
||||
$callable = 'twig_constant_is_defined';
|
||||
if ($this->hasAttribute('twig_callable')) {
|
||||
$name = $this->getAttribute('twig_callable')->getName();
|
||||
if ($name !== $this->getAttribute('name')) {
|
||||
trigger_deprecation('twig/twig', '3.12', 'Changing the value of a "function" node in a NodeVisitor class is not supported anymore.');
|
||||
$this->removeAttribute('twig_callable');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->hasAttribute('twig_callable')) {
|
||||
$this->setAttribute('twig_callable', $compiler->getEnvironment()->getFunction($name));
|
||||
}
|
||||
|
||||
if ('constant' === $name && $this->getAttribute('is_defined_test')) {
|
||||
$this->getNode('arguments')->setNode('checkDefined', new ConstantExpression(true, $this->getTemplateLine()));
|
||||
}
|
||||
$this->setAttribute('callable', $callable);
|
||||
$this->setAttribute('is_variadic', $function->isVariadic());
|
||||
|
||||
$this->compileCallable($compiler);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ use Twig\Template;
|
||||
|
||||
class GetAttrExpression extends AbstractExpression
|
||||
{
|
||||
|
||||
/**
|
||||
* @param ArrayExpression|NameExpression|null $arguments
|
||||
*/
|
||||
public function __construct(AbstractExpression $node, AbstractExpression $attribute, ?AbstractExpression $arguments, string $type, int $lineno)
|
||||
{
|
||||
$nodes = ['node' => $node, 'attribute' => $attribute];
|
||||
@@ -25,12 +29,17 @@ class GetAttrExpression extends AbstractExpression
|
||||
$nodes['arguments'] = $arguments;
|
||||
}
|
||||
|
||||
if ($arguments && !$arguments instanceof ArrayExpression && !$arguments instanceof NameExpression) {
|
||||
trigger_deprecation('twig/twig', '3.15', \sprintf('Not passing a "%s" instance as the "arguments" argument of the "%s" constructor is deprecated ("%s" given).', ArrayExpression::class, static::class, $arguments::class));
|
||||
}
|
||||
|
||||
parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'optimizable' => true], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$env = $compiler->getEnvironment();
|
||||
$arrayAccessSandbox = false;
|
||||
|
||||
// optimize array calls
|
||||
if (
|
||||
@@ -44,20 +53,38 @@ class GetAttrExpression extends AbstractExpression
|
||||
->raw('(('.$var.' = ')
|
||||
->subcompile($this->getNode('node'))
|
||||
->raw(') && is_array(')
|
||||
->raw($var)
|
||||
->raw($var);
|
||||
|
||||
if (!$env->hasExtension(SandboxExtension::class)) {
|
||||
$compiler
|
||||
->raw(') || ')
|
||||
->raw($var)
|
||||
->raw(' instanceof ArrayAccess ? (')
|
||||
->raw($var)
|
||||
->raw('[')
|
||||
->subcompile($this->getNode('attribute'))
|
||||
->raw('] ?? null) : null)')
|
||||
;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$arrayAccessSandbox = true;
|
||||
|
||||
$compiler
|
||||
->raw(') || ')
|
||||
->raw($var)
|
||||
->raw(' instanceof ArrayAccess ? (')
|
||||
->raw(' instanceof ArrayAccess && in_array(')
|
||||
->raw($var.'::class')
|
||||
->raw(', CoreExtension::ARRAY_LIKE_CLASSES, true) ? (')
|
||||
->raw($var)
|
||||
->raw('[')
|
||||
->subcompile($this->getNode('attribute'))
|
||||
->raw('] ?? null) : null)')
|
||||
->raw('] ?? null) : ')
|
||||
;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler->raw('twig_get_attribute($this->env, $this->source, ');
|
||||
$compiler->raw('CoreExtension::getAttribute($this->env, $this->source, ');
|
||||
|
||||
if ($this->getAttribute('ignore_strict_check')) {
|
||||
$this->getNode('node')->setAttribute('ignore_strict_check', true);
|
||||
@@ -83,5 +110,9 @@ class GetAttrExpression extends AbstractExpression
|
||||
->raw(', ')->repr($this->getNode('node')->getTemplateLine())
|
||||
->raw(')')
|
||||
;
|
||||
|
||||
if ($arrayAccessSandbox) {
|
||||
$compiler->raw(')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,17 +19,21 @@ use Twig\Node\Node;
|
||||
*/
|
||||
final class InlinePrint extends AbstractExpression
|
||||
{
|
||||
/**
|
||||
* @param AbstractExpression $node
|
||||
*/
|
||||
public function __construct(Node $node, int $lineno)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.16', \sprintf('The "%s" class is deprecated with no replacement.', static::class));
|
||||
|
||||
parent::__construct(['node' => $node], [], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('print (')
|
||||
->raw('yield ')
|
||||
->subcompile($this->getNode('node'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ class MethodCallExpression extends AbstractExpression
|
||||
{
|
||||
public function __construct(AbstractExpression $node, string $method, ArrayExpression $arguments, int $lineno)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.15', 'The "%s" class is deprecated, use "%s" instead.', __CLASS__, MacroReferenceExpression::class);
|
||||
|
||||
parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false, 'is_defined_test' => false], $lineno);
|
||||
|
||||
if ($node instanceof NameExpression) {
|
||||
@@ -39,23 +41,13 @@ class MethodCallExpression extends AbstractExpression
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('twig_call_macro($macros[')
|
||||
->raw('CoreExtension::callMacro($macros[')
|
||||
->repr($this->getNode('node')->getAttribute('name'))
|
||||
->raw('], ')
|
||||
->repr($this->getAttribute('method'))
|
||||
->raw(', [')
|
||||
;
|
||||
$first = true;
|
||||
foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) {
|
||||
if (!$first) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
$first = false;
|
||||
|
||||
$compiler->subcompile($pair['value']);
|
||||
}
|
||||
$compiler
|
||||
->raw('], ')
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('arguments'))
|
||||
->raw(', ')
|
||||
->repr($this->getTemplateLine())
|
||||
->raw(', $context, $this->getSourceContext())');
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\Variable\ContextVariable;
|
||||
|
||||
class NameExpression extends AbstractExpression
|
||||
{
|
||||
@@ -24,6 +25,10 @@ class NameExpression extends AbstractExpression
|
||||
|
||||
public function __construct(string $name, int $lineno)
|
||||
{
|
||||
if (self::class === static::class) {
|
||||
trigger_deprecation('twig/twig', '3.15', 'The "%s" class is deprecated, use "%s" instead.', self::class, ContextVariable::class);
|
||||
}
|
||||
|
||||
parent::__construct([], ['name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false], $lineno);
|
||||
}
|
||||
|
||||
@@ -34,7 +39,7 @@ class NameExpression extends AbstractExpression
|
||||
$compiler->addDebugInfo($this);
|
||||
|
||||
if ($this->getAttribute('is_defined_test')) {
|
||||
if ($this->isSpecial()) {
|
||||
if (isset($this->specialVars[$name])) {
|
||||
$compiler->repr(true);
|
||||
} elseif (\PHP_VERSION_ID >= 70400) {
|
||||
$compiler
|
||||
@@ -51,7 +56,7 @@ class NameExpression extends AbstractExpression
|
||||
->raw(', $context))')
|
||||
;
|
||||
}
|
||||
} elseif ($this->isSpecial()) {
|
||||
} elseif (isset($this->specialVars[$name])) {
|
||||
$compiler->raw($this->specialVars[$name]);
|
||||
} elseif ($this->getAttribute('always_defined')) {
|
||||
$compiler
|
||||
@@ -85,13 +90,23 @@ class NameExpression extends AbstractExpression
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.11 (to be removed in 4.0)
|
||||
*/
|
||||
public function isSpecial()
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.11', 'The "%s()" method is deprecated and will be removed in Twig 4.0.', __METHOD__);
|
||||
|
||||
return isset($this->specialVars[$this->getAttribute('name')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.11 (to be removed in 4.0)
|
||||
*/
|
||||
public function isSimple()
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.11', 'The "%s()" method is deprecated and will be removed in Twig 4.0.', __METHOD__);
|
||||
|
||||
return !$this->isSpecial() && !$this->getAttribute('is_defined_test');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,22 +12,35 @@
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\EmptyNode;
|
||||
use Twig\Node\Expression\Binary\AndBinary;
|
||||
use Twig\Node\Expression\Test\DefinedTest;
|
||||
use Twig\Node\Expression\Test\NullTest;
|
||||
use Twig\Node\Expression\Unary\NotUnary;
|
||||
use Twig\Node\Node;
|
||||
use Twig\TwigTest;
|
||||
|
||||
class NullCoalesceExpression extends ConditionalExpression
|
||||
{
|
||||
/**
|
||||
* @param AbstractExpression $left
|
||||
* @param AbstractExpression $right
|
||||
*/
|
||||
public function __construct(Node $left, Node $right, int $lineno)
|
||||
{
|
||||
$test = new DefinedTest(clone $left, 'defined', new Node(), $left->getTemplateLine());
|
||||
if (!$left instanceof AbstractExpression) {
|
||||
trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "left" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, get_class($left));
|
||||
}
|
||||
if (!$right instanceof AbstractExpression) {
|
||||
trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "right" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, get_class($right));
|
||||
}
|
||||
|
||||
$test = new DefinedTest(clone $left, new TwigTest('defined'), new EmptyNode(), $left->getTemplateLine());
|
||||
// for "block()", we don't need the null test as the return value is always a string
|
||||
if (!$left instanceof BlockReferenceExpression) {
|
||||
$test = new AndBinary(
|
||||
$test,
|
||||
new NotUnary(new NullTest($left, 'null', new Node(), $left->getTemplateLine()), $left->getTemplateLine()),
|
||||
new NotUnary(new NullTest($left, new TwigTest('null'), new EmptyNode(), $left->getTemplateLine()), $left->getTemplateLine()),
|
||||
$left->getTemplateLine()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ use Twig\Compiler;
|
||||
*/
|
||||
class ParentExpression extends AbstractExpression
|
||||
{
|
||||
public function __construct(string $name, int $lineno, string $tag = null)
|
||||
public function __construct(string $name, int $lineno)
|
||||
{
|
||||
parent::__construct([], ['output' => false, 'name' => $name], $lineno, $tag);
|
||||
parent::__construct([], ['output' => false, 'name' => $name], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
@@ -31,7 +31,7 @@ class ParentExpression extends AbstractExpression
|
||||
if ($this->getAttribute('output')) {
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write('$this->displayParentBlock(')
|
||||
->write('yield from $this->yieldParentBlock(')
|
||||
->string($this->getAttribute('name'))
|
||||
->raw(", \$context, \$blocks);\n")
|
||||
;
|
||||
|
||||
@@ -12,20 +12,38 @@
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
|
||||
class TempNameExpression extends AbstractExpression
|
||||
{
|
||||
public function __construct(string $name, int $lineno)
|
||||
public const RESERVED_NAMES = ['varargs', 'context', 'macros', 'blocks', 'this'];
|
||||
|
||||
public function __construct(string|int|null $name, int $lineno)
|
||||
{
|
||||
// All names supported by ExpressionParser::parsePrimaryExpression() should be excluded
|
||||
if ($name && \in_array(strtolower($name), ['true', 'false', 'none', 'null'])) {
|
||||
throw new SyntaxError(\sprintf('You cannot assign a value to "%s".', $name), $lineno);
|
||||
}
|
||||
|
||||
if (self::class === static::class) {
|
||||
trigger_deprecation('twig/twig', '3.15', 'The "%s" class is deprecated.', self::class);
|
||||
}
|
||||
|
||||
if (null !== $name && (is_int($name) || ctype_digit($name))) {
|
||||
$name = (int) $name;
|
||||
} elseif (in_array($name, self::RESERVED_NAMES)) {
|
||||
$name = "\u{035C}".$name;
|
||||
}
|
||||
|
||||
parent::__construct([], ['name' => $name], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('$_')
|
||||
->raw($this->getAttribute('name'))
|
||||
->raw('_')
|
||||
;
|
||||
if (null === $this->getAttribute('name')) {
|
||||
$this->setAttribute('name', $compiler->getVarName());
|
||||
}
|
||||
|
||||
$compiler->raw('$'.$this->getAttribute('name'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,16 +33,16 @@ class ConstantTest extends TestExpression
|
||||
->raw(' === constant(')
|
||||
;
|
||||
|
||||
if ($this->getNode('arguments')->hasNode(1)) {
|
||||
if ($this->getNode('arguments')->hasNode('1')) {
|
||||
$compiler
|
||||
->raw('get_class(')
|
||||
->subcompile($this->getNode('arguments')->getNode(1))
|
||||
->subcompile($this->getNode('arguments')->getNode('1'))
|
||||
->raw(')."::".')
|
||||
;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->subcompile($this->getNode('arguments')->getNode(0))
|
||||
->subcompile($this->getNode('arguments')->getNode('0'))
|
||||
->raw('))')
|
||||
;
|
||||
}
|
||||
|
||||
@@ -11,17 +11,21 @@
|
||||
|
||||
namespace Twig\Node\Expression\Test;
|
||||
|
||||
use Twig\Attribute\FirstClassTwigCallableReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\BlockReferenceExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\FunctionExpression;
|
||||
use Twig\Node\Expression\GetAttrExpression;
|
||||
use Twig\Node\Expression\MacroReferenceExpression;
|
||||
use Twig\Node\Expression\MethodCallExpression;
|
||||
use Twig\Node\Expression\NameExpression;
|
||||
use Twig\Node\Expression\TestExpression;
|
||||
use Twig\Node\Node;
|
||||
use Twig\TwigTest;
|
||||
|
||||
/**
|
||||
* Checks if a variable is defined in the current context.
|
||||
@@ -35,8 +39,16 @@ use Twig\Node\Node;
|
||||
*/
|
||||
class DefinedTest extends TestExpression
|
||||
{
|
||||
public function __construct(Node $node, string $name, ?Node $arguments, int $lineno)
|
||||
/**
|
||||
* @param AbstractExpression $node
|
||||
*/
|
||||
#[FirstClassTwigCallableReady]
|
||||
public function __construct(Node $node, TwigTest|string $name, ?Node $arguments, int $lineno)
|
||||
{
|
||||
if (!$node instanceof AbstractExpression) {
|
||||
trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "node" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, get_class($node));
|
||||
}
|
||||
|
||||
if ($node instanceof NameExpression) {
|
||||
$node->setAttribute('is_defined_test', true);
|
||||
} elseif ($node instanceof GetAttrExpression) {
|
||||
@@ -44,6 +56,8 @@ class DefinedTest extends TestExpression
|
||||
$this->changeIgnoreStrictCheck($node);
|
||||
} elseif ($node instanceof BlockReferenceExpression) {
|
||||
$node->setAttribute('is_defined_test', true);
|
||||
} elseif ($node instanceof MacroReferenceExpression) {
|
||||
$node->setAttribute('is_defined_test', true);
|
||||
} elseif ($node instanceof FunctionExpression && 'constant' === $node->getAttribute('name')) {
|
||||
$node->setAttribute('is_defined_test', true);
|
||||
} elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) {
|
||||
@@ -54,6 +68,10 @@ class DefinedTest extends TestExpression
|
||||
throw new SyntaxError('The "defined" test only works with simple variables.', $lineno);
|
||||
}
|
||||
|
||||
if (\is_string($name) && 'defined' !== $name) {
|
||||
trigger_deprecation('twig/twig', '3.12', 'Creating a "DefinedTest" instance with a test name that is not "defined" is deprecated.');
|
||||
}
|
||||
|
||||
parent::__construct($node, $name, $arguments, $lineno);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class DivisiblebyTest extends TestExpression
|
||||
->raw('(0 == ')
|
||||
->subcompile($this->getNode('node'))
|
||||
->raw(' % ')
|
||||
->subcompile($this->getNode('arguments')->getNode(0))
|
||||
->subcompile($this->getNode('arguments')->getNode('0'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class SameasTest extends TestExpression
|
||||
->raw('(')
|
||||
->subcompile($this->getNode('node'))
|
||||
->raw(' === ')
|
||||
->subcompile($this->getNode('arguments')->getNode(0))
|
||||
->subcompile($this->getNode('arguments')->getNode('0'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
@@ -11,31 +11,62 @@
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Attribute\FirstClassTwigCallableReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\NameDeprecation;
|
||||
use Twig\Node\Node;
|
||||
use Twig\TwigTest;
|
||||
|
||||
class TestExpression extends CallExpression
|
||||
{
|
||||
public function __construct(Node $node, string $name, ?Node $arguments, int $lineno)
|
||||
#[FirstClassTwigCallableReady]
|
||||
/**
|
||||
* @param AbstractExpression $node
|
||||
*/
|
||||
public function __construct(Node $node, string|TwigTest $test, ?Node $arguments, int $lineno)
|
||||
{
|
||||
if (!$node instanceof AbstractExpression) {
|
||||
trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "node" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, get_class($node));
|
||||
}
|
||||
|
||||
$nodes = ['node' => $node];
|
||||
if (null !== $arguments) {
|
||||
$nodes['arguments'] = $arguments;
|
||||
}
|
||||
|
||||
parent::__construct($nodes, ['name' => $name], $lineno);
|
||||
if ($test instanceof TwigTest) {
|
||||
$name = $test->getName();
|
||||
} else {
|
||||
$name = $test;
|
||||
trigger_deprecation('twig/twig', '3.12', 'Not passing an instance of "TwigTest" when creating a "%s" test of type "%s" is deprecated.', $name, static::class);
|
||||
}
|
||||
|
||||
parent::__construct($nodes, ['name' => $name, 'type' => 'test'], $lineno);
|
||||
|
||||
if ($test instanceof TwigTest) {
|
||||
$this->setAttribute('twig_callable', $test);
|
||||
}
|
||||
|
||||
$this->deprecateAttribute('arguments', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('callable', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('is_variadic', new NameDeprecation('twig/twig', '3.12'));
|
||||
$this->deprecateAttribute('dynamic_name', new NameDeprecation('twig/twig', '3.12'));
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$name = $this->getAttribute('name');
|
||||
$test = $compiler->getEnvironment()->getTest($name);
|
||||
if ($this->hasAttribute('twig_callable')) {
|
||||
$name = $this->getAttribute('twig_callable')->getName();
|
||||
if ($name !== $this->getAttribute('name')) {
|
||||
trigger_deprecation('twig/twig', '3.12', 'Changing the value of a "test" node in a NodeVisitor class is not supported anymore.');
|
||||
$this->removeAttribute('twig_callable');
|
||||
}
|
||||
}
|
||||
|
||||
$this->setAttribute('name', $name);
|
||||
$this->setAttribute('type', 'test');
|
||||
$this->setAttribute('arguments', $test->getArguments());
|
||||
$this->setAttribute('callable', $test->getCallable());
|
||||
$this->setAttribute('is_variadic', $test->isVariadic());
|
||||
if (!$this->hasAttribute('twig_callable')) {
|
||||
$this->setAttribute('twig_callable', $compiler->getEnvironment()->getTest($this->getAttribute('name')));
|
||||
}
|
||||
|
||||
$this->compileCallable($compiler);
|
||||
}
|
||||
|
||||
@@ -18,16 +18,30 @@ use Twig\Node\Node;
|
||||
|
||||
abstract class AbstractUnary extends AbstractExpression
|
||||
{
|
||||
/**
|
||||
* @param AbstractExpression $node
|
||||
*/
|
||||
public function __construct(Node $node, int $lineno)
|
||||
{
|
||||
parent::__construct(['node' => $node], [], $lineno);
|
||||
if (!$node instanceof AbstractExpression) {
|
||||
trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance argument to "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, get_class($node));
|
||||
}
|
||||
|
||||
parent::__construct(['node' => $node], ['with_parentheses' => false], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler->raw(' ');
|
||||
if ($this->hasExplicitParentheses()) {
|
||||
$compiler->raw('(');
|
||||
} else {
|
||||
$compiler->raw(' ');
|
||||
}
|
||||
$this->operator($compiler);
|
||||
$compiler->subcompile($this->getNode('node'));
|
||||
if ($this->hasExplicitParentheses()) {
|
||||
$compiler->raw(')');
|
||||
}
|
||||
}
|
||||
|
||||
abstract public function operator(Compiler $compiler): Compiler;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
@@ -18,18 +19,22 @@ use Twig\Compiler;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class FlushNode extends Node
|
||||
{
|
||||
public function __construct(int $lineno, string $tag)
|
||||
public function __construct(int $lineno)
|
||||
{
|
||||
parent::__construct([], [], $lineno, $tag);
|
||||
parent::__construct([], [], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write("flush();\n")
|
||||
;
|
||||
$compiler->addDebugInfo($this);
|
||||
|
||||
if ($compiler->getEnvironment()->useYield()) {
|
||||
$compiler->write("yield '';\n");
|
||||
}
|
||||
|
||||
$compiler->write("flush();\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
@@ -18,11 +19,12 @@ use Twig\Compiler;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class ForLoopNode extends Node
|
||||
{
|
||||
public function __construct(int $lineno, string $tag = null)
|
||||
public function __construct(int $lineno)
|
||||
{
|
||||
parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno, $tag);
|
||||
parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
@@ -36,7 +38,7 @@ class ForLoopNode extends Node
|
||||
->write("++\$context['loop']['index0'];\n")
|
||||
->write("++\$context['loop']['index'];\n")
|
||||
->write("\$context['loop']['first'] = false;\n")
|
||||
->write("if (isset(\$context['loop']['length'])) {\n")
|
||||
->write("if (isset(\$context['loop']['revindex0'], \$context['loop']['revindex'])) {\n")
|
||||
->indent()
|
||||
->write("--\$context['loop']['revindex0'];\n")
|
||||
->write("--\$context['loop']['revindex'];\n")
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\AssignNameExpression;
|
||||
@@ -21,20 +22,21 @@ use Twig\Node\Expression\AssignNameExpression;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class ForNode extends Node
|
||||
{
|
||||
private $loop;
|
||||
|
||||
public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, ?Node $ifexpr, Node $body, ?Node $else, int $lineno, string $tag = null)
|
||||
public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, ?Node $ifexpr, Node $body, ?Node $else, int $lineno)
|
||||
{
|
||||
$body = new Node([$body, $this->loop = new ForLoopNode($lineno, $tag)]);
|
||||
$body = new Nodes([$body, $this->loop = new ForLoopNode($lineno)]);
|
||||
|
||||
$nodes = ['key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body];
|
||||
if (null !== $else) {
|
||||
$nodes['else'] = $else;
|
||||
}
|
||||
|
||||
parent::__construct($nodes, ['with_loop' => true], $lineno, $tag);
|
||||
parent::__construct($nodes, ['with_loop' => true], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
@@ -42,7 +44,7 @@ class ForNode extends Node
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write("\$context['_parent'] = \$context;\n")
|
||||
->write("\$context['_seq'] = twig_ensure_traversable(")
|
||||
->write("\$context['_seq'] = CoreExtension::ensureTraversable(")
|
||||
->subcompile($this->getNode('seq'))
|
||||
->raw(");\n")
|
||||
;
|
||||
@@ -99,7 +101,14 @@ class ForNode extends Node
|
||||
$compiler->write("\$_parent = \$context['_parent'];\n");
|
||||
|
||||
// remove some "private" loop variables (needed for nested loops)
|
||||
$compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\'], $context[\'loop\']);'."\n");
|
||||
$compiler->write('unset($context[\'_seq\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\']');
|
||||
if ($this->hasNode('else')) {
|
||||
$compiler->raw(', $context[\'_iterated\']');
|
||||
}
|
||||
if ($this->getAttribute('with_loop')) {
|
||||
$compiler->raw(', $context[\'loop\']');
|
||||
}
|
||||
$compiler->raw(");\n");
|
||||
|
||||
// keep the values set in the inner context for variables defined in the outer context
|
||||
$compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n");
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
@@ -19,16 +20,17 @@ use Twig\Compiler;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class IfNode extends Node
|
||||
{
|
||||
public function __construct(Node $tests, ?Node $else, int $lineno, string $tag = null)
|
||||
public function __construct(Node $tests, ?Node $else, int $lineno)
|
||||
{
|
||||
$nodes = ['tests' => $tests];
|
||||
if (null !== $else) {
|
||||
$nodes['else'] = $else;
|
||||
}
|
||||
|
||||
parent::__construct($nodes, [], $lineno, $tag);
|
||||
parent::__construct($nodes, [], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
@@ -47,13 +49,13 @@ class IfNode extends Node
|
||||
}
|
||||
|
||||
$compiler
|
||||
->subcompile($this->getNode('tests')->getNode($i))
|
||||
->subcompile($this->getNode('tests')->getNode((string) $i))
|
||||
->raw(") {\n")
|
||||
->indent()
|
||||
;
|
||||
// The node might not exists if the content is empty
|
||||
if ($this->getNode('tests')->hasNode($i + 1)) {
|
||||
$compiler->subcompile($this->getNode('tests')->getNode($i + 1));
|
||||
if ($this->getNode('tests')->hasNode((string) ($i + 1))) {
|
||||
$compiler->subcompile($this->getNode('tests')->getNode((string) ($i + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,38 +11,38 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\NameExpression;
|
||||
use Twig\Node\Expression\Variable\AssignTemplateVariable;
|
||||
|
||||
/**
|
||||
* Represents an import node.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class ImportNode extends Node
|
||||
{
|
||||
public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, string $tag = null, bool $global = true)
|
||||
public function __construct(AbstractExpression $expr, AbstractExpression|AssignTemplateVariable $var, int $lineno)
|
||||
{
|
||||
parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno, $tag);
|
||||
if (\func_num_args() > 3) {
|
||||
trigger_deprecation('twig/twig', '3.15', \sprintf('Passing more than 3 arguments to "%s()" is deprecated.', __METHOD__));
|
||||
}
|
||||
|
||||
if (!$var instanceof AssignTemplateVariable) {
|
||||
trigger_deprecation('twig/twig', '3.15', \sprintf('Passing a "%s" instance as the second argument of "%s" is deprecated, pass a "%s" instead.', $var::class, __CLASS__, AssignTemplateVariable::class));
|
||||
|
||||
$var = new AssignTemplateVariable($var->getAttribute('name'), $lineno);
|
||||
}
|
||||
|
||||
parent::__construct(['expr' => $expr, 'var' => $var], [], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write('$macros[')
|
||||
->repr($this->getNode('var')->getAttribute('name'))
|
||||
->raw('] = ')
|
||||
;
|
||||
|
||||
if ($this->getAttribute('global')) {
|
||||
$compiler
|
||||
->raw('$this->macros[')
|
||||
->repr($this->getNode('var')->getAttribute('name'))
|
||||
->raw('] = ')
|
||||
;
|
||||
}
|
||||
$compiler->subcompile($this->getNode('var'));
|
||||
|
||||
if ($this->getNode('expr') instanceof NameExpression && '_self' === $this->getNode('expr')->getAttribute('name')) {
|
||||
$compiler->raw('$this');
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
|
||||
@@ -20,16 +21,17 @@ use Twig\Node\Expression\AbstractExpression;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class IncludeNode extends Node implements NodeOutputInterface
|
||||
{
|
||||
public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null)
|
||||
public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno)
|
||||
{
|
||||
$nodes = ['expr' => $expr];
|
||||
if (null !== $variables) {
|
||||
$nodes['variables'] = $variables;
|
||||
}
|
||||
|
||||
parent::__construct($nodes, ['only' => $only, 'ignore_missing' => $ignoreMissing], $lineno, $tag);
|
||||
parent::__construct($nodes, ['only' => $only, 'ignore_missing' => $ignoreMissing], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
@@ -40,13 +42,12 @@ class IncludeNode extends Node implements NodeOutputInterface
|
||||
$template = $compiler->getVarName();
|
||||
|
||||
$compiler
|
||||
->write(sprintf("$%s = null;\n", $template))
|
||||
->write("try {\n")
|
||||
->indent()
|
||||
->write(sprintf('$%s = ', $template))
|
||||
->write(\sprintf('$%s = ', $template))
|
||||
;
|
||||
|
||||
$this->addGetTemplate($compiler);
|
||||
$this->addGetTemplate($compiler, $template);
|
||||
|
||||
$compiler
|
||||
->raw(";\n")
|
||||
@@ -54,12 +55,14 @@ class IncludeNode extends Node implements NodeOutputInterface
|
||||
->write("} catch (LoaderError \$e) {\n")
|
||||
->indent()
|
||||
->write("// ignore missing template\n")
|
||||
->write(\sprintf("\$$template = null;\n", $template))
|
||||
->outdent()
|
||||
->write("}\n")
|
||||
->write(sprintf("if ($%s) {\n", $template))
|
||||
->write(\sprintf("if ($%s) {\n", $template))
|
||||
->indent()
|
||||
->write(sprintf('$%s->display(', $template))
|
||||
->write(\sprintf('yield from $%s->unwrap()->yield(', $template))
|
||||
;
|
||||
|
||||
$this->addTemplateArguments($compiler);
|
||||
$compiler
|
||||
->raw(");\n")
|
||||
@@ -67,17 +70,18 @@ class IncludeNode extends Node implements NodeOutputInterface
|
||||
->write("}\n")
|
||||
;
|
||||
} else {
|
||||
$compiler->write('yield from ');
|
||||
$this->addGetTemplate($compiler);
|
||||
$compiler->raw('->display(');
|
||||
$compiler->raw('->unwrap()->yield(');
|
||||
$this->addTemplateArguments($compiler);
|
||||
$compiler->raw(");\n");
|
||||
}
|
||||
}
|
||||
|
||||
protected function addGetTemplate(Compiler $compiler)
|
||||
protected function addGetTemplate(Compiler $compiler/* , string $template = '' */)
|
||||
{
|
||||
$compiler
|
||||
->write('$this->loadTemplate(')
|
||||
->raw('$this->loadTemplate(')
|
||||
->subcompile($this->getNode('expr'))
|
||||
->raw(', ')
|
||||
->repr($this->getTemplateName())
|
||||
@@ -93,12 +97,12 @@ class IncludeNode extends Node implements NodeOutputInterface
|
||||
$compiler->raw(false === $this->getAttribute('only') ? '$context' : '[]');
|
||||
} elseif (false === $this->getAttribute('only')) {
|
||||
$compiler
|
||||
->raw('twig_array_merge($context, ')
|
||||
->raw('CoreExtension::merge($context, ')
|
||||
->subcompile($this->getNode('variables'))
|
||||
->raw(')')
|
||||
;
|
||||
} else {
|
||||
$compiler->raw('twig_to_array(');
|
||||
$compiler->raw('CoreExtension::toArray(');
|
||||
$compiler->subcompile($this->getNode('variables'));
|
||||
$compiler->raw(')');
|
||||
}
|
||||
|
||||
@@ -11,101 +11,109 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\Variable\LocalVariable;
|
||||
|
||||
/**
|
||||
* Represents a macro node.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class MacroNode extends Node
|
||||
{
|
||||
public const VARARGS_NAME = 'varargs';
|
||||
|
||||
public function __construct(string $name, Node $body, Node $arguments, int $lineno, string $tag = null)
|
||||
/**
|
||||
* @param BodyNode $body
|
||||
* @param ArrayExpression $arguments
|
||||
*/
|
||||
public function __construct(string $name, Node $body, Node $arguments, int $lineno)
|
||||
{
|
||||
foreach ($arguments as $argumentName => $argument) {
|
||||
if (self::VARARGS_NAME === $argumentName) {
|
||||
throw new SyntaxError(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(), $argument->getSourceContext());
|
||||
if (!$body instanceof BodyNode) {
|
||||
trigger_deprecation('twig/twig', '3.12', \sprintf('Not passing a "%s" instance as the "body" argument of the "%s" constructor is deprecated ("%s" given).', BodyNode::class, static::class, $body::class));
|
||||
}
|
||||
|
||||
if (!$arguments instanceof ArrayExpression) {
|
||||
trigger_deprecation('twig/twig', '3.15', \sprintf('Not passing a "%s" instance as the "arguments" argument of the "%s" constructor is deprecated ("%s" given).', ArrayExpression::class, static::class, $arguments::class));
|
||||
|
||||
$args = new ArrayExpression([], $arguments->getTemplateLine());
|
||||
foreach ($arguments as $name => $default) {
|
||||
$args->addElement($default, new LocalVariable($name, $default->getTemplateLine()));
|
||||
}
|
||||
$arguments = $args;
|
||||
}
|
||||
|
||||
foreach ($arguments->getKeyValuePairs() as $pair) {
|
||||
if ("\u{035C}".self::VARARGS_NAME === $pair['key']->getAttribute('name')) {
|
||||
throw new SyntaxError(\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), $pair['value']->getTemplateLine(), $pair['value']->getSourceContext());
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno, $tag);
|
||||
parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write(sprintf('public function macro_%s(', $this->getAttribute('name')))
|
||||
->write(\sprintf('public function macro_%s(', $this->getAttribute('name')))
|
||||
;
|
||||
|
||||
$count = \count($this->getNode('arguments'));
|
||||
$pos = 0;
|
||||
foreach ($this->getNode('arguments') as $name => $default) {
|
||||
/** @var ArrayExpression $arguments */
|
||||
$arguments = $this->getNode('arguments');
|
||||
foreach ($arguments->getKeyValuePairs() as $pair) {
|
||||
$name = $pair['key'];
|
||||
$default = $pair['value'];
|
||||
$compiler
|
||||
->raw('$__'.$name.'__ = ')
|
||||
->subcompile($name)
|
||||
->raw(' = ')
|
||||
->subcompile($default)
|
||||
->raw(', ')
|
||||
;
|
||||
|
||||
if (++$pos < $count) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
}
|
||||
|
||||
if ($count) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('...$__varargs__')
|
||||
->raw(")\n")
|
||||
->raw('...$varargs')
|
||||
->raw("): string|Markup\n")
|
||||
->write("{\n")
|
||||
->indent()
|
||||
->write("\$macros = \$this->macros;\n")
|
||||
->write("\$context = \$this->env->mergeGlobals([\n")
|
||||
->write("\$context = [\n")
|
||||
->indent()
|
||||
;
|
||||
|
||||
foreach ($this->getNode('arguments') as $name => $default) {
|
||||
foreach ($arguments->getKeyValuePairs() as $pair) {
|
||||
$name = $pair['key'];
|
||||
$var = $name->getAttribute('name');
|
||||
if (str_starts_with($var, "\u{035C}")) {
|
||||
$var = substr($var, \strlen("\u{035C}"));
|
||||
}
|
||||
$compiler
|
||||
->write('')
|
||||
->string($name)
|
||||
->raw(' => $__'.$name.'__')
|
||||
->string($var)
|
||||
->raw(' => ')
|
||||
->subcompile($name)
|
||||
->raw(",\n")
|
||||
;
|
||||
}
|
||||
|
||||
$node = new CaptureNode($this->getNode('body'), $this->getNode('body')->lineno);
|
||||
|
||||
$compiler
|
||||
->write('')
|
||||
->string(self::VARARGS_NAME)
|
||||
->raw(' => ')
|
||||
;
|
||||
|
||||
$compiler
|
||||
->raw("\$__varargs__,\n")
|
||||
->raw("\$varargs,\n")
|
||||
->outdent()
|
||||
->write("]);\n\n")
|
||||
->write("] + \$this->env->getGlobals();\n\n")
|
||||
->write("\$blocks = [];\n\n")
|
||||
;
|
||||
if ($compiler->getEnvironment()->isDebug()) {
|
||||
$compiler->write("ob_start();\n");
|
||||
} else {
|
||||
$compiler->write("ob_start(function () { return ''; });\n");
|
||||
}
|
||||
$compiler
|
||||
->write("try {\n")
|
||||
->indent()
|
||||
->subcompile($this->getNode('body'))
|
||||
->write('return ')
|
||||
->subcompile($node)
|
||||
->raw("\n")
|
||||
->write("return ('' === \$tmp = ob_get_contents()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n")
|
||||
->outdent()
|
||||
->write("} finally {\n")
|
||||
->indent()
|
||||
->write("ob_end_clean();\n")
|
||||
->outdent()
|
||||
->write("}\n")
|
||||
->outdent()
|
||||
->write("}\n\n")
|
||||
;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
@@ -20,26 +21,34 @@ use Twig\Source;
|
||||
/**
|
||||
* 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.
|
||||
* If you need to customize the behavior of the generated class, add nodes to
|
||||
* the following nodes: display_start, display_end, constructor_start,
|
||||
* constructor_end, and class_end.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
final class ModuleNode extends Node
|
||||
{
|
||||
/**
|
||||
* @param BodyNode $body
|
||||
*/
|
||||
public function __construct(Node $body, ?AbstractExpression $parent, Node $blocks, Node $macros, Node $traits, $embeddedTemplates, Source $source)
|
||||
{
|
||||
if (!$body instanceof BodyNode) {
|
||||
trigger_deprecation('twig/twig', '3.12', \sprintf('Not passing a "%s" instance as the "body" argument of the "%s" constructor is deprecated.', BodyNode::class, static::class));
|
||||
}
|
||||
|
||||
$nodes = [
|
||||
'body' => $body,
|
||||
'blocks' => $blocks,
|
||||
'macros' => $macros,
|
||||
'traits' => $traits,
|
||||
'display_start' => new Node(),
|
||||
'display_end' => new Node(),
|
||||
'constructor_start' => new Node(),
|
||||
'constructor_end' => new Node(),
|
||||
'class_end' => new Node(),
|
||||
'display_start' => new Nodes(),
|
||||
'display_end' => new Nodes(),
|
||||
'constructor_start' => new Nodes(),
|
||||
'constructor_end' => new Nodes(),
|
||||
'class_end' => new Nodes(),
|
||||
];
|
||||
if (null !== $parent) {
|
||||
$nodes['parent'] = $parent;
|
||||
@@ -106,7 +115,7 @@ final class ModuleNode extends Node
|
||||
$parent = $this->getNode('parent');
|
||||
|
||||
$compiler
|
||||
->write("protected function doGetParent(array \$context)\n", "{\n")
|
||||
->write("protected function doGetParent(array \$context): bool|string|Template|TemplateWrapper\n", "{\n")
|
||||
->indent()
|
||||
->addDebugInfo($parent)
|
||||
->write('return ')
|
||||
@@ -143,6 +152,7 @@ final class ModuleNode extends Node
|
||||
->write("use Twig\Environment;\n")
|
||||
->write("use Twig\Error\LoaderError;\n")
|
||||
->write("use Twig\Error\RuntimeError;\n")
|
||||
->write("use Twig\Extension\CoreExtension;\n")
|
||||
->write("use Twig\Extension\SandboxExtension;\n")
|
||||
->write("use Twig\Markup;\n")
|
||||
->write("use Twig\Sandbox\SecurityError;\n")
|
||||
@@ -150,7 +160,9 @@ final class ModuleNode extends Node
|
||||
->write("use Twig\Sandbox\SecurityNotAllowedFilterError;\n")
|
||||
->write("use Twig\Sandbox\SecurityNotAllowedFunctionError;\n")
|
||||
->write("use Twig\Source;\n")
|
||||
->write("use Twig\Template;\n\n")
|
||||
->write("use Twig\Template;\n")
|
||||
->write("use Twig\TemplateWrapper;\n")
|
||||
->write("\n")
|
||||
;
|
||||
}
|
||||
$compiler
|
||||
@@ -160,8 +172,11 @@ final class ModuleNode extends Node
|
||||
->raw(" extends Template\n")
|
||||
->write("{\n")
|
||||
->indent()
|
||||
->write("private \$source;\n")
|
||||
->write("private \$macros = [];\n\n")
|
||||
->write("private Source \$source;\n")
|
||||
->write("/**\n")
|
||||
->write(" * @var array<string, Template>\n")
|
||||
->write(" */\n")
|
||||
->write("private array \$macros = [];\n\n")
|
||||
;
|
||||
}
|
||||
|
||||
@@ -188,14 +203,14 @@ final class ModuleNode extends Node
|
||||
|
||||
$compiler
|
||||
->addDebugInfo($node)
|
||||
->write(sprintf('$_trait_%s = $this->loadTemplate(', $i))
|
||||
->write(\sprintf('$_trait_%s = $this->loadTemplate(', $i))
|
||||
->subcompile($node)
|
||||
->raw(', ')
|
||||
->repr($node->getTemplateName())
|
||||
->raw(', ')
|
||||
->repr($node->getTemplateLine())
|
||||
->raw(");\n")
|
||||
->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i))
|
||||
->write(\sprintf("if (!\$_trait_%s->unwrap()->isTraitable()) {\n", $i))
|
||||
->indent()
|
||||
->write("throw new RuntimeError('Template \"'.")
|
||||
->subcompile($trait->getNode('template'))
|
||||
@@ -204,12 +219,12 @@ final class ModuleNode extends Node
|
||||
->raw(", \$this->source);\n")
|
||||
->outdent()
|
||||
->write("}\n")
|
||||
->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i))
|
||||
->write(\sprintf("\$_trait_%s_blocks = \$_trait_%s->unwrap()->getBlocks();\n\n", $i, $i))
|
||||
;
|
||||
|
||||
foreach ($trait->getNode('targets') as $key => $value) {
|
||||
$compiler
|
||||
->write(sprintf('if (!isset($_trait_%s_blocks[', $i))
|
||||
->write(\sprintf('if (!isset($_trait_%s_blocks[', $i))
|
||||
->string($key)
|
||||
->raw("])) {\n")
|
||||
->indent()
|
||||
@@ -223,13 +238,17 @@ final class ModuleNode extends Node
|
||||
->outdent()
|
||||
->write("}\n\n")
|
||||
|
||||
->write(sprintf('$_trait_%s_blocks[', $i))
|
||||
->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")
|
||||
->raw("]); \$this->traitAliases[")
|
||||
->subcompile($value)
|
||||
->raw("] = ")
|
||||
->string($key)
|
||||
->raw(";\n\n")
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -242,7 +261,7 @@ final class ModuleNode extends Node
|
||||
|
||||
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))
|
||||
;
|
||||
}
|
||||
|
||||
@@ -275,7 +294,7 @@ final class ModuleNode extends Node
|
||||
|
||||
foreach ($this->getNode('blocks') as $name => $node) {
|
||||
$compiler
|
||||
->write(sprintf("'%s' => [\$this, 'block_%s'],\n", $name, $name))
|
||||
->write(\sprintf("'%s' => [\$this, 'block_%s'],\n", $name, $name))
|
||||
;
|
||||
}
|
||||
|
||||
@@ -303,7 +322,7 @@ final class ModuleNode extends Node
|
||||
protected function compileDisplay(Compiler $compiler)
|
||||
{
|
||||
$compiler
|
||||
->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n")
|
||||
->write("protected function doDisplay(array \$context, array \$blocks = []): iterable\n", "{\n")
|
||||
->indent()
|
||||
->write("\$macros = \$this->macros;\n")
|
||||
->subcompile($this->getNode('display_start'))
|
||||
@@ -324,15 +343,24 @@ final class ModuleNode extends Node
|
||||
->repr($parent->getTemplateLine())
|
||||
->raw(");\n")
|
||||
;
|
||||
$compiler->write('$this->parent');
|
||||
} else {
|
||||
$compiler->write('$this->getParent($context)');
|
||||
}
|
||||
$compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n");
|
||||
$compiler->write('yield from ');
|
||||
|
||||
if ($parent instanceof ConstantExpression) {
|
||||
$compiler->raw('$this->parent');
|
||||
} else {
|
||||
$compiler->raw('$this->getParent($context)');
|
||||
}
|
||||
$compiler->raw("->unwrap()->yield(\$context, array_merge(\$this->blocks, \$blocks));\n");
|
||||
}
|
||||
|
||||
$compiler->subcompile($this->getNode('display_end'));
|
||||
|
||||
if (!$this->hasNode('parent')) {
|
||||
$compiler->write("yield from [];\n");
|
||||
}
|
||||
|
||||
$compiler
|
||||
->subcompile($this->getNode('display_end'))
|
||||
->outdent()
|
||||
->write("}\n\n")
|
||||
;
|
||||
@@ -358,7 +386,7 @@ final class ModuleNode extends Node
|
||||
->write("/**\n")
|
||||
->write(" * @codeCoverageIgnore\n")
|
||||
->write(" */\n")
|
||||
->write("public function getTemplateName()\n", "{\n")
|
||||
->write("public function getTemplateName(): string\n", "{\n")
|
||||
->indent()
|
||||
->write('return ')
|
||||
->repr($this->getSourceContext()->getName())
|
||||
@@ -380,13 +408,13 @@ final class ModuleNode extends Node
|
||||
$traitable = !$this->hasNode('parent') && 0 === \count($this->getNode('macros'));
|
||||
if ($traitable) {
|
||||
if ($this->getNode('body') instanceof BodyNode) {
|
||||
$nodes = $this->getNode('body')->getNode(0);
|
||||
$nodes = $this->getNode('body')->getNode('0');
|
||||
} else {
|
||||
$nodes = $this->getNode('body');
|
||||
}
|
||||
|
||||
if (!\count($nodes)) {
|
||||
$nodes = new Node([$nodes]);
|
||||
$nodes = new Nodes([$nodes]);
|
||||
}
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
@@ -394,14 +422,6 @@ final class ModuleNode extends Node
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($node instanceof BlockReferenceNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$traitable = false;
|
||||
break;
|
||||
}
|
||||
@@ -415,9 +435,9 @@ final class ModuleNode extends Node
|
||||
->write("/**\n")
|
||||
->write(" * @codeCoverageIgnore\n")
|
||||
->write(" */\n")
|
||||
->write("public function isTraitable()\n", "{\n")
|
||||
->write("public function isTraitable(): bool\n", "{\n")
|
||||
->indent()
|
||||
->write(sprintf("return %s;\n", $traitable ? 'true' : 'false'))
|
||||
->write("return false;\n")
|
||||
->outdent()
|
||||
->write("}\n\n")
|
||||
;
|
||||
@@ -429,9 +449,9 @@ final class ModuleNode extends Node
|
||||
->write("/**\n")
|
||||
->write(" * @codeCoverageIgnore\n")
|
||||
->write(" */\n")
|
||||
->write("public function getDebugInfo()\n", "{\n")
|
||||
->write("public function getDebugInfo(): array\n", "{\n")
|
||||
->indent()
|
||||
->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true))))
|
||||
->write(\sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true))))
|
||||
->outdent()
|
||||
->write("}\n\n")
|
||||
;
|
||||
@@ -440,7 +460,7 @@ final class ModuleNode extends Node
|
||||
protected function compileGetSourceContext(Compiler $compiler)
|
||||
{
|
||||
$compiler
|
||||
->write("public function getSourceContext()\n", "{\n")
|
||||
->write("public function getSourceContext(): Source\n", "{\n")
|
||||
->indent()
|
||||
->write('return new Source(')
|
||||
->string($compiler->getEnvironment()->isDebug() ? $this->getSourceContext()->getCode() : '')
|
||||
@@ -458,7 +478,7 @@ final class ModuleNode extends Node
|
||||
{
|
||||
if ($node instanceof ConstantExpression) {
|
||||
$compiler
|
||||
->write(sprintf('%s = $this->loadTemplate(', $var))
|
||||
->write(\sprintf('%s = $this->loadTemplate(', $var))
|
||||
->subcompile($node)
|
||||
->raw(', ')
|
||||
->repr($node->getTemplateName())
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Source;
|
||||
|
||||
@@ -19,61 +20,89 @@ use Twig\Source;
|
||||
* Represents a node in the AST.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @implements \IteratorAggregate<int|string, Node>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class Node implements \Countable, \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @var array<string|int, Node>
|
||||
*/
|
||||
protected $nodes;
|
||||
protected $attributes;
|
||||
protected $lineno;
|
||||
protected $tag;
|
||||
|
||||
private $sourceContext;
|
||||
/** @var array<string, NameDeprecation> */
|
||||
private $nodeNameDeprecations = [];
|
||||
/** @var array<string, NameDeprecation> */
|
||||
private $attributeNameDeprecations = [];
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @param array<string|int, Node> $nodes An array of named nodes
|
||||
* @param array $attributes An array of attributes (should not be nodes)
|
||||
* @param int $lineno The line number
|
||||
*/
|
||||
public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0, string $tag = null)
|
||||
public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0)
|
||||
{
|
||||
if (self::class === static::class) {
|
||||
trigger_deprecation('twig/twig', '3.15', \sprintf('Instantiating "%s" directly is deprecated; the class will become abstract in 4.0.', self::class));
|
||||
}
|
||||
|
||||
foreach ($nodes as $name => $node) {
|
||||
if (!$node instanceof self) {
|
||||
throw new \InvalidArgumentException(sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, static::class));
|
||||
throw new \InvalidArgumentException(\sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', get_debug_type($node), $name, static::class));
|
||||
}
|
||||
}
|
||||
$this->nodes = $nodes;
|
||||
$this->attributes = $attributes;
|
||||
$this->lineno = $lineno;
|
||||
$this->tag = $tag;
|
||||
|
||||
if (\func_num_args() > 3) {
|
||||
trigger_deprecation('twig/twig', '3.12', \sprintf('The "tag" constructor argument of the "%s" class is deprecated and ignored (check which TokenParser class set it to "%s"), the tag is now automatically set by the Parser when needed.', static::class, func_get_arg(3) ?: 'null'));
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
$attributes = [];
|
||||
foreach ($this->attributes as $name => $value) {
|
||||
$attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true)));
|
||||
$repr = static::class;
|
||||
|
||||
if ($this->tag) {
|
||||
$repr .= \sprintf("\n tag: %s", $this->tag);
|
||||
}
|
||||
|
||||
$repr = [static::class.'('.implode(', ', $attributes)];
|
||||
$attributes = [];
|
||||
foreach ($this->attributes as $name => $value) {
|
||||
if (\is_callable($value)) {
|
||||
$v = '\Closure';
|
||||
} elseif ($value instanceof \Stringable) {
|
||||
$v = (string) $value;
|
||||
} else {
|
||||
$v = str_replace("\n", '', var_export($value, true));
|
||||
}
|
||||
$attributes[] = \sprintf('%s: %s', $name, $v);
|
||||
}
|
||||
|
||||
if ($attributes) {
|
||||
$repr .= \sprintf("\n attributes:\n %s", implode("\n ", $attributes));
|
||||
}
|
||||
|
||||
if (\count($this->nodes)) {
|
||||
$repr .= "\n nodes:";
|
||||
foreach ($this->nodes as $name => $node) {
|
||||
$len = \strlen($name) + 4;
|
||||
$len = \strlen($name) + 6;
|
||||
$noderepr = [];
|
||||
foreach (explode("\n", (string) $node) as $line) {
|
||||
$noderepr[] = str_repeat(' ', $len).$line;
|
||||
}
|
||||
|
||||
$repr[] = sprintf(' %s: %s', $name, ltrim(implode("\n", $noderepr)));
|
||||
$repr .= \sprintf("\n %s: %s", $name, ltrim(implode("\n", $noderepr)));
|
||||
}
|
||||
|
||||
$repr[] = ')';
|
||||
} else {
|
||||
$repr[0] .= ')';
|
||||
}
|
||||
|
||||
return implode("\n", $repr);
|
||||
return $repr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,7 +111,7 @@ class Node implements \Countable, \IteratorAggregate
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
foreach ($this->nodes as $node) {
|
||||
$node->compile($compiler);
|
||||
$compiler->subcompile($node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +125,18 @@ class Node implements \Countable, \IteratorAggregate
|
||||
return $this->tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function setNodeTag(string $tag): void
|
||||
{
|
||||
if ($this->tag) {
|
||||
throw new \LogicException('The tag of a node can only be set once.');
|
||||
}
|
||||
|
||||
$this->tag = $tag;
|
||||
}
|
||||
|
||||
public function hasAttribute(string $name): bool
|
||||
{
|
||||
return \array_key_exists($name, $this->attributes);
|
||||
@@ -104,7 +145,17 @@ class Node implements \Countable, \IteratorAggregate
|
||||
public function getAttribute(string $name)
|
||||
{
|
||||
if (!\array_key_exists($name, $this->attributes)) {
|
||||
throw new \LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class));
|
||||
throw new \LogicException(\sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class));
|
||||
}
|
||||
|
||||
$triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true;
|
||||
if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) {
|
||||
$dep = $this->attributeNameDeprecations[$name];
|
||||
if ($dep->getNewName()) {
|
||||
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute "%s" on a "%s" class is deprecated, get the "%s" attribute instead.', $name, static::class, $dep->getNewName());
|
||||
} else {
|
||||
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting attribute "%s" on a "%s" class is deprecated.', $name, static::class);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->attributes[$name];
|
||||
@@ -112,38 +163,96 @@ class Node implements \Countable, \IteratorAggregate
|
||||
|
||||
public function setAttribute(string $name, $value): void
|
||||
{
|
||||
$triggerDeprecation = \func_num_args() > 2 ? func_get_arg(2) : true;
|
||||
if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) {
|
||||
$dep = $this->attributeNameDeprecations[$name];
|
||||
if ($dep->getNewName()) {
|
||||
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute "%s" on a "%s" class is deprecated, set the "%s" attribute instead.', $name, static::class, $dep->getNewName());
|
||||
} else {
|
||||
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting attribute "%s" on a "%s" class is deprecated.', $name, static::class);
|
||||
}
|
||||
}
|
||||
|
||||
$this->attributes[$name] = $value;
|
||||
}
|
||||
|
||||
public function deprecateAttribute(string $name, NameDeprecation $dep): void
|
||||
{
|
||||
$this->attributeNameDeprecations[$name] = $dep;
|
||||
}
|
||||
|
||||
public function removeAttribute(string $name): void
|
||||
{
|
||||
unset($this->attributes[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $name
|
||||
*/
|
||||
public function hasNode(string $name): bool
|
||||
{
|
||||
return isset($this->nodes[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $name
|
||||
*/
|
||||
public function getNode(string $name): self
|
||||
{
|
||||
if (!isset($this->nodes[$name])) {
|
||||
throw new \LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, static::class));
|
||||
throw new \LogicException(\sprintf('Node "%s" does not exist for Node "%s".', $name, static::class));
|
||||
}
|
||||
|
||||
$triggerDeprecation = \func_num_args() > 1 ? func_get_arg(1) : true;
|
||||
if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) {
|
||||
$dep = $this->nodeNameDeprecations[$name];
|
||||
if ($dep->getNewName()) {
|
||||
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node "%s" on a "%s" class is deprecated, get the "%s" node instead.', $name, static::class, $dep->getNewName());
|
||||
} else {
|
||||
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Getting node "%s" on a "%s" class is deprecated.', $name, static::class);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->nodes[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $name
|
||||
*/
|
||||
public function setNode(string $name, self $node): void
|
||||
{
|
||||
$triggerDeprecation = \func_num_args() > 2 ? func_get_arg(2) : true;
|
||||
if ($triggerDeprecation && isset($this->nodeNameDeprecations[$name])) {
|
||||
$dep = $this->nodeNameDeprecations[$name];
|
||||
if ($dep->getNewName()) {
|
||||
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node "%s" on a "%s" class is deprecated, set the "%s" node instead.', $name, static::class, $dep->getNewName());
|
||||
} else {
|
||||
trigger_deprecation($dep->getPackage(), $dep->getVersion(), 'Setting node "%s" on a "%s" class is deprecated.', $name, static::class);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $this->sourceContext) {
|
||||
$node->setSourceContext($this->sourceContext);
|
||||
}
|
||||
$this->nodes[$name] = $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $name
|
||||
*/
|
||||
public function removeNode(string $name): void
|
||||
{
|
||||
unset($this->nodes[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $name
|
||||
*/
|
||||
public function deprecateNode(string $name, NameDeprecation $dep): void
|
||||
{
|
||||
$this->nodeNameDeprecations[$name] = $dep;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
|
||||
@@ -20,19 +21,23 @@ use Twig\Node\Expression\AbstractExpression;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class PrintNode extends Node implements NodeOutputInterface
|
||||
{
|
||||
public function __construct(AbstractExpression $expr, int $lineno, string $tag = null)
|
||||
public function __construct(AbstractExpression $expr, int $lineno)
|
||||
{
|
||||
parent::__construct(['expr' => $expr], [], $lineno, $tag);
|
||||
parent::__construct(['expr' => $expr], [], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
/** @var AbstractExpression */
|
||||
$expr = $this->getNode('expr');
|
||||
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write('echo ')
|
||||
->subcompile($this->getNode('expr'))
|
||||
->write($expr->isGenerator() ? 'yield from ' : 'yield ')
|
||||
->subcompile($expr)
|
||||
->raw(";\n")
|
||||
;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
@@ -18,11 +19,12 @@ use Twig\Compiler;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class SandboxNode extends Node
|
||||
{
|
||||
public function __construct(Node $body, int $lineno, string $tag = null)
|
||||
public function __construct(Node $body, int $lineno)
|
||||
{
|
||||
parent::__construct(['body' => $body], [], $lineno, $tag);
|
||||
parent::__construct(['body' => $body], [], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
|
||||
@@ -19,26 +20,35 @@ use Twig\Node\Expression\ConstantExpression;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class SetNode extends Node implements NodeCaptureInterface
|
||||
{
|
||||
public function __construct(bool $capture, Node $names, Node $values, int $lineno, string $tag = null)
|
||||
public function __construct(bool $capture, Node $names, Node $values, int $lineno)
|
||||
{
|
||||
parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => false], $lineno, $tag);
|
||||
|
||||
/*
|
||||
* Optimizes the node when capture is used for a large block of text.
|
||||
*
|
||||
* {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig\Markup("foo");
|
||||
*/
|
||||
if ($this->getAttribute('capture')) {
|
||||
$this->setAttribute('safe', true);
|
||||
|
||||
$values = $this->getNode('values');
|
||||
if ($values instanceof TextNode) {
|
||||
$this->setNode('values', new ConstantExpression($values->getAttribute('data'), $values->getTemplateLine()));
|
||||
$this->setAttribute('capture', false);
|
||||
$safe = false;
|
||||
if ($capture) {
|
||||
$safe = true;
|
||||
// Node::class === get_class($values) should be removed in Twig 4.0
|
||||
if (($values instanceof Nodes || Node::class === get_class($values)) && !count($values)) {
|
||||
$values = new ConstantExpression('', $values->getTemplateLine());
|
||||
$capture = false;
|
||||
} elseif ($values instanceof TextNode) {
|
||||
$values = new ConstantExpression($values->getAttribute('data'), $values->getTemplateLine());
|
||||
$capture = false;
|
||||
} elseif ($values instanceof PrintNode && $values->getNode('expr') instanceof ConstantExpression) {
|
||||
$values = $values->getNode('expr');
|
||||
$capture = false;
|
||||
} else {
|
||||
$values = new CaptureNode($values, $values->getTemplateLine());
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => $safe], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
@@ -46,7 +56,7 @@ class SetNode extends Node implements NodeCaptureInterface
|
||||
$compiler->addDebugInfo($this);
|
||||
|
||||
if (\count($this->getNode('names')) > 1) {
|
||||
$compiler->write('list(');
|
||||
$compiler->write('[');
|
||||
foreach ($this->getNode('names') as $idx => $node) {
|
||||
if ($idx) {
|
||||
$compiler->raw(', ');
|
||||
@@ -54,29 +64,15 @@ class SetNode extends Node implements NodeCaptureInterface
|
||||
|
||||
$compiler->subcompile($node);
|
||||
}
|
||||
$compiler->raw(')');
|
||||
$compiler->raw(']');
|
||||
} else {
|
||||
if ($this->getAttribute('capture')) {
|
||||
if ($compiler->getEnvironment()->isDebug()) {
|
||||
$compiler->write("ob_start();\n");
|
||||
} else {
|
||||
$compiler->write("ob_start(function () { return ''; });\n");
|
||||
}
|
||||
$compiler
|
||||
->subcompile($this->getNode('values'))
|
||||
;
|
||||
}
|
||||
|
||||
$compiler->subcompile($this->getNode('names'), false);
|
||||
|
||||
if ($this->getAttribute('capture')) {
|
||||
$compiler->raw(" = ('' === \$tmp = ob_get_clean()) ? '' : new Markup(\$tmp, \$this->env->getCharset())");
|
||||
}
|
||||
}
|
||||
$compiler->raw(' = ');
|
||||
|
||||
if (!$this->getAttribute('capture')) {
|
||||
$compiler->raw(' = ');
|
||||
|
||||
if ($this->getAttribute('capture')) {
|
||||
$compiler->subcompile($this->getNode('values'));
|
||||
} else {
|
||||
if (\count($this->getNode('names')) > 1) {
|
||||
$compiler->write('[');
|
||||
foreach ($this->getNode('values') as $idx => $value) {
|
||||
@@ -89,17 +85,31 @@ class SetNode extends Node implements NodeCaptureInterface
|
||||
$compiler->raw(']');
|
||||
} else {
|
||||
if ($this->getAttribute('safe')) {
|
||||
$compiler
|
||||
->raw("('' === \$tmp = ")
|
||||
->subcompile($this->getNode('values'))
|
||||
->raw(") ? '' : new Markup(\$tmp, \$this->env->getCharset())")
|
||||
;
|
||||
if ($this->getNode('values') instanceof ConstantExpression) {
|
||||
if ('' === $this->getNode('values')->getAttribute('value')) {
|
||||
$compiler->raw('""');
|
||||
} else {
|
||||
$compiler
|
||||
->raw('new Markup(')
|
||||
->subcompile($this->getNode('values'))
|
||||
->raw(', $this->env->getCharset())')
|
||||
;
|
||||
}
|
||||
} else {
|
||||
$compiler
|
||||
->raw("('' === \$tmp = ")
|
||||
->subcompile($this->getNode('values'))
|
||||
->raw(") ? '' : new Markup(\$tmp, \$this->env->getCharset())")
|
||||
;
|
||||
}
|
||||
} else {
|
||||
$compiler->subcompile($this->getNode('values'));
|
||||
}
|
||||
}
|
||||
|
||||
$compiler->raw(';');
|
||||
}
|
||||
|
||||
$compiler->raw(";\n");
|
||||
$compiler->raw("\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
@@ -19,6 +20,7 @@ use Twig\Compiler;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class TextNode extends Node implements NodeOutputInterface
|
||||
{
|
||||
public function __construct(string $data, int $lineno)
|
||||
@@ -28,9 +30,10 @@ class TextNode extends Node implements NodeOutputInterface
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler->addDebugInfo($this);
|
||||
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write('echo ')
|
||||
->write('yield ')
|
||||
->string($this->getAttribute('data'))
|
||||
->raw(";\n")
|
||||
;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
@@ -18,16 +19,17 @@ use Twig\Compiler;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class WithNode extends Node
|
||||
{
|
||||
public function __construct(Node $body, ?Node $variables, bool $only, int $lineno, string $tag = null)
|
||||
public function __construct(Node $body, ?Node $variables, bool $only, int $lineno)
|
||||
{
|
||||
$nodes = ['body' => $body];
|
||||
if (null !== $variables) {
|
||||
$nodes['variables'] = $variables;
|
||||
}
|
||||
|
||||
parent::__construct($nodes, ['only' => $only], $lineno, $tag);
|
||||
parent::__construct($nodes, ['only' => $only], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
@@ -36,35 +38,35 @@ class WithNode extends Node
|
||||
|
||||
$parentContextName = $compiler->getVarName();
|
||||
|
||||
$compiler->write(sprintf("\$%s = \$context;\n", $parentContextName));
|
||||
$compiler->write(\sprintf("\$%s = \$context;\n", $parentContextName));
|
||||
|
||||
if ($this->hasNode('variables')) {
|
||||
$node = $this->getNode('variables');
|
||||
$varsName = $compiler->getVarName();
|
||||
$compiler
|
||||
->write(sprintf('$%s = ', $varsName))
|
||||
->write(\sprintf('$%s = ', $varsName))
|
||||
->subcompile($node)
|
||||
->raw(";\n")
|
||||
->write(sprintf("if (!is_iterable(\$%s)) {\n", $varsName))
|
||||
->write(\sprintf("if (!is_iterable(\$%s)) {\n", $varsName))
|
||||
->indent()
|
||||
->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a hash.', ")
|
||||
->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a mapping.', ")
|
||||
->repr($node->getTemplateLine())
|
||||
->raw(", \$this->getSourceContext());\n")
|
||||
->outdent()
|
||||
->write("}\n")
|
||||
->write(sprintf("\$%s = twig_to_array(\$%s);\n", $varsName, $varsName))
|
||||
->write(\sprintf("\$%s = CoreExtension::toArray(\$%s);\n", $varsName, $varsName))
|
||||
;
|
||||
|
||||
if ($this->getAttribute('only')) {
|
||||
$compiler->write("\$context = [];\n");
|
||||
}
|
||||
|
||||
$compiler->write(sprintf("\$context = \$this->env->mergeGlobals(array_merge(\$context, \$%s));\n", $varsName));
|
||||
$compiler->write(\sprintf("\$context = \$%s + \$context + \$this->env->getGlobals();\n", $varsName));
|
||||
}
|
||||
|
||||
$compiler
|
||||
->subcompile($this->getNode('body'))
|
||||
->write(sprintf("\$context = \$%s;\n", $parentContextName))
|
||||
->write(\sprintf("\$context = \$%s;\n", $parentContextName))
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ use Twig\Node\Node;
|
||||
/**
|
||||
* Used to make node visitors compatible with Twig 1.x and 2.x.
|
||||
*
|
||||
* To be removed in Twig 3.1.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @deprecated since 3.9 (to be removed in 4.0)
|
||||
*/
|
||||
abstract class AbstractNodeVisitor implements NodeVisitorInterface
|
||||
{
|
||||
|
||||
@@ -16,14 +16,14 @@ use Twig\Extension\EscaperExtension;
|
||||
use Twig\Node\AutoEscapeNode;
|
||||
use Twig\Node\BlockNode;
|
||||
use Twig\Node\BlockReferenceNode;
|
||||
use Twig\Node\DoNode;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ConditionalExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\FilterExpression;
|
||||
use Twig\Node\Expression\InlinePrint;
|
||||
use Twig\Node\ImportNode;
|
||||
use Twig\Node\ModuleNode;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Node\Nodes;
|
||||
use Twig\Node\PrintNode;
|
||||
use Twig\NodeTraverser;
|
||||
|
||||
@@ -59,7 +59,7 @@ final class EscaperNodeVisitor implements NodeVisitorInterface
|
||||
} elseif ($node instanceof BlockNode) {
|
||||
$this->statusStack[] = $this->blocks[$node->getAttribute('name')] ?? $this->needEscaping();
|
||||
} elseif ($node instanceof ImportNode) {
|
||||
$this->safeVars[] = $node->getNode('var')->getAttribute('name');
|
||||
$this->safeVars[] = $node->getNode('var')->getNode('var')->getAttribute('name');
|
||||
}
|
||||
|
||||
return $node;
|
||||
@@ -75,11 +75,13 @@ final class EscaperNodeVisitor implements NodeVisitorInterface
|
||||
return $this->preEscapeFilterNode($node, $env);
|
||||
} elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping()) {
|
||||
$expression = $node->getNode('expr');
|
||||
if ($expression instanceof ConditionalExpression && $this->shouldUnwrapConditional($expression, $env, $type)) {
|
||||
return new DoNode($this->unwrapConditional($expression, $env, $type), $expression->getTemplateLine());
|
||||
if ($expression instanceof ConditionalExpression) {
|
||||
$this->escapeConditional($expression, $env, $type);
|
||||
} else {
|
||||
$node->setNode('expr', $this->escapeExpression($expression, $env, $type));
|
||||
}
|
||||
|
||||
return $this->escapePrintNode($node, $env, $type);
|
||||
return $node;
|
||||
}
|
||||
|
||||
if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) {
|
||||
@@ -91,85 +93,60 @@ final class EscaperNodeVisitor implements NodeVisitorInterface
|
||||
return $node;
|
||||
}
|
||||
|
||||
private function shouldUnwrapConditional(ConditionalExpression $expression, Environment $env, string $type): bool
|
||||
private function escapeConditional(ConditionalExpression $expression, Environment $env, string $type): void
|
||||
{
|
||||
$expr2Safe = $this->isSafeFor($type, $expression->getNode('expr2'), $env);
|
||||
$expr3Safe = $this->isSafeFor($type, $expression->getNode('expr3'), $env);
|
||||
|
||||
return $expr2Safe !== $expr3Safe;
|
||||
}
|
||||
|
||||
private function unwrapConditional(ConditionalExpression $expression, Environment $env, string $type): ConditionalExpression
|
||||
{
|
||||
// convert "echo a ? b : c" to "a ? echo b : echo c" recursively
|
||||
/** @var AbstractExpression $expr2 */
|
||||
$expr2 = $expression->getNode('expr2');
|
||||
if ($expr2 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr2, $env, $type)) {
|
||||
$expr2 = $this->unwrapConditional($expr2, $env, $type);
|
||||
if ($expr2 instanceof ConditionalExpression) {
|
||||
$this->escapeConditional($expr2, $env, $type);
|
||||
} else {
|
||||
$expr2 = $this->escapeInlinePrintNode(new InlinePrint($expr2, $expr2->getTemplateLine()), $env, $type);
|
||||
$expression->setNode('expr2', $this->escapeExpression($expr2, $env, $type));
|
||||
}
|
||||
|
||||
/** @var AbstractExpression $expr3 */
|
||||
$expr3 = $expression->getNode('expr3');
|
||||
if ($expr3 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr3, $env, $type)) {
|
||||
$expr3 = $this->unwrapConditional($expr3, $env, $type);
|
||||
if ($expr3 instanceof ConditionalExpression) {
|
||||
$this->escapeConditional($expr3, $env, $type);
|
||||
} else {
|
||||
$expr3 = $this->escapeInlinePrintNode(new InlinePrint($expr3, $expr3->getTemplateLine()), $env, $type);
|
||||
$expression->setNode('expr3', $this->escapeExpression($expr3, $env, $type));
|
||||
}
|
||||
|
||||
return new ConditionalExpression($expression->getNode('expr1'), $expr2, $expr3, $expression->getTemplateLine());
|
||||
}
|
||||
|
||||
private function escapeInlinePrintNode(InlinePrint $node, Environment $env, string $type): Node
|
||||
private function escapeExpression(AbstractExpression $expression, Environment $env, string $type): AbstractExpression
|
||||
{
|
||||
$expression = $node->getNode('node');
|
||||
|
||||
if ($this->isSafeFor($type, $expression, $env)) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
return new InlinePrint($this->getEscaperFilter($type, $expression), $node->getTemplateLine());
|
||||
}
|
||||
|
||||
private function escapePrintNode(PrintNode $node, Environment $env, string $type): Node
|
||||
{
|
||||
if (false === $type) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
$expression = $node->getNode('expr');
|
||||
|
||||
if ($this->isSafeFor($type, $expression, $env)) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
$class = \get_class($node);
|
||||
|
||||
return new $class($this->getEscaperFilter($type, $expression), $node->getTemplateLine());
|
||||
return $this->isSafeFor($type, $expression, $env) ? $expression : $this->getEscaperFilter($env, $type, $expression);
|
||||
}
|
||||
|
||||
private function preEscapeFilterNode(FilterExpression $filter, Environment $env): FilterExpression
|
||||
{
|
||||
$name = $filter->getNode('filter')->getAttribute('value');
|
||||
if ($filter->hasAttribute('twig_callable')) {
|
||||
$type = $filter->getAttribute('twig_callable')->getPreEscape();
|
||||
} else {
|
||||
// legacy
|
||||
$name = $filter->getNode('filter', false)->getAttribute('value');
|
||||
$type = $env->getFilter($name)->getPreEscape();
|
||||
}
|
||||
|
||||
$type = $env->getFilter($name)->getPreEscape();
|
||||
if (null === $type) {
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/** @var AbstractExpression $node */
|
||||
$node = $filter->getNode('node');
|
||||
if ($this->isSafeFor($type, $node, $env)) {
|
||||
return $filter;
|
||||
}
|
||||
|
||||
$filter->setNode('node', $this->getEscaperFilter($type, $node));
|
||||
$filter->setNode('node', $this->getEscaperFilter($env, $type, $node));
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
private function isSafeFor(string $type, Node $expression, Environment $env): bool
|
||||
private function isSafeFor(string $type, AbstractExpression $expression, Environment $env): bool
|
||||
{
|
||||
$safe = $this->safeAnalysis->getSafe($expression);
|
||||
|
||||
if (null === $safe) {
|
||||
if (!$safe) {
|
||||
if (null === $this->traverser) {
|
||||
$this->traverser = new NodeTraverser($env, [$this->safeAnalysis]);
|
||||
}
|
||||
@@ -192,13 +169,13 @@ final class EscaperNodeVisitor implements NodeVisitorInterface
|
||||
return $this->defaultStrategy ?: false;
|
||||
}
|
||||
|
||||
private function getEscaperFilter(string $type, Node $node): FilterExpression
|
||||
private function getEscaperFilter(Environment $env, string $type, AbstractExpression $node): FilterExpression
|
||||
{
|
||||
$line = $node->getTemplateLine();
|
||||
$name = new ConstantExpression('escape', $line);
|
||||
$args = new Node([new ConstantExpression($type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]);
|
||||
$filter = $env->getFilter('escape');
|
||||
$args = new Nodes([new ConstantExpression($type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]);
|
||||
|
||||
return new FilterExpression($node, $name, $args, $line);
|
||||
return new FilterExpression($node, $filter, $args, $line);
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\NodeVisitor;
|
||||
|
||||
use Twig\Environment;
|
||||
use Twig\Node\Expression\AssignNameExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\GetAttrExpression;
|
||||
use Twig\Node\Expression\MethodCallExpression;
|
||||
use Twig\Node\Expression\NameExpression;
|
||||
use Twig\Node\ImportNode;
|
||||
use Twig\Node\ModuleNode;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MacroAutoImportNodeVisitor implements NodeVisitorInterface
|
||||
{
|
||||
private $inAModule = false;
|
||||
private $hasMacroCalls = false;
|
||||
|
||||
public function enterNode(Node $node, Environment $env): Node
|
||||
{
|
||||
if ($node instanceof ModuleNode) {
|
||||
$this->inAModule = true;
|
||||
$this->hasMacroCalls = false;
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
public function leaveNode(Node $node, Environment $env): Node
|
||||
{
|
||||
if ($node instanceof ModuleNode) {
|
||||
$this->inAModule = false;
|
||||
if ($this->hasMacroCalls) {
|
||||
$node->getNode('constructor_end')->setNode('_auto_macro_import', new ImportNode(new NameExpression('_self', 0), new AssignNameExpression('_self', 0), 0, 'import', true));
|
||||
}
|
||||
} elseif ($this->inAModule) {
|
||||
if (
|
||||
$node instanceof GetAttrExpression
|
||||
&& $node->getNode('node') instanceof NameExpression
|
||||
&& '_self' === $node->getNode('node')->getAttribute('name')
|
||||
&& $node->getNode('attribute') instanceof ConstantExpression
|
||||
) {
|
||||
$this->hasMacroCalls = true;
|
||||
|
||||
$name = $node->getNode('attribute')->getAttribute('value');
|
||||
$node = new MethodCallExpression($node->getNode('node'), 'macro_'.$name, $node->getNode('arguments'), $node->getTemplateLine());
|
||||
$node->setAttribute('safe', true);
|
||||
}
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
// we must be ran before auto-escaping
|
||||
return -10;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ use Twig\Environment;
|
||||
use Twig\Node\BlockReferenceNode;
|
||||
use Twig\Node\Expression\BlockReferenceExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\FilterExpression;
|
||||
use Twig\Node\Expression\FunctionExpression;
|
||||
use Twig\Node\Expression\GetAttrExpression;
|
||||
use Twig\Node\Expression\NameExpression;
|
||||
@@ -24,6 +23,7 @@ use Twig\Node\ForNode;
|
||||
use Twig\Node\IncludeNode;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Node\PrintNode;
|
||||
use Twig\Node\TextNode;
|
||||
|
||||
/**
|
||||
* Tries to optimize the AST.
|
||||
@@ -43,21 +43,28 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
|
||||
public const OPTIMIZE_NONE = 0;
|
||||
public const OPTIMIZE_FOR = 2;
|
||||
public const OPTIMIZE_RAW_FILTER = 4;
|
||||
public const OPTIMIZE_TEXT_NODES = 8;
|
||||
|
||||
private $loops = [];
|
||||
private $loopsTargets = [];
|
||||
private $optimizers;
|
||||
|
||||
/**
|
||||
* @param int $optimizers The optimizer mode
|
||||
*/
|
||||
public function __construct(int $optimizers = -1)
|
||||
{
|
||||
if ($optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER)) {
|
||||
throw new \InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers));
|
||||
public function __construct(
|
||||
private int $optimizers = -1,
|
||||
) {
|
||||
if ($optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_TEXT_NODES)) {
|
||||
throw new \InvalidArgumentException(\sprintf('Optimizer mode "%s" is not valid.', $optimizers));
|
||||
}
|
||||
|
||||
$this->optimizers = $optimizers;
|
||||
if (-1 !== $optimizers && self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $optimizers)) {
|
||||
trigger_deprecation('twig/twig', '3.11', 'The "Twig\NodeVisitor\OptimizerNodeVisitor::OPTIMIZE_RAW_FILTER" option is deprecated and does nothing.');
|
||||
}
|
||||
|
||||
if (-1 !== $optimizers && self::OPTIMIZE_TEXT_NODES === (self::OPTIMIZE_TEXT_NODES & $optimizers)) {
|
||||
trigger_deprecation('twig/twig', '3.12', 'The "Twig\NodeVisitor\OptimizerNodeVisitor::OPTIMIZE_TEXT_NODES" option is deprecated and does nothing.');
|
||||
}
|
||||
}
|
||||
|
||||
public function enterNode(Node $node, Environment $env): Node
|
||||
@@ -75,10 +82,6 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
|
||||
$this->leaveOptimizeFor($node);
|
||||
}
|
||||
|
||||
if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) {
|
||||
$node = $this->optimizeRawFilter($node);
|
||||
}
|
||||
|
||||
$node = $this->optimizePrintNode($node);
|
||||
|
||||
return $node;
|
||||
@@ -98,6 +101,11 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
|
||||
}
|
||||
|
||||
$exprNode = $node->getNode('expr');
|
||||
|
||||
if ($exprNode instanceof ConstantExpression && \is_string($exprNode->getAttribute('value'))) {
|
||||
return new TextNode($exprNode->getAttribute('value'), $exprNode->getTemplateLine());
|
||||
}
|
||||
|
||||
if (
|
||||
$exprNode instanceof BlockReferenceExpression
|
||||
|| $exprNode instanceof ParentExpression
|
||||
@@ -110,18 +118,6 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes "raw" filters.
|
||||
*/
|
||||
private function optimizeRawFilter(Node $node): Node
|
||||
{
|
||||
if ($node instanceof FilterExpression && 'raw' == $node->getNode('filter')->getAttribute('value')) {
|
||||
return $node->getNode('node');
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimizes "for" tag by removing the "loop" variable creation whenever possible.
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,7 @@ use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\FilterExpression;
|
||||
use Twig\Node\Expression\FunctionExpression;
|
||||
use Twig\Node\Expression\GetAttrExpression;
|
||||
use Twig\Node\Expression\MacroReferenceExpression;
|
||||
use Twig\Node\Expression\MethodCallExpression;
|
||||
use Twig\Node\Expression\NameExpression;
|
||||
use Twig\Node\Expression\ParentExpression;
|
||||
@@ -36,11 +37,14 @@ final class SafeAnalysisNodeVisitor implements NodeVisitorInterface
|
||||
$this->safeVars = $safeVars;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSafe(Node $node)
|
||||
{
|
||||
$hash = spl_object_hash($node);
|
||||
if (!isset($this->data[$hash])) {
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($this->data[$hash] as $bucket) {
|
||||
@@ -54,6 +58,8 @@ final class SafeAnalysisNodeVisitor implements NodeVisitorInterface
|
||||
|
||||
return $bucket['value'];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function setSafe(Node $node, array $safe): void
|
||||
@@ -96,49 +102,58 @@ final class SafeAnalysisNodeVisitor implements NodeVisitorInterface
|
||||
$this->setSafe($node, $safe);
|
||||
} elseif ($node instanceof FilterExpression) {
|
||||
// filter expression is safe when the filter is safe
|
||||
$name = $node->getNode('filter')->getAttribute('value');
|
||||
$args = $node->getNode('arguments');
|
||||
if ($filter = $env->getFilter($name)) {
|
||||
$safe = $filter->getSafe($args);
|
||||
if ($node->hasAttribute('twig_callable')) {
|
||||
$filter = $node->getAttribute('twig_callable');
|
||||
} else {
|
||||
// legacy
|
||||
$filter = $env->getFilter($node->getAttribute('name'));
|
||||
}
|
||||
|
||||
if ($filter) {
|
||||
$safe = $filter->getSafe($node->getNode('arguments'));
|
||||
if (null === $safe) {
|
||||
trigger_deprecation('twig/twig', '3.16', 'The "%s::getSafe()" method should not return "null" anymore, return "[]" instead.', $filter::class);
|
||||
$safe = [];
|
||||
}
|
||||
|
||||
if (!$safe) {
|
||||
$safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety());
|
||||
}
|
||||
$this->setSafe($node, $safe);
|
||||
} else {
|
||||
$this->setSafe($node, []);
|
||||
}
|
||||
} elseif ($node instanceof FunctionExpression) {
|
||||
// function expression is safe when the function is safe
|
||||
$name = $node->getAttribute('name');
|
||||
$args = $node->getNode('arguments');
|
||||
if ($function = $env->getFunction($name)) {
|
||||
$this->setSafe($node, $function->getSafe($args));
|
||||
if ($node->hasAttribute('twig_callable')) {
|
||||
$function = $node->getAttribute('twig_callable');
|
||||
} else {
|
||||
$this->setSafe($node, []);
|
||||
// legacy
|
||||
$function = $env->getFunction($node->getAttribute('name'));
|
||||
}
|
||||
} elseif ($node instanceof MethodCallExpression) {
|
||||
if ($node->getAttribute('safe')) {
|
||||
$this->setSafe($node, ['all']);
|
||||
} else {
|
||||
$this->setSafe($node, []);
|
||||
|
||||
if ($function) {
|
||||
$safe = $function->getSafe($node->getNode('arguments'));
|
||||
if (null === $safe) {
|
||||
trigger_deprecation('twig/twig', '3.16', 'The "%s::getSafe()" method should not return "null" anymore, return "[]" instead.', $function::class);
|
||||
$safe = [];
|
||||
}
|
||||
$this->setSafe($node, $safe);
|
||||
}
|
||||
} elseif ($node instanceof MethodCallExpression || $node instanceof MacroReferenceExpression) {
|
||||
// all macro calls are safe
|
||||
$this->setSafe($node, ['all']);
|
||||
} elseif ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression) {
|
||||
$name = $node->getNode('node')->getAttribute('name');
|
||||
if (\in_array($name, $this->safeVars)) {
|
||||
$this->setSafe($node, ['all']);
|
||||
} else {
|
||||
$this->setSafe($node, []);
|
||||
}
|
||||
} else {
|
||||
$this->setSafe($node, []);
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
private function intersectSafe(array $a = null, array $b = null): array
|
||||
private function intersectSafe(array $a, array $b): array
|
||||
{
|
||||
if (null === $a || null === $b) {
|
||||
if (!$a || !$b) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
@@ -15,14 +15,17 @@ use Twig\Environment;
|
||||
use Twig\Node\CheckSecurityCallNode;
|
||||
use Twig\Node\CheckSecurityNode;
|
||||
use Twig\Node\CheckToStringNode;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\Binary\ConcatBinary;
|
||||
use Twig\Node\Expression\Binary\RangeBinary;
|
||||
use Twig\Node\Expression\FilterExpression;
|
||||
use Twig\Node\Expression\FunctionExpression;
|
||||
use Twig\Node\Expression\GetAttrExpression;
|
||||
use Twig\Node\Expression\NameExpression;
|
||||
use Twig\Node\Expression\Unary\SpreadUnary;
|
||||
use Twig\Node\ModuleNode;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Node\Nodes;
|
||||
use Twig\Node\PrintNode;
|
||||
use Twig\Node\SetNode;
|
||||
|
||||
@@ -34,8 +37,11 @@ use Twig\Node\SetNode;
|
||||
final class SandboxNodeVisitor implements NodeVisitorInterface
|
||||
{
|
||||
private $inAModule = false;
|
||||
/** @var array<string, int> */
|
||||
private $tags;
|
||||
/** @var array<string, int> */
|
||||
private $filters;
|
||||
/** @var array<string, int> */
|
||||
private $functions;
|
||||
private $needsToStringWrap = false;
|
||||
|
||||
@@ -51,22 +57,22 @@ final class SandboxNodeVisitor implements NodeVisitorInterface
|
||||
} elseif ($this->inAModule) {
|
||||
// look for tags
|
||||
if ($node->getNodeTag() && !isset($this->tags[$node->getNodeTag()])) {
|
||||
$this->tags[$node->getNodeTag()] = $node;
|
||||
$this->tags[$node->getNodeTag()] = $node->getTemplateLine();
|
||||
}
|
||||
|
||||
// look for filters
|
||||
if ($node instanceof FilterExpression && !isset($this->filters[$node->getNode('filter')->getAttribute('value')])) {
|
||||
$this->filters[$node->getNode('filter')->getAttribute('value')] = $node;
|
||||
if ($node instanceof FilterExpression && !isset($this->filters[$node->getAttribute('name')])) {
|
||||
$this->filters[$node->getAttribute('name')] = $node->getTemplateLine();
|
||||
}
|
||||
|
||||
// look for functions
|
||||
if ($node instanceof FunctionExpression && !isset($this->functions[$node->getAttribute('name')])) {
|
||||
$this->functions[$node->getAttribute('name')] = $node;
|
||||
$this->functions[$node->getAttribute('name')] = $node->getTemplateLine();
|
||||
}
|
||||
|
||||
// the .. operator is equivalent to the range() function
|
||||
if ($node instanceof RangeBinary && !isset($this->functions['range'])) {
|
||||
$this->functions['range'] = $node;
|
||||
$this->functions['range'] = $node->getTemplateLine();
|
||||
}
|
||||
|
||||
if ($node instanceof PrintNode) {
|
||||
@@ -102,8 +108,8 @@ final class SandboxNodeVisitor implements NodeVisitorInterface
|
||||
if ($node instanceof ModuleNode) {
|
||||
$this->inAModule = false;
|
||||
|
||||
$node->setNode('constructor_end', new Node([new CheckSecurityCallNode(), $node->getNode('constructor_end')]));
|
||||
$node->setNode('class_end', new Node([new CheckSecurityNode($this->filters, $this->tags, $this->functions), $node->getNode('class_end')]));
|
||||
$node->setNode('constructor_end', new Nodes([new CheckSecurityCallNode(), $node->getNode('constructor_end')]));
|
||||
$node->setNode('class_end', new Nodes([new CheckSecurityNode($this->filters, $this->tags, $this->functions), $node->getNode('class_end')]));
|
||||
} elseif ($this->inAModule) {
|
||||
if ($node instanceof PrintNode || $node instanceof SetNode) {
|
||||
$this->needsToStringWrap = false;
|
||||
@@ -116,8 +122,19 @@ final class SandboxNodeVisitor implements NodeVisitorInterface
|
||||
private function wrapNode(Node $node, string $name): void
|
||||
{
|
||||
$expr = $node->getNode($name);
|
||||
if ($expr instanceof NameExpression || $expr instanceof GetAttrExpression) {
|
||||
$node->setNode($name, new CheckToStringNode($expr));
|
||||
if (($expr instanceof NameExpression || $expr instanceof GetAttrExpression) && !$expr->isGenerator()) {
|
||||
// Simplify in 4.0 as the spread attribute has been removed there
|
||||
$new = new CheckToStringNode($expr);
|
||||
if ($expr->hasAttribute('spread')) {
|
||||
$new->setAttribute('spread', $expr->getAttribute('spread'));
|
||||
}
|
||||
$node->setNode($name, $new);
|
||||
} elseif ($expr instanceof SpreadUnary) {
|
||||
$this->wrapNode($expr, 'node');
|
||||
} elseif ($expr instanceof ArrayExpression) {
|
||||
foreach ($expr as $name => $_) {
|
||||
$this->wrapNode($expr, $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user