This commit is contained in:
emmymayo
2025-02-05 23:15:46 +01:00
commit 7269c99357
16995 changed files with 3389680 additions and 0 deletions
@@ -0,0 +1 @@
<?php
@@ -0,0 +1 @@
<?php
@@ -0,0 +1,7 @@
<?php
namespace MailPoetVendor\Twig\Attribute;
if (!defined('ABSPATH')) exit;
#[\Attribute(\Attribute::TARGET_CLASS)]
final class YieldReady
{
}
@@ -0,0 +1,10 @@
<?php
namespace MailPoetVendor\Twig\Cache;
if (!defined('ABSPATH')) exit;
interface CacheInterface
{
public function generateKey(string $name, string $className) : string;
public function write(string $key, string $content) : void;
public function load(string $key) : void;
public function getTimestamp(string $key) : int;
}
@@ -0,0 +1,46 @@
<?php
namespace MailPoetVendor\Twig\Cache;
if (!defined('ABSPATH')) exit;
final class ChainCache implements CacheInterface
{
private $caches;
public function __construct(iterable $caches)
{
$this->caches = $caches;
}
public function generateKey(string $name, string $className) : string
{
return $className . '#' . $name;
}
public function write(string $key, string $content) : void
{
$splitKey = $this->splitKey($key);
foreach ($this->caches as $cache) {
$cache->write($cache->generateKey(...$splitKey), $content);
}
}
public function load(string $key) : void
{
[$name, $className] = $this->splitKey($key);
foreach ($this->caches as $cache) {
$cache->load($cache->generateKey($name, $className));
if (\class_exists($className, \false)) {
break;
}
}
}
public function getTimestamp(string $key) : int
{
$splitKey = $this->splitKey($key);
foreach ($this->caches as $cache) {
if (0 < ($timestamp = $cache->getTimestamp($cache->generateKey(...$splitKey)))) {
return $timestamp;
}
}
return 0;
}
private function splitKey(string $key) : array
{
return \array_reverse(\explode('#', $key, 2));
}
}
@@ -0,0 +1,60 @@
<?php
namespace MailPoetVendor\Twig\Cache;
if (!defined('ABSPATH')) exit;
class FilesystemCache implements CacheInterface
{
public const FORCE_BYTECODE_INVALIDATION = 1;
private $directory;
private $options;
public function __construct(string $directory, int $options = 0)
{
$this->directory = \rtrim($directory, '\\/') . '/';
$this->options = $options;
}
public function generateKey(string $name, string $className) : string
{
$hash = \hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $className);
return $this->directory . $hash[0] . $hash[1] . '/' . $hash . '.php';
}
public function load(string $key) : void
{
if (\is_file($key)) {
@(include_once $key);
}
}
public function write(string $key, string $content) : void
{
$dir = \dirname($key);
if (!\is_dir($dir)) {
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));
}
}
} elseif (!\is_writable($dir)) {
throw new \RuntimeException(\sprintf('Unable to write in the cache directory (%s).', $dir));
}
$tmpFile = \tempnam($dir, \basename($key));
if (\false !== @\file_put_contents($tmpFile, $content) && @\rename($tmpFile, $key)) {
@\chmod($key, 0666 & ~\umask());
if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) {
// Compile cached file into bytecode cache
if (\function_exists('opcache_invalidate') && \filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
@\opcache_invalidate($key, \true);
} elseif (\function_exists('apc_compile_file')) {
\apc_compile_file($key);
}
}
return;
}
throw new \RuntimeException(\sprintf('Failed to write cache file "%s".', $key));
}
public function getTimestamp(string $key) : int
{
if (!\is_file($key)) {
return 0;
}
return (int) @\filemtime($key);
}
}
@@ -0,0 +1,20 @@
<?php
namespace MailPoetVendor\Twig\Cache;
if (!defined('ABSPATH')) exit;
final class NullCache implements CacheInterface
{
public function generateKey(string $name, string $className) : string
{
return '';
}
public function write(string $key, string $content) : void
{
}
public function load(string $key) : void
{
}
public function getTimestamp(string $key) : int
{
return 0;
}
}
@@ -0,0 +1,10 @@
<?php
namespace MailPoetVendor\Twig\Cache;
if (!defined('ABSPATH')) exit;
class ReadOnlyFilesystemCache extends FilesystemCache
{
public function write(string $key, string $content) : void
{
// Do nothing with the content, it's a read-only filesystem.
}
}
@@ -0,0 +1,165 @@
<?php
namespace MailPoetVendor\Twig;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Node\Node;
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 getEnvironment() : Environment
{
return $this->env;
}
public function getSource() : string
{
return $this->source;
}
public function reset(int $indentation = 0)
{
$this->lastLine = null;
$this->source = '';
$this->debugInfo = [];
$this->sourceOffset = 0;
// source code starts at 1 (as we then increment it when we encounter new lines)
$this->sourceLine = 1;
$this->indentation = $indentation;
$this->varNameSalt = 0;
return $this;
}
public function compile(Node $node, int $indentation = 0)
{
$this->reset($indentation);
$this->didUseEchoStack[] = $this->didUseEcho;
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 #[YieldReady].', $this->didUseEcho, \get_class($node));
}
return $this;
} finally {
$this->didUseEcho = \array_pop($this->didUseEchoStack);
}
}
public function subcompile(Node $node, bool $raw = \true)
{
if (!$raw) {
$this->source .= \str_repeat(' ', $this->indentation * 4);
}
$this->didUseEchoStack[] = $this->didUseEcho;
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 #[YieldReady].', $this->didUseEcho, \get_class($node));
}
return $this;
} finally {
$this->didUseEcho = \array_pop($this->didUseEchoStack);
}
}
public function raw(string $string)
{
$this->checkForEcho($string);
$this->source .= $string;
return $this;
}
public function write(...$strings)
{
foreach ($strings as $string) {
$this->checkForEcho($string);
$this->source .= \str_repeat(' ', $this->indentation * 4) . $string;
}
return $this;
}
public function string(string $value)
{
$this->source .= \sprintf('"%s"', \addcslashes($value, "\x00\t\"\$\\"));
return $this;
}
public function repr($value)
{
if (\is_int($value) || \is_float($value)) {
if (\false !== ($locale = \setlocale(\LC_NUMERIC, '0'))) {
\setlocale(\LC_NUMERIC, 'C');
}
$this->raw(\var_export($value, \true));
if (\false !== $locale) {
\setlocale(\LC_NUMERIC, $locale);
}
} elseif (null === $value) {
$this->raw('null');
} elseif (\is_bool($value)) {
$this->raw($value ? 'true' : 'false');
} elseif (\is_array($value)) {
$this->raw('array(');
$first = \true;
foreach ($value as $key => $v) {
if (!$first) {
$this->raw(', ');
}
$first = \false;
$this->repr($key);
$this->raw(' => ');
$this->repr($v);
}
$this->raw(')');
} else {
$this->string($value);
}
return $this;
}
public function addDebugInfo(Node $node)
{
if ($node->getTemplateLine() != $this->lastLine) {
$this->write(\sprintf("// line %d\n", $node->getTemplateLine()));
$this->sourceLine += \substr_count($this->source, "\n", $this->sourceOffset);
$this->sourceOffset = \strlen($this->source);
$this->debugInfo[$this->sourceLine] = $node->getTemplateLine();
$this->lastLine = $node->getTemplateLine();
}
return $this;
}
public function getDebugInfo() : array
{
\ksort($this->debugInfo);
return $this->debugInfo;
}
public function indent(int $step = 1)
{
$this->indentation += $step;
return $this;
}
public function outdent(int $step = 1)
{
// can't outdent by more steps than the current indentation level
if ($this->indentation < $step) {
throw new \LogicException('Unable to call outdent() as the indentation would become negative.');
}
$this->indentation -= $step;
return $this;
}
public function getVarName() : string
{
return \sprintf('__internal_compile_%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;
}
}
@@ -0,0 +1,452 @@
<?php
namespace MailPoetVendor\Twig;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Cache\CacheInterface;
use MailPoetVendor\Twig\Cache\FilesystemCache;
use MailPoetVendor\Twig\Cache\NullCache;
use MailPoetVendor\Twig\Error\Error;
use MailPoetVendor\Twig\Error\LoaderError;
use MailPoetVendor\Twig\Error\RuntimeError;
use MailPoetVendor\Twig\Error\SyntaxError;
use MailPoetVendor\Twig\Extension\CoreExtension;
use MailPoetVendor\Twig\Extension\EscaperExtension;
use MailPoetVendor\Twig\Extension\ExtensionInterface;
use MailPoetVendor\Twig\Extension\OptimizerExtension;
use MailPoetVendor\Twig\Extension\YieldNotReadyExtension;
use MailPoetVendor\Twig\Loader\ArrayLoader;
use MailPoetVendor\Twig\Loader\ChainLoader;
use MailPoetVendor\Twig\Loader\LoaderInterface;
use MailPoetVendor\Twig\Node\Expression\Binary\AbstractBinary;
use MailPoetVendor\Twig\Node\Expression\Unary\AbstractUnary;
use MailPoetVendor\Twig\Node\ModuleNode;
use MailPoetVendor\Twig\Node\Node;
use MailPoetVendor\Twig\NodeVisitor\NodeVisitorInterface;
use MailPoetVendor\Twig\Runtime\EscaperRuntime;
use MailPoetVendor\Twig\RuntimeLoader\FactoryRuntimeLoader;
use MailPoetVendor\Twig\RuntimeLoader\RuntimeLoaderInterface;
use MailPoetVendor\Twig\TokenParser\TokenParserInterface;
class Environment
{
public const VERSION = '3.11.2';
public const VERSION_ID = 301102;
public const MAJOR_VERSION = 4;
public const MINOR_VERSION = 11;
public const RELEASE_VERSION = 2;
public const EXTRA_VERSION = '';
private $charset;
private $loader;
private $debug;
private $autoReload;
private $cache;
private $lexer;
private $parser;
private $compiler;
private $globals = [];
private $resolvedGlobals;
private $loadedTemplates;
private $strictVariables;
private $originalCache;
private $extensionSet;
private $runtimeLoaders = [];
private $runtimes = [];
private $optionsHash;
private $useYield;
private $defaultRuntimeLoader;
public function __construct(LoaderInterface $loader, $options = [])
{
$this->setLoader($loader);
$options = \array_merge(['debug' => \false, 'charset' => 'UTF-8', 'strict_variables' => \false, 'autoescape' => 'html', '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());
$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']));
}
public function useYield() : bool
{
return $this->useYield;
}
public function enableDebug()
{
$this->debug = \true;
$this->updateOptionsHash();
}
public function disableDebug()
{
$this->debug = \false;
$this->updateOptionsHash();
}
public function isDebug()
{
return $this->debug;
}
public function enableAutoReload()
{
$this->autoReload = \true;
}
public function disableAutoReload()
{
$this->autoReload = \false;
}
public function isAutoReload()
{
return $this->autoReload;
}
public function enableStrictVariables()
{
$this->strictVariables = \true;
$this->updateOptionsHash();
}
public function disableStrictVariables()
{
$this->strictVariables = \false;
$this->updateOptionsHash();
}
public function isStrictVariables()
{
return $this->strictVariables;
}
public function getCache($original = \true)
{
return $original ? $this->originalCache : $this->cache;
}
public function setCache($cache)
{
if (\is_string($cache)) {
$this->originalCache = $cache;
$this->cache = new FilesystemCache($cache, $this->autoReload ? FilesystemCache::FORCE_BYTECODE_INVALIDATION : 0);
} elseif (\false === $cache) {
$this->originalCache = $cache;
$this->cache = new NullCache();
} elseif ($cache instanceof CacheInterface) {
$this->originalCache = $this->cache = $cache;
} else {
throw new \LogicException('Cache can only be a string, false, or a \\Twig\\Cache\\CacheInterface implementation.');
}
}
public function getTemplateClass(string $name, ?int $index = null) : string
{
$key = $this->getLoader()->getCacheKey($name) . $this->optionsHash;
return '__TwigTemplate_' . \hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $key) . (null === $index ? '' : '___' . $index);
}
public function render($name, array $context = []) : string
{
return $this->load($name)->render($context);
}
public function display($name, array $context = []) : void
{
$this->load($name)->display($context);
}
public function load($name) : TemplateWrapper
{
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));
}
public function loadTemplate(string $cls, string $name, ?int $index = null) : Template
{
$mainCls = $cls;
if (null !== $index) {
$cls .= '___' . $index;
}
if (isset($this->loadedTemplates[$cls])) {
return $this->loadedTemplates[$cls];
}
if (!\class_exists($cls, \false)) {
$key = $this->cache->generateKey($name, $mainCls);
if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) {
$this->cache->load($key);
}
if (!\class_exists($cls, \false)) {
$source = $this->getLoader()->getSourceContext($name);
$content = $this->compileSource($source);
$this->cache->write($key, $content);
$this->cache->load($key);
if (!\class_exists($mainCls, \false)) {
eval('?>' . $content);
}
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);
}
}
}
$this->extensionSet->initRuntime();
return $this->loadedTemplates[$cls] = new $cls($this);
}
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);
} else {
$name = \sprintf('__string_template__%s', $hash);
}
$loader = new ChainLoader([new ArrayLoader([$name => $template]), $current = $this->getLoader()]);
$this->setLoader($loader);
try {
return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name));
} finally {
$this->setLoader($current);
}
}
public function isTemplateFresh(string $name, int $time) : bool
{
return $this->extensionSet->getLastModified() <= $time && $this->getLoader()->isFresh($name, $time);
}
public function resolveTemplate($names) : TemplateWrapper
{
if (!\is_array($names)) {
return $this->load($names);
}
$count = \count($names);
foreach ($names as $name) {
if ($name instanceof Template) {
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;
}
if (1 !== $count && !$this->getLoader()->exists($name)) {
continue;
}
return $this->load($name);
}
throw new LoaderError(\sprintf('Unable to find one of the following templates: "%s".', \implode('", "', $names)));
}
public function setLexer(Lexer $lexer)
{
$this->lexer = $lexer;
}
public function tokenize(Source $source) : TokenStream
{
if (null === $this->lexer) {
$this->lexer = new Lexer($this);
}
return $this->lexer->tokenize($source);
}
public function setParser(Parser $parser)
{
$this->parser = $parser;
}
public function parse(TokenStream $stream) : ModuleNode
{
if (null === $this->parser) {
$this->parser = new Parser($this);
}
return $this->parser->parse($stream);
}
public function setCompiler(Compiler $compiler)
{
$this->compiler = $compiler;
}
public function compile(Node $node) : string
{
if (null === $this->compiler) {
$this->compiler = new Compiler($this);
}
return $this->compiler->compile($node)->getSource();
}
public function compileSource(Source $source) : string
{
try {
return $this->compile($this->parse($this->tokenize($source)));
} catch (Error $e) {
$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);
}
}
public function setLoader(LoaderInterface $loader)
{
$this->loader = $loader;
}
public function getLoader() : LoaderInterface
{
return $this->loader;
}
public function setCharset(string $charset)
{
if ('UTF8' === ($charset = \strtoupper($charset ?: ''))) {
// iconv on Windows requires "UTF-8" instead of "UTF8"
$charset = 'UTF-8';
}
$this->charset = $charset;
}
public function getCharset() : string
{
return $this->charset;
}
public function hasExtension(string $class) : bool
{
return $this->extensionSet->hasExtension($class);
}
public function addRuntimeLoader(RuntimeLoaderInterface $loader)
{
$this->runtimeLoaders[] = $loader;
}
public function getExtension(string $class) : ExtensionInterface
{
return $this->extensionSet->getExtension($class);
}
public function getRuntime(string $class)
{
if (isset($this->runtimes[$class])) {
return $this->runtimes[$class];
}
foreach ($this->runtimeLoaders as $loader) {
if (null !== ($runtime = $loader->load($class))) {
return $this->runtimes[$class] = $runtime;
}
}
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)
{
$this->extensionSet->addExtension($extension);
$this->updateOptionsHash();
}
public function setExtensions(array $extensions)
{
$this->extensionSet->setExtensions($extensions);
$this->updateOptionsHash();
}
public function getExtensions() : array
{
return $this->extensionSet->getExtensions();
}
public function addTokenParser(TokenParserInterface $parser)
{
$this->extensionSet->addTokenParser($parser);
}
public function getTokenParsers() : array
{
return $this->extensionSet->getTokenParsers();
}
public function getTokenParser(string $name) : ?TokenParserInterface
{
return $this->extensionSet->getTokenParser($name);
}
public function registerUndefinedTokenParserCallback(callable $callable) : void
{
$this->extensionSet->registerUndefinedTokenParserCallback($callable);
}
public function addNodeVisitor(NodeVisitorInterface $visitor)
{
$this->extensionSet->addNodeVisitor($visitor);
}
public function getNodeVisitors() : array
{
return $this->extensionSet->getNodeVisitors();
}
public function addFilter(TwigFilter $filter)
{
$this->extensionSet->addFilter($filter);
}
public function getFilter(string $name) : ?TwigFilter
{
return $this->extensionSet->getFilter($name);
}
public function registerUndefinedFilterCallback(callable $callable) : void
{
$this->extensionSet->registerUndefinedFilterCallback($callable);
}
public function getFilters() : array
{
return $this->extensionSet->getFilters();
}
public function addTest(TwigTest $test)
{
$this->extensionSet->addTest($test);
}
public function getTests() : array
{
return $this->extensionSet->getTests();
}
public function getTest(string $name) : ?TwigTest
{
return $this->extensionSet->getTest($name);
}
public function addFunction(TwigFunction $function)
{
$this->extensionSet->addFunction($function);
}
public function getFunction(string $name) : ?TwigFunction
{
return $this->extensionSet->getFunction($name);
}
public function registerUndefinedFunctionCallback(callable $callable) : void
{
$this->extensionSet->registerUndefinedFunctionCallback($callable);
}
public function getFunctions() : array
{
return $this->extensionSet->getFunctions();
}
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));
}
if (null !== $this->resolvedGlobals) {
$this->resolvedGlobals[$name] = $value;
} else {
$this->globals[$name] = $value;
}
}
public function getGlobals() : array
{
if ($this->extensionSet->isInitialized()) {
if (null === $this->resolvedGlobals) {
$this->resolvedGlobals = \array_merge($this->extensionSet->getGlobals(), $this->globals);
}
return $this->resolvedGlobals;
}
return \array_merge($this->extensionSet->getGlobals(), $this->globals);
}
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;
}
}
return $context;
}
public function getUnaryOperators() : array
{
return $this->extensionSet->getUnaryOperators();
}
public function getBinaryOperators() : array
{
return $this->extensionSet->getBinaryOperators();
}
private function updateOptionsHash() : void
{
$this->optionsHash = \implode(':', [$this->extensionSet->getSignature(), \PHP_MAJOR_VERSION, \PHP_MINOR_VERSION, self::VERSION, (int) $this->debug, (int) $this->strictVariables, $this->useYield ? '1' : '0']);
}
}
@@ -0,0 +1,153 @@
<?php
namespace MailPoetVendor\Twig\Error;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Source;
use MailPoetVendor\Twig\Template;
class Error extends \Exception
{
private $lineno;
private $name;
private $rawMessage;
private $sourcePath;
private $sourceCode;
public function __construct(string $message, int $lineno = -1, ?Source $source = null, ?\Throwable $previous = null)
{
parent::__construct('', 0, $previous);
if (null === $source) {
$name = null;
} else {
$name = $source->getName();
$this->sourceCode = $source->getCode();
$this->sourcePath = $source->getPath();
}
$this->lineno = $lineno;
$this->name = $name;
$this->rawMessage = $message;
$this->updateRepr();
}
public function getRawMessage() : string
{
return $this->rawMessage;
}
public function getTemplateLine() : int
{
return $this->lineno;
}
public function setTemplateLine(int $lineno) : void
{
$this->lineno = $lineno;
$this->updateRepr();
}
public function getSourceContext() : ?Source
{
return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null;
}
public function setSourceContext(?Source $source = null) : void
{
if (null === $source) {
$this->sourceCode = $this->name = $this->sourcePath = null;
} else {
$this->sourceCode = $source->getCode();
$this->name = $source->getName();
$this->sourcePath = $source->getPath();
}
$this->updateRepr();
}
public function guess() : void
{
$this->guessTemplateInfo();
$this->updateRepr();
}
public function appendMessage($rawMessage) : void
{
$this->rawMessage .= $rawMessage;
$this->updateRepr();
}
private function updateRepr() : void
{
$this->message = $this->rawMessage;
if ($this->sourcePath && $this->lineno > 0) {
$this->file = $this->sourcePath;
$this->line = $this->lineno;
return;
}
$dot = \false;
if (\str_ends_with($this->message, '.')) {
$this->message = \substr($this->message, 0, -1);
$dot = \true;
}
$questionMark = \false;
if (\str_ends_with($this->message, '?')) {
$this->message = \substr($this->message, 0, -1);
$questionMark = \true;
}
if ($this->name) {
if (\is_string($this->name) || \is_object($this->name) && \method_exists($this->name, '__toString')) {
$name = \sprintf('"%s"', $this->name);
} else {
$name = \json_encode($this->name);
}
$this->message .= \sprintf(' in %s', $name);
}
if ($this->lineno && $this->lineno >= 0) {
$this->message .= \sprintf(' at line %d', $this->lineno);
}
if ($dot) {
$this->message .= '.';
}
if ($questionMark) {
$this->message .= '?';
}
}
private function guessTemplateInfo() : void
{
$template = null;
$templateClass = null;
$backtrace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT);
foreach ($backtrace as $trace) {
if (isset($trace['object']) && $trace['object'] instanceof Template) {
$currentClass = \get_class($trace['object']);
$isEmbedContainer = null === $templateClass ? \false : \str_starts_with($templateClass, $currentClass);
if (null === $this->name || $this->name == $trace['object']->getTemplateName() && !$isEmbedContainer) {
$template = $trace['object'];
$templateClass = \get_class($trace['object']);
}
}
}
// update template name
if (null !== $template && null === $this->name) {
$this->name = $template->getTemplateName();
}
// update template path if any
if (null !== $template && null === $this->sourcePath) {
$src = $template->getSourceContext();
$this->sourceCode = $src->getCode();
$this->sourcePath = $src->getPath();
}
if (null === $template || $this->lineno > -1) {
return;
}
$r = new \ReflectionObject($template);
$file = $r->getFileName();
$exceptions = [$e = $this];
while ($e = $e->getPrevious()) {
$exceptions[] = $e;
}
while ($e = \array_pop($exceptions)) {
$traces = $e->getTrace();
\array_unshift($traces, ['file' => $e->getFile(), 'line' => $e->getLine()]);
while ($trace = \array_shift($traces)) {
if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) {
continue;
}
foreach ($template->getDebugInfo() as $codeLine => $templateLine) {
if ($codeLine <= $trace['line']) {
// update template line
$this->lineno = $templateLine;
return;
}
}
}
}
}
}
@@ -0,0 +1,6 @@
<?php
namespace MailPoetVendor\Twig\Error;
if (!defined('ABSPATH')) exit;
class LoaderError extends Error
{
}
@@ -0,0 +1,6 @@
<?php
namespace MailPoetVendor\Twig\Error;
if (!defined('ABSPATH')) exit;
class RuntimeError extends Error
{
}
@@ -0,0 +1,21 @@
<?php
namespace MailPoetVendor\Twig\Error;
if (!defined('ABSPATH')) exit;
class SyntaxError extends Error
{
public function addSuggestions(string $name, array $items) : void
{
$alternatives = [];
foreach ($items as $item) {
$lev = \levenshtein($name, $item);
if ($lev <= \strlen($name) / 3 || \str_contains($item, $name)) {
$alternatives[$item] = $lev;
}
}
if (!$alternatives) {
return;
}
\asort($alternatives);
$this->appendMessage(\sprintf(' Did you mean "%s"?', \implode('", "', \array_keys($alternatives))));
}
}
@@ -0,0 +1,852 @@
<?php
namespace MailPoetVendor\Twig;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Error\SyntaxError;
use MailPoetVendor\Twig\Node\Expression\AbstractExpression;
use MailPoetVendor\Twig\Node\Expression\ArrayExpression;
use MailPoetVendor\Twig\Node\Expression\ArrowFunctionExpression;
use MailPoetVendor\Twig\Node\Expression\AssignNameExpression;
use MailPoetVendor\Twig\Node\Expression\Binary\AbstractBinary;
use MailPoetVendor\Twig\Node\Expression\Binary\ConcatBinary;
use MailPoetVendor\Twig\Node\Expression\BlockReferenceExpression;
use MailPoetVendor\Twig\Node\Expression\ConditionalExpression;
use MailPoetVendor\Twig\Node\Expression\ConstantExpression;
use MailPoetVendor\Twig\Node\Expression\GetAttrExpression;
use MailPoetVendor\Twig\Node\Expression\MethodCallExpression;
use MailPoetVendor\Twig\Node\Expression\NameExpression;
use MailPoetVendor\Twig\Node\Expression\ParentExpression;
use MailPoetVendor\Twig\Node\Expression\TestExpression;
use MailPoetVendor\Twig\Node\Expression\Unary\AbstractUnary;
use MailPoetVendor\Twig\Node\Expression\Unary\NegUnary;
use MailPoetVendor\Twig\Node\Expression\Unary\NotUnary;
use MailPoetVendor\Twig\Node\Expression\Unary\PosUnary;
use MailPoetVendor\Twig\Node\Node;
class ExpressionParser
{
public const OPERATOR_LEFT = 1;
public const OPERATOR_RIGHT = 2;
private $parser;
private $env;
private $unaryOperators;
private $binaryOperators;
public function __construct(Parser $parser, Environment $env)
{
$this->parser = $parser;
$this->env = $env;
$this->unaryOperators = $env->getUnaryOperators();
$this->binaryOperators = $env->getBinaryOperators();
}
public function parseExpression($precedence = 0, $allowArrow = \false)
{
if ($allowArrow && ($arrow = $this->parseArrow())) {
return $arrow;
}
$expr = $this->getPrimary();
$token = $this->parser->getCurrentToken();
while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
$op = $this->binaryOperators[$token->getValue()];
$this->parser->getStream()->next();
if ('is not' === $token->getValue()) {
$expr = $this->parseNotTestExpression($expr);
} elseif ('is' === $token->getValue()) {
$expr = $this->parseTestExpression($expr);
} elseif (isset($op['callable'])) {
$expr = $op['callable']($this->parser, $expr);
} else {
$expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence'], \true);
$class = $op['class'];
$expr = new $class($expr, $expr1, $token->getLine());
}
$token = $this->parser->getCurrentToken();
}
if (0 === $precedence) {
return $this->parseConditionalExpression($expr);
}
return $expr;
}
private function parseArrow()
{
$stream = $this->parser->getStream();
// short array syntax (one argument, no parentheses)?
if ($stream->look(1)->test(
12
)) {
$line = $stream->getCurrent()->getLine();
$token = $stream->expect(
5
);
$names = [new AssignNameExpression($token->getValue(), $token->getLine())];
$stream->expect(
12
);
return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
}
// first, determine if we are parsing an arrow function by finding => (long form)
$i = 0;
if (!$stream->look($i)->test(
9,
'('
)) {
return null;
}
++$i;
while (\true) {
// variable name
++$i;
if (!$stream->look($i)->test(
9,
','
)) {
break;
}
++$i;
}
if (!$stream->look($i)->test(
9,
')'
)) {
return null;
}
++$i;
if (!$stream->look($i)->test(
12
)) {
return null;
}
// yes, let's parse it properly
$token = $stream->expect(
9,
'('
);
$line = $token->getLine();
$names = [];
while (\true) {
$token = $stream->expect(
5
);
$names[] = new AssignNameExpression($token->getValue(), $token->getLine());
if (!$stream->nextIf(
9,
','
)) {
break;
}
}
$stream->expect(
9,
')'
);
$stream->expect(
12
);
return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
}
private function getPrimary() : AbstractExpression
{
$token = $this->parser->getCurrentToken();
if ($this->isUnary($token)) {
$operator = $this->unaryOperators[$token->getValue()];
$this->parser->getStream()->next();
$expr = $this->parseExpression($operator['precedence']);
$class = $operator['class'];
return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
} elseif ($token->test(
9,
'('
)) {
$this->parser->getStream()->next();
$expr = $this->parseExpression();
$this->parser->getStream()->expect(
9,
')',
'An opened parenthesis is not properly closed'
);
return $this->parsePostfixExpression($expr);
}
return $this->parsePrimaryExpression();
}
private function parseConditionalExpression($expr) : AbstractExpression
{
while ($this->parser->getStream()->nextIf(
9,
'?'
)) {
if (!$this->parser->getStream()->nextIf(
9,
':'
)) {
$expr2 = $this->parseExpression();
if ($this->parser->getStream()->nextIf(
9,
':'
)) {
// Ternary operator (expr ? expr2 : expr3)
$expr3 = $this->parseExpression();
} else {
// Ternary without else (expr ? expr2)
$expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine());
}
} else {
// Ternary without then (expr ?: expr3)
$expr2 = $expr;
$expr3 = $this->parseExpression();
}
$expr = new ConditionalExpression($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine());
}
return $expr;
}
private function isUnary(Token $token) : bool
{
return $token->test(
8
) && isset($this->unaryOperators[$token->getValue()]);
}
private function isBinary(Token $token) : bool
{
return $token->test(
8
) && isset($this->binaryOperators[$token->getValue()]);
}
public function parsePrimaryExpression()
{
$token = $this->parser->getCurrentToken();
switch ($token->getType()) {
case 5:
$this->parser->getStream()->next();
switch ($token->getValue()) {
case 'true':
case 'TRUE':
$node = new ConstantExpression(\true, $token->getLine());
break;
case 'false':
case 'FALSE':
$node = new ConstantExpression(\false, $token->getLine());
break;
case 'none':
case 'NONE':
case 'null':
case 'NULL':
$node = new ConstantExpression(null, $token->getLine());
break;
default:
if ('(' === $this->parser->getCurrentToken()->getValue()) {
$node = $this->getFunctionNode($token->getValue(), $token->getLine());
} else {
$node = new NameExpression($token->getValue(), $token->getLine());
}
}
break;
case 6:
$this->parser->getStream()->next();
$node = new ConstantExpression($token->getValue(), $token->getLine());
break;
case 7:
case 10:
$node = $this->parseStringExpression();
break;
case 8:
if (\preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) {
// in this context, string operators are variable names
$this->parser->getStream()->next();
$node = new NameExpression($token->getValue(), $token->getLine());
break;
}
if (isset($this->unaryOperators[$token->getValue()])) {
$class = $this->unaryOperators[$token->getValue()]['class'];
if (!\in_array($class, [NegUnary::class, PosUnary::class])) {
throw new SyntaxError(\sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
}
$this->parser->getStream()->next();
$expr = $this->parsePrimaryExpression();
$node = new $class($expr, $token->getLine());
break;
}
// no break
default:
if ($token->test(
9,
'['
)) {
$node = $this->parseSequenceExpression();
} elseif ($token->test(
9,
'{'
)) {
$node = $this->parseMappingExpression();
} elseif ($token->test(
8,
'='
) && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {
throw new SyntaxError(\sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
} else {
throw new SyntaxError(\sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
}
}
return $this->parsePostfixExpression($node);
}
public function parseStringExpression()
{
$stream = $this->parser->getStream();
$nodes = [];
// a string cannot be followed by another string in a single expression
$nextCanBeString = \true;
while (\true) {
if ($nextCanBeString && ($token = $stream->nextIf(
7
))) {
$nodes[] = new ConstantExpression($token->getValue(), $token->getLine());
$nextCanBeString = \false;
} elseif ($stream->nextIf(
10
)) {
$nodes[] = $this->parseExpression();
$stream->expect(
11
);
$nextCanBeString = \true;
} else {
break;
}
}
$expr = \array_shift($nodes);
foreach ($nodes as $node) {
$expr = new ConcatBinary($expr, $node, $node->getTemplateLine());
}
return $expr;
}
public function parseArrayExpression()
{
trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseSequenceExpression()" instead.', __METHOD__);
return $this->parseSequenceExpression();
}
public function parseSequenceExpression()
{
$stream = $this->parser->getStream();
$stream->expect(
9,
'[',
'A sequence element was expected'
);
$node = new ArrayExpression([], $stream->getCurrent()->getLine());
$first = \true;
while (!$stream->test(
9,
']'
)) {
if (!$first) {
$stream->expect(
9,
',',
'A sequence element must be followed by a comma'
);
// trailing ,?
if ($stream->test(
9,
']'
)) {
break;
}
}
$first = \false;
if ($stream->test(
13
)) {
$stream->next();
$expr = $this->parseExpression();
$expr->setAttribute('spread', \true);
$node->addElement($expr);
} else {
$node->addElement($this->parseExpression());
}
}
$stream->expect(
9,
']',
'An opened sequence is not properly closed'
);
return $node;
}
public function parseHashExpression()
{
trigger_deprecation('twig/twig', '3.11', 'Calling "%s()" is deprecated, use "parseMappingExpression()" instead.', __METHOD__);
return $this->parseMappingExpression();
}
public function parseMappingExpression()
{
$stream = $this->parser->getStream();
$stream->expect(
9,
'{',
'A mapping element was expected'
);
$node = new ArrayExpression([], $stream->getCurrent()->getLine());
$first = \true;
while (!$stream->test(
9,
'}'
)) {
if (!$first) {
$stream->expect(
9,
',',
'A mapping value must be followed by a comma'
);
// trailing ,?
if ($stream->test(
9,
'}'
)) {
break;
}
}
$first = \false;
if ($stream->test(
13
)) {
$stream->next();
$value = $this->parseExpression();
$value->setAttribute('spread', \true);
$node->addElement($value);
continue;
}
// a mapping key can be:
//
// * a number -- 12
// * a string -- 'a'
// * a name, which is equivalent to a string -- a
// * an expression, which must be enclosed in parentheses -- (1 + 2)
if ($token = $stream->nextIf(
5
)) {
$key = new ConstantExpression($token->getValue(), $token->getLine());
// {a} is a shortcut for {a:a}
if ($stream->test(Token::PUNCTUATION_TYPE, [',', '}'])) {
$value = new NameExpression($key->getAttribute('value'), $key->getTemplateLine());
$node->addElement($value, $key);
continue;
}
} elseif (($token = $stream->nextIf(
7
)) || ($token = $stream->nextIf(
6
))) {
$key = new ConstantExpression($token->getValue(), $token->getLine());
} elseif ($stream->test(
9,
'('
)) {
$key = $this->parseExpression();
} else {
$current = $stream->getCurrent();
throw new SyntaxError(\sprintf('A mapping key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext());
}
$stream->expect(
9,
':',
'A mapping key must be followed by a colon (:)'
);
$value = $this->parseExpression();
$node->addElement($value, $key);
}
$stream->expect(
9,
'}',
'An opened mapping is not properly closed'
);
return $node;
}
public function parsePostfixExpression($node)
{
while (\true) {
$token = $this->parser->getCurrentToken();
if (9 == $token->getType()) {
if ('.' == $token->getValue() || '[' == $token->getValue()) {
$node = $this->parseSubscriptExpression($node);
} elseif ('|' == $token->getValue()) {
$node = $this->parseFilterExpression($node);
} else {
break;
}
} else {
break;
}
}
return $node;
}
public function getFunctionNode($name, $line)
{
switch ($name) {
case 'parent':
$this->parseArguments();
if (!\count($this->parser->getBlockStack())) {
throw new SyntaxError('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext());
}
if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext());
}
return new ParentExpression($this->parser->peekBlockStack(), $line);
case 'block':
$args = $this->parseArguments();
if (\count($args) < 1) {
throw new SyntaxError('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext());
}
return new BlockReferenceExpression($args->getNode('0'), \count($args) > 1 ? $args->getNode('1') : null, $line);
case 'attribute':
$args = $this->parseArguments();
if (\count($args) < 2) {
throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext());
}
return new GetAttrExpression($args->getNode('0'), $args->getNode('1'), \count($args) > 2 ? $args->getNode('2') : null, Template::ANY_CALL, $line);
default:
if (null !== ($alias = $this->parser->getImportedSymbol('function', $name))) {
$arguments = new ArrayExpression([], $line);
foreach ($this->parseArguments() as $n) {
$arguments->addElement($n);
}
$node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line);
$node->setAttribute('safe', \true);
return $node;
}
$args = $this->parseArguments(\true);
$class = $this->getFunctionNodeClass($name, $line);
return new $class($name, $args, $line);
}
}
public function parseSubscriptExpression($node)
{
$stream = $this->parser->getStream();
$token = $stream->next();
$lineno = $token->getLine();
$arguments = new ArrayExpression([], $lineno);
$type = Template::ANY_CALL;
if ('.' == $token->getValue()) {
$token = $stream->next();
if (5 == $token->getType() || 6 == $token->getType() || 8 == $token->getType() && \preg_match(Lexer::REGEX_NAME, $token->getValue())) {
$arg = new ConstantExpression($token->getValue(), $lineno);
if ($stream->test(
9,
'('
)) {
$type = Template::METHOD_CALL;
foreach ($this->parseArguments() as $n) {
$arguments->addElement($n);
}
}
} else {
throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext());
}
if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
$name = $arg->getAttribute('value');
$node = new MethodCallExpression($node, 'macro_' . $name, $arguments, $lineno);
$node->setAttribute('safe', \true);
return $node;
}
} else {
$type = Template::ARRAY_CALL;
// slice?
$slice = \false;
if ($stream->test(
9,
':'
)) {
$slice = \true;
$arg = new ConstantExpression(0, $token->getLine());
} else {
$arg = $this->parseExpression();
}
if ($stream->nextIf(
9,
':'
)) {
$slice = \true;
}
if ($slice) {
if ($stream->test(
9,
']'
)) {
$length = new ConstantExpression(null, $token->getLine());
} else {
$length = $this->parseExpression();
}
$class = $this->getFilterNodeClass('slice', $token->getLine());
$arguments = new Node([$arg, $length]);
$filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine());
$stream->expect(
9,
']'
);
return $filter;
}
$stream->expect(
9,
']'
);
}
return new GetAttrExpression($node, $arg, $arguments, $type, $lineno);
}
public function parseFilterExpression($node)
{
$this->parser->getStream()->next();
return $this->parseFilterExpressionRaw($node);
}
public function parseFilterExpressionRaw($node, $tag = null)
{
while (\true) {
$token = $this->parser->getStream()->expect(
5
);
$name = new ConstantExpression($token->getValue(), $token->getLine());
if (!$this->parser->getStream()->test(
9,
'('
)) {
$arguments = new Node();
} else {
$arguments = $this->parseArguments(\true, \false, \true);
}
$class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
$node = new $class($node, $name, $arguments, $token->getLine(), $tag);
if (!$this->parser->getStream()->test(
9,
'|'
)) {
break;
}
$this->parser->getStream()->next();
}
return $node;
}
public function parseArguments($namedArguments = \false, $definition = \false, $allowArrow = \false)
{
$args = [];
$stream = $this->parser->getStream();
$stream->expect(
9,
'(',
'A list of arguments must begin with an opening parenthesis'
);
while (!$stream->test(
9,
')'
)) {
if (!empty($args)) {
$stream->expect(
9,
',',
'Arguments must be separated by a comma'
);
// if the comma above was a trailing comma, early exit the argument parse loop
if ($stream->test(
9,
')'
)) {
break;
}
}
if ($definition) {
$token = $stream->expect(
5,
null,
'An argument must be a name'
);
$value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine());
} else {
$value = $this->parseExpression(0, $allowArrow);
}
$name = null;
if ($namedArguments && ($token = $stream->nextIf(
8,
'='
))) {
if (!$value instanceof NameExpression) {
throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext());
}
$name = $value->getAttribute('name');
if ($definition) {
$value = $this->parsePrimaryExpression();
if (!$this->checkConstantExpression($value)) {
throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).', $token->getLine(), $stream->getSourceContext());
}
} else {
$value = $this->parseExpression(0, $allowArrow);
}
}
if ($definition) {
if (null === $name) {
$name = $value->getAttribute('name');
$value = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine());
}
$args[$name] = $value;
} else {
if (null === $name) {
$args[] = $value;
} else {
$args[$name] = $value;
}
}
}
$stream->expect(
9,
')',
'A list of arguments must be closed by a parenthesis'
);
return new Node($args);
}
public function parseAssignmentExpression()
{
$stream = $this->parser->getStream();
$targets = [];
while (\true) {
$token = $this->parser->getCurrentToken();
if ($stream->test(
8
) && \preg_match(Lexer::REGEX_NAME, $token->getValue())) {
// in this context, string operators are variable names
$this->parser->getStream()->next();
} else {
$stream->expect(
5,
null,
'Only variables can be assigned to'
);
}
$value = $token->getValue();
if (\in_array(\strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), ['true', 'false', 'none', 'null'])) {
throw new SyntaxError(\sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext());
}
$targets[] = new AssignNameExpression($value, $token->getLine());
if (!$stream->nextIf(
9,
','
)) {
break;
}
}
return new Node($targets);
}
public function parseMultitargetExpression()
{
$targets = [];
while (\true) {
$targets[] = $this->parseExpression();
if (!$this->parser->getStream()->nextIf(
9,
','
)) {
break;
}
}
return new Node($targets);
}
private function parseNotTestExpression(Node $node) : NotUnary
{
return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine());
}
private function parseTestExpression(Node $node) : TestExpression
{
$stream = $this->parser->getStream();
[$name, $test] = $this->getTest($node->getTemplateLine());
$class = $this->getTestNodeClass($test);
$arguments = null;
if ($stream->test(
9,
'('
)) {
$arguments = $this->parseArguments(\true);
} elseif ($test->hasOneMandatoryArgument()) {
$arguments = new Node([0 => $this->parsePrimaryExpression()]);
}
if ('defined' === $name && $node instanceof NameExpression && null !== ($alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name')))) {
$node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine());
$node->setAttribute('safe', \true);
}
return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine());
}
private function getTest(int $line) : array
{
$stream = $this->parser->getStream();
$name = $stream->expect(
5
)->getValue();
if ($test = $this->env->getTest($name)) {
return [$name, $test];
}
if ($stream->test(
5
)) {
// try 2-words tests
$name = $name . ' ' . $this->parser->getCurrentToken()->getValue();
if ($test = $this->env->getTest($name)) {
$stream->next();
return [$name, $test];
}
}
$e = new SyntaxError(\sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext());
$e->addSuggestions($name, \array_keys($this->env->getTests()));
throw $e;
}
private function getTestNodeClass(TwigTest $test) : string
{
if ($test->isDeprecated()) {
$stream = $this->parser->getStream();
$message = \sprintf('Twig Test "%s" is deprecated', $test->getName());
if ($test->getAlternative()) {
$message .= \sprintf('. Use "%s" instead', $test->getAlternative());
}
$src = $stream->getSourceContext();
$message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
trigger_deprecation($test->getDeprecatingPackage(), $test->getDeprecatedVersion(), $message);
}
return $test->getNodeClass();
}
private function getFunctionNodeClass(string $name, int $line) : string
{
if (!($function = $this->env->getFunction($name))) {
$e = new SyntaxError(\sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext());
$e->addSuggestions($name, \array_keys($this->env->getFunctions()));
throw $e;
}
if ($function->isDeprecated()) {
$message = \sprintf('Twig Function "%s" is deprecated', $function->getName());
if ($function->getAlternative()) {
$message .= \sprintf('. Use "%s" instead', $function->getAlternative());
}
$src = $this->parser->getStream()->getSourceContext();
$message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
trigger_deprecation($function->getDeprecatingPackage(), $function->getDeprecatedVersion(), $message);
}
return $function->getNodeClass();
}
private function getFilterNodeClass(string $name, int $line) : string
{
if (!($filter = $this->env->getFilter($name))) {
$e = new SyntaxError(\sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext());
$e->addSuggestions($name, \array_keys($this->env->getFilters()));
throw $e;
}
if ($filter->isDeprecated()) {
$message = \sprintf('Twig Filter "%s" is deprecated', $filter->getName());
if ($filter->getAlternative()) {
$message .= \sprintf('. Use "%s" instead', $filter->getAlternative());
}
$src = $this->parser->getStream()->getSourceContext();
$message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
trigger_deprecation($filter->getDeprecatingPackage(), $filter->getDeprecatedVersion(), $message);
}
return $filter->getNodeClass();
}
// checks that the node only contains "constant" elements
private function checkConstantExpression(Node $node) : bool
{
if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression || $node instanceof NegUnary || $node instanceof PosUnary)) {
return \false;
}
foreach ($node as $n) {
if (!$this->checkConstantExpression($n)) {
return \false;
}
}
return \true;
}
}
@@ -0,0 +1,30 @@
<?php
namespace MailPoetVendor\Twig\Extension;
if (!defined('ABSPATH')) exit;
abstract class AbstractExtension implements ExtensionInterface
{
public function getTokenParsers()
{
return [];
}
public function getNodeVisitors()
{
return [];
}
public function getFilters()
{
return [];
}
public function getTests()
{
return [];
}
public function getFunctions()
{
return [];
}
public function getOperators()
{
return [[], []];
}
}
@@ -0,0 +1,35 @@
<?php
namespace MailPoetVendor\Twig\Extension;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Environment;
use MailPoetVendor\Twig\Template;
use MailPoetVendor\Twig\TemplateWrapper;
use MailPoetVendor\Twig\TwigFunction;
final class DebugExtension extends AbstractExtension
{
public function getFunctions() : array
{
// dump is safe if var_dump is overridden by xdebug
$isDumpOutputHtmlSafe = \extension_loaded('xdebug') && (\false === \ini_get('xdebug.overload_var_dump') || \ini_get('xdebug.overload_var_dump')) && (\false === \ini_get('html_errors') || \ini_get('html_errors')) || 'cli' === \PHP_SAPI;
return [new TwigFunction('dump', [self::class, 'dump'], ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => \true, 'needs_environment' => \true, 'is_variadic' => \true])];
}
public static function 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;
}
}
\var_dump($vars);
} else {
\var_dump(...$vars);
}
return \ob_get_clean();
}
}
@@ -0,0 +1,107 @@
<?php
namespace MailPoetVendor\Twig\Extension;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Environment;
use MailPoetVendor\Twig\FileExtensionEscapingStrategy;
use MailPoetVendor\Twig\Node\Expression\ConstantExpression;
use MailPoetVendor\Twig\Node\Expression\Filter\RawFilter;
use MailPoetVendor\Twig\Node\Node;
use MailPoetVendor\Twig\NodeVisitor\EscaperNodeVisitor;
use MailPoetVendor\Twig\Runtime\EscaperRuntime;
use MailPoetVendor\Twig\TokenParser\AutoEscapeTokenParser;
use MailPoetVendor\Twig\TwigFilter;
final class EscaperExtension extends AbstractExtension
{
private $environment;
private $escapers = [];
private $escaper;
private $defaultStrategy;
public function __construct($defaultStrategy = 'html')
{
$this->setDefaultStrategy($defaultStrategy);
}
public function getTokenParsers() : array
{
return [new AutoEscapeTokenParser()];
}
public function getNodeVisitors() : array
{
return [new EscaperNodeVisitor()];
}
public function getFilters() : array
{
return [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])];
}
public function setEnvironment(Environment $environment, bool $triggerDeprecation = \true) : void
{
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);
}
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;
}
public function setDefaultStrategy($defaultStrategy) : void
{
if ('name' === $defaultStrategy) {
$defaultStrategy = [FileExtensionEscapingStrategy::class, 'guess'];
}
$this->defaultStrategy = $defaultStrategy;
}
public function getDefaultStrategy(string $name)
{
// disable string callables to avoid calling a function named html or js,
// or any other upcoming escaping strategy
if (!\is_string($this->defaultStrategy) && \false !== $this->defaultStrategy) {
return \call_user_func($this->defaultStrategy, $name);
}
return $this->defaultStrategy;
}
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);
}
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;
}
public function setSafeClasses(array $safeClasses = [])
{
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);
}
public function addSafeClass(string $class, array $strategies)
{
trigger_deprecation('twig/twig', '3.10', 'The "%s()" method is deprecated, use the "Twig\\Runtime\\EscaperRuntime::addSafeClass()" method instead.', __METHOD__);
if (!isset($this->escaper)) {
throw new \LogicException(\sprintf('You must call "setEnvironment()" before calling "%s()".', __METHOD__));
}
$this->escaper->addSafeClass($class, $strategies);
}
public static function escapeFilterIsSafe(Node $filterArgs)
{
foreach ($filterArgs as $arg) {
if ($arg instanceof ConstantExpression) {
return [$arg->getAttribute('value')];
}
return [];
}
return ['html'];
}
}
@@ -0,0 +1,19 @@
<?php
namespace MailPoetVendor\Twig\Extension;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\ExpressionParser;
use MailPoetVendor\Twig\Node\Expression\AbstractExpression;
use MailPoetVendor\Twig\NodeVisitor\NodeVisitorInterface;
use MailPoetVendor\Twig\TokenParser\TokenParserInterface;
use MailPoetVendor\Twig\TwigFilter;
use MailPoetVendor\Twig\TwigFunction;
use MailPoetVendor\Twig\TwigTest;
interface ExtensionInterface
{
public function getTokenParsers();
public function getNodeVisitors();
public function getFilters();
public function getTests();
public function getFunctions();
public function getOperators();
}
@@ -0,0 +1,7 @@
<?php
namespace MailPoetVendor\Twig\Extension;
if (!defined('ABSPATH')) exit;
interface GlobalsInterface
{
public function getGlobals() : array;
}
@@ -0,0 +1,16 @@
<?php
namespace MailPoetVendor\Twig\Extension;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\NodeVisitor\OptimizerNodeVisitor;
final class OptimizerExtension extends AbstractExtension
{
private $optimizers;
public function __construct(int $optimizers = -1)
{
$this->optimizers = $optimizers;
}
public function getNodeVisitors() : array
{
return [new OptimizerNodeVisitor($this->optimizers)];
}
}
@@ -0,0 +1,30 @@
<?php
namespace MailPoetVendor\Twig\Extension;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Profiler\NodeVisitor\ProfilerNodeVisitor;
use MailPoetVendor\Twig\Profiler\Profile;
class ProfilerExtension extends AbstractExtension
{
private $actives = [];
public function __construct(Profile $profile)
{
$this->actives[] = $profile;
}
public function enter(Profile $profile)
{
$this->actives[0]->addProfile($profile);
\array_unshift($this->actives, $profile);
}
public function leave(Profile $profile)
{
$profile->leave();
\array_shift($this->actives);
if (1 === \count($this->actives)) {
$this->actives[0]->leave();
}
}
public function getNodeVisitors() : array
{
return [new ProfilerNodeVisitor(static::class)];
}
}
@@ -0,0 +1,6 @@
<?php
namespace MailPoetVendor\Twig\Extension;
if (!defined('ABSPATH')) exit;
interface RuntimeExtensionInterface
{
}
@@ -0,0 +1,111 @@
<?php
namespace MailPoetVendor\Twig\Extension;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\NodeVisitor\SandboxNodeVisitor;
use MailPoetVendor\Twig\Sandbox\SecurityNotAllowedMethodError;
use MailPoetVendor\Twig\Sandbox\SecurityNotAllowedPropertyError;
use MailPoetVendor\Twig\Sandbox\SecurityPolicyInterface;
use MailPoetVendor\Twig\Sandbox\SourcePolicyInterface;
use MailPoetVendor\Twig\Source;
use MailPoetVendor\Twig\TokenParser\SandboxTokenParser;
final class SandboxExtension extends AbstractExtension
{
private $sandboxedGlobally;
private $sandboxed;
private $policy;
private $sourcePolicy;
public function __construct(SecurityPolicyInterface $policy, $sandboxed = \false, ?SourcePolicyInterface $sourcePolicy = null)
{
$this->policy = $policy;
$this->sandboxedGlobally = $sandboxed;
$this->sourcePolicy = $sourcePolicy;
}
public function getTokenParsers() : array
{
return [new SandboxTokenParser()];
}
public function getNodeVisitors() : array
{
return [new SandboxNodeVisitor()];
}
public function enableSandbox() : void
{
$this->sandboxed = \true;
}
public function disableSandbox() : void
{
$this->sandboxed = \false;
}
public function isSandboxed(?Source $source = null) : bool
{
return $this->sandboxedGlobally || $this->sandboxed || $this->isSourceSandboxed($source);
}
public function isSandboxedGlobally() : bool
{
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;
}
public function getSecurityPolicy() : SecurityPolicyInterface
{
return $this->policy;
}
public function checkSecurity($tags, $filters, $functions, ?Source $source = null) : void
{
if ($this->isSandboxed($source)) {
$this->policy->checkSecurity($tags, $filters, $functions);
}
}
public function checkMethodAllowed($obj, $method, int $lineno = -1, ?Source $source = null) : void
{
if ($this->isSandboxed($source)) {
try {
$this->policy->checkMethodAllowed($obj, $method);
} catch (SecurityNotAllowedMethodError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
}
}
public function checkPropertyAllowed($obj, $property, int $lineno = -1, ?Source $source = null) : void
{
if ($this->isSandboxed($source)) {
try {
$this->policy->checkPropertyAllowed($obj, $property);
} catch (SecurityNotAllowedPropertyError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
}
}
public function ensureToStringAllowed($obj, int $lineno = -1, ?Source $source = null)
{
if (\is_array($obj)) {
foreach ($obj as $v) {
$this->ensureToStringAllowed($v, $lineno, $source);
}
return $obj;
}
if ($this->isSandboxed($source) && \is_object($obj) && \method_exists($obj, '__toString')) {
try {
$this->policy->checkMethodAllowed($obj, '__toString');
} catch (SecurityNotAllowedMethodError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
}
return $obj;
}
}
@@ -0,0 +1,68 @@
<?php
namespace MailPoetVendor\Twig\Extension;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\NodeVisitor\NodeVisitorInterface;
use MailPoetVendor\Twig\TokenParser\TokenParserInterface;
use MailPoetVendor\Twig\TwigFilter;
use MailPoetVendor\Twig\TwigFunction;
use MailPoetVendor\Twig\TwigTest;
final class StagingExtension extends AbstractExtension
{
private $functions = [];
private $filters = [];
private $visitors = [];
private $tokenParsers = [];
private $tests = [];
public function addFunction(TwigFunction $function) : void
{
if (isset($this->functions[$function->getName()])) {
throw new \LogicException(\sprintf('Function "%s" is already registered.', $function->getName()));
}
$this->functions[$function->getName()] = $function;
}
public function getFunctions() : array
{
return $this->functions;
}
public function addFilter(TwigFilter $filter) : void
{
if (isset($this->filters[$filter->getName()])) {
throw new \LogicException(\sprintf('Filter "%s" is already registered.', $filter->getName()));
}
$this->filters[$filter->getName()] = $filter;
}
public function getFilters() : array
{
return $this->filters;
}
public function addNodeVisitor(NodeVisitorInterface $visitor) : void
{
$this->visitors[] = $visitor;
}
public function getNodeVisitors() : array
{
return $this->visitors;
}
public function addTokenParser(TokenParserInterface $parser) : void
{
if (isset($this->tokenParsers[$parser->getTag()])) {
throw new \LogicException(\sprintf('Tag "%s" is already registered.', $parser->getTag()));
}
$this->tokenParsers[$parser->getTag()] = $parser;
}
public function getTokenParsers() : array
{
return $this->tokenParsers;
}
public function addTest(TwigTest $test) : void
{
if (isset($this->tests[$test->getName()])) {
throw new \LogicException(\sprintf('Test "%s" is already registered.', $test->getName()));
}
$this->tests[$test->getName()] = $test;
}
public function getTests() : array
{
return $this->tests;
}
}
@@ -0,0 +1,17 @@
<?php
namespace MailPoetVendor\Twig\Extension;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Environment;
use MailPoetVendor\Twig\TemplateWrapper;
use MailPoetVendor\Twig\TwigFunction;
final class StringLoaderExtension extends AbstractExtension
{
public function getFunctions() : array
{
return [new TwigFunction('template_from_string', [self::class, 'templateFromString'], ['needs_environment' => \true])];
}
public static function templateFromString(Environment $env, $template, ?string $name = null) : TemplateWrapper
{
return $env->createTemplate((string) $template, $name);
}
}
@@ -0,0 +1,16 @@
<?php
namespace MailPoetVendor\Twig\Extension;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\NodeVisitor\YieldNotReadyNodeVisitor;
final class YieldNotReadyExtension extends AbstractExtension
{
private $useYield;
public function __construct(bool $useYield)
{
$this->useYield = $useYield;
}
public function getNodeVisitors() : array
{
return [new YieldNotReadyNodeVisitor($this->useYield)];
}
}
@@ -0,0 +1,344 @@
<?php
namespace MailPoetVendor\Twig;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Error\RuntimeError;
use MailPoetVendor\Twig\Extension\ExtensionInterface;
use MailPoetVendor\Twig\Extension\GlobalsInterface;
use MailPoetVendor\Twig\Extension\StagingExtension;
use MailPoetVendor\Twig\Node\Expression\AbstractExpression;
use MailPoetVendor\Twig\Node\Expression\Binary\AbstractBinary;
use MailPoetVendor\Twig\Node\Expression\Unary\AbstractUnary;
use MailPoetVendor\Twig\NodeVisitor\NodeVisitorInterface;
use MailPoetVendor\Twig\TokenParser\TokenParserInterface;
final class ExtensionSet
{
private $extensions;
private $initialized = \false;
private $runtimeInitialized = \false;
private $staging;
private $parsers;
private $visitors;
private $filters;
private $tests;
private $functions;
private $unaryOperators;
private $binaryOperators;
private $globals;
private $functionCallbacks = [];
private $filterCallbacks = [];
private $parserCallbacks = [];
private $lastModified = 0;
public function __construct()
{
$this->staging = new StagingExtension();
}
public function initRuntime()
{
$this->runtimeInitialized = \true;
}
public function hasExtension(string $class) : bool
{
return isset($this->extensions[\ltrim($class, '\\')]);
}
public function getExtension(string $class) : ExtensionInterface
{
$class = \ltrim($class, '\\');
if (!isset($this->extensions[$class])) {
throw new RuntimeError(\sprintf('The "%s" extension is not enabled.', $class));
}
return $this->extensions[$class];
}
public function setExtensions(array $extensions) : void
{
foreach ($extensions as $extension) {
$this->addExtension($extension);
}
}
public function getExtensions() : array
{
return $this->extensions;
}
public function getSignature() : string
{
return \json_encode(\array_keys($this->extensions));
}
public function isInitialized() : bool
{
return $this->initialized || $this->runtimeInitialized;
}
public function getLastModified() : int
{
if (0 !== $this->lastModified) {
return $this->lastModified;
}
foreach ($this->extensions as $extension) {
$r = new \ReflectionObject($extension);
if (\is_file($r->getFileName()) && ($extensionTime = \filemtime($r->getFileName())) > $this->lastModified) {
$this->lastModified = $extensionTime;
}
}
return $this->lastModified;
}
public function addExtension(ExtensionInterface $extension) : void
{
$class = \get_class($extension);
if ($this->initialized) {
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));
}
$this->extensions[$class] = $extension;
}
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()));
}
$this->staging->addFunction($function);
}
public function getFunctions() : array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->functions;
}
public function getFunction(string $name) : ?TwigFunction
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->functions[$name])) {
return $this->functions[$name];
}
foreach ($this->functions as $pattern => $function) {
$pattern = \str_replace('\\*', '(.*?)', \preg_quote($pattern, '#'), $count);
if ($count && \preg_match('#^' . $pattern . '$#', $name, $matches)) {
\array_shift($matches);
$function->setArguments($matches);
return $function;
}
}
foreach ($this->functionCallbacks as $callback) {
if (\false !== ($function = $callback($name))) {
return $function;
}
}
return null;
}
public function registerUndefinedFunctionCallback(callable $callable) : void
{
$this->functionCallbacks[] = $callable;
}
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()));
}
$this->staging->addFilter($filter);
}
public function getFilters() : array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->filters;
}
public function getFilter(string $name) : ?TwigFilter
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->filters[$name])) {
return $this->filters[$name];
}
foreach ($this->filters as $pattern => $filter) {
$pattern = \str_replace('\\*', '(.*?)', \preg_quote($pattern, '#'), $count);
if ($count && \preg_match('#^' . $pattern . '$#', $name, $matches)) {
\array_shift($matches);
$filter->setArguments($matches);
return $filter;
}
}
foreach ($this->filterCallbacks as $callback) {
if (\false !== ($filter = $callback($name))) {
return $filter;
}
}
return null;
}
public function registerUndefinedFilterCallback(callable $callable) : void
{
$this->filterCallbacks[] = $callable;
}
public function addNodeVisitor(NodeVisitorInterface $visitor) : void
{
if ($this->initialized) {
throw new \LogicException('Unable to add a node visitor as extensions have already been initialized.');
}
$this->staging->addNodeVisitor($visitor);
}
public function getNodeVisitors() : array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->visitors;
}
public function addTokenParser(TokenParserInterface $parser) : void
{
if ($this->initialized) {
throw new \LogicException('Unable to add a token parser as extensions have already been initialized.');
}
$this->staging->addTokenParser($parser);
}
public function getTokenParsers() : array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->parsers;
}
public function getTokenParser(string $name) : ?TokenParserInterface
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->parsers[$name])) {
return $this->parsers[$name];
}
foreach ($this->parserCallbacks as $callback) {
if (\false !== ($parser = $callback($name))) {
return $parser;
}
}
return null;
}
public function registerUndefinedTokenParserCallback(callable $callable) : void
{
$this->parserCallbacks[] = $callable;
}
public function getGlobals() : array
{
if (null !== $this->globals) {
return $this->globals;
}
$globals = [];
foreach ($this->extensions as $extension) {
if (!$extension instanceof GlobalsInterface) {
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);
}
if ($this->initialized) {
$this->globals = $globals;
}
return $globals;
}
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()));
}
$this->staging->addTest($test);
}
public function getTests() : array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->tests;
}
public function getTest(string $name) : ?TwigTest
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->tests[$name])) {
return $this->tests[$name];
}
foreach ($this->tests as $pattern => $test) {
$pattern = \str_replace('\\*', '(.*?)', \preg_quote($pattern, '#'), $count);
if ($count) {
if (\preg_match('#^' . $pattern . '$#', $name, $matches)) {
\array_shift($matches);
$test->setArguments($matches);
return $test;
}
}
}
return null;
}
public function getUnaryOperators() : array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->unaryOperators;
}
public function getBinaryOperators() : array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->binaryOperators;
}
private function initExtensions() : void
{
$this->parsers = [];
$this->filters = [];
$this->functions = [];
$this->tests = [];
$this->visitors = [];
$this->unaryOperators = [];
$this->binaryOperators = [];
foreach ($this->extensions as $extension) {
$this->initExtension($extension);
}
$this->initExtension($this->staging);
// Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception
$this->initialized = \true;
}
private function initExtension(ExtensionInterface $extension) : void
{
// filters
foreach ($extension->getFilters() as $filter) {
$this->filters[$filter->getName()] = $filter;
}
// functions
foreach ($extension->getFunctions() as $function) {
$this->functions[$function->getName()] = $function;
}
// tests
foreach ($extension->getTests() as $test) {
$this->tests[$test->getName()] = $test;
}
// token parsers
foreach ($extension->getTokenParsers() as $parser) {
if (!$parser instanceof TokenParserInterface) {
throw new \LogicException('getTokenParsers() must return an array of \\Twig\\TokenParser\\TokenParserInterface.');
}
$this->parsers[$parser->getTag()] = $parser;
}
// node visitors
foreach ($extension->getNodeVisitors() as $visitor) {
$this->visitors[] = $visitor;
}
// operators
if ($operators = $extension->getOperators()) {
if (!\is_array($operators)) {
throw new \InvalidArgumentException(\sprintf('"%s::getOperators()" must return an array with operators, got "%s".', \get_class($extension), \is_object($operators) ? \get_class($operators) : \gettype($operators) . (\is_resource($operators) ? '' : '#' . $operators)));
}
if (2 !== \count($operators)) {
throw new \InvalidArgumentException(\sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators)));
}
$this->unaryOperators = \array_merge($this->unaryOperators, $operators[0]);
$this->binaryOperators = \array_merge($this->binaryOperators, $operators[1]);
}
}
}
@@ -0,0 +1,27 @@
<?php
namespace MailPoetVendor\Twig;
if (!defined('ABSPATH')) exit;
class FileExtensionEscapingStrategy
{
public static function guess(string $name)
{
if (\in_array(\substr($name, -1), ['/', '\\'])) {
return 'html';
// return html for directories
}
if (\str_ends_with($name, '.twig')) {
$name = \substr($name, 0, -5);
}
$extension = \pathinfo($name, \PATHINFO_EXTENSION);
switch ($extension) {
case 'js':
return 'js';
case 'css':
return 'css';
case 'txt':
return \false;
default:
return 'html';
}
}
}
@@ -0,0 +1,407 @@
<?php
namespace MailPoetVendor\Twig;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Error\SyntaxError;
class Lexer
{
private $isInitialized = \false;
private $tokens;
private $code;
private $cursor;
private $lineno;
private $end;
private $state;
private $states;
private $brackets;
private $env;
private $source;
private $options;
private $regexes;
private $position;
private $positions;
private $currentVarBlockLine;
public const STATE_DATA = 0;
public const STATE_BLOCK = 1;
public const STATE_VAR = 2;
public const STATE_STRING = 3;
public const STATE_INTERPOLATION = 4;
public const REGEX_NAME = '/[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*/A';
public const REGEX_NUMBER = '/[0-9]+(?:\\.[0-9]+)?([Ee][\\+\\-][0-9]+)?/A';
public const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
public const REGEX_DQ_STRING_DELIM = '/"/A';
public const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\\{))[^#"\\\\]*)*/As';
public const PUNCTUATION = '()[]{}?:.,|';
public function __construct(Environment $env, array $options = [])
{
$this->env = $env;
$this->options = \array_merge(['tag_comment' => ['{#', '#}'], 'tag_block' => ['{%', '%}'], 'tag_variable' => ['{{', '}}'], 'whitespace_trim' => '-', 'whitespace_line_trim' => '~', 'whitespace_line_chars' => ' \\t\\0\\x0B', 'interpolation' => ['#{', '}']], $options);
}
private function initialize()
{
if ($this->isInitialized) {
return;
}
// 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 = [
// }}
'lex_var' => '{
\\s*
(?:' . \preg_quote($this->options['whitespace_trim'] . $this->options['tag_variable'][1], '#') . '\\s*' . '|' . \preg_quote($this->options['whitespace_line_trim'] . $this->options['tag_variable'][1], '#') . '[' . $this->options['whitespace_line_chars'] . ']*' . '|' . \preg_quote($this->options['tag_variable'][1], '#') . ')
}Ax',
// %}
'lex_block' => '{
\\s*
(?:' . \preg_quote($this->options['whitespace_trim'] . $this->options['tag_block'][1], '#') . '\\s*\\n?' . '|' . \preg_quote($this->options['whitespace_line_trim'] . $this->options['tag_block'][1], '#') . '[' . $this->options['whitespace_line_chars'] . ']*' . '|' . \preg_quote($this->options['tag_block'][1], '#') . '\\n?' . ')
}Ax',
// {% endverbatim %}
'lex_raw_data' => '{' . \preg_quote($this->options['tag_block'][0], '#') . '(' . $this->options['whitespace_trim'] . '|' . $this->options['whitespace_line_trim'] . ')?\\s*endverbatim\\s*' . '(?:' . \preg_quote($this->options['whitespace_trim'] . $this->options['tag_block'][1], '#') . '\\s*' . '|' . \preg_quote($this->options['whitespace_line_trim'] . $this->options['tag_block'][1], '#') . '[' . $this->options['whitespace_line_chars'] . ']*' . '|' . \preg_quote($this->options['tag_block'][1], '#') . ')
}sx',
'operator' => $this->getOperatorRegex(),
// #}
'lex_comment' => '{
(?:' . \preg_quote($this->options['whitespace_trim'] . $this->options['tag_comment'][1], '#') . '\\s*\\n?' . '|' . \preg_quote($this->options['whitespace_line_trim'] . $this->options['tag_comment'][1], '#') . '[' . $this->options['whitespace_line_chars'] . ']*' . '|' . \preg_quote($this->options['tag_comment'][1], '#') . '\\n?' . ')
}sx',
// verbatim %}
'lex_block_raw' => '{
\\s*verbatim\\s*
(?:' . \preg_quote($this->options['whitespace_trim'] . $this->options['tag_block'][1], '#') . '\\s*' . '|' . \preg_quote($this->options['whitespace_line_trim'] . $this->options['tag_block'][1], '#') . '[' . $this->options['whitespace_line_chars'] . ']*' . '|' . \preg_quote($this->options['tag_block'][1], '#') . ')
}Asx',
'lex_block_line' => '{\\s*line\\s+(\\d+)\\s*' . \preg_quote($this->options['tag_block'][1], '#') . '}As',
// {{ or {% or {#
'lex_tokens_start' => '{
(' . \preg_quote($this->options['tag_variable'][0], '#') . '|' . \preg_quote($this->options['tag_block'][0], '#') . '|' . \preg_quote($this->options['tag_comment'][0], '#') . ')(' . \preg_quote($this->options['whitespace_trim'], '#') . '|' . \preg_quote($this->options['whitespace_line_trim'], '#') . ')?
}sx',
'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
{
$this->initialize();
$this->source = $source;
$this->code = \str_replace(["\r\n", "\r"], "\n", $source->getCode());
$this->cursor = 0;
$this->lineno = 1;
$this->end = \strlen($this->code);
$this->tokens = [];
$this->state = self::STATE_DATA;
$this->states = [];
$this->brackets = [];
$this->position = -1;
// find all token starts in one go
\preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, \PREG_OFFSET_CAPTURE);
$this->positions = $matches;
while ($this->cursor < $this->end) {
// dispatch to the lexing functions depending
// on the current state
switch ($this->state) {
case self::STATE_DATA:
$this->lexData();
break;
case self::STATE_BLOCK:
$this->lexBlock();
break;
case self::STATE_VAR:
$this->lexVar();
break;
case self::STATE_STRING:
$this->lexString();
break;
case self::STATE_INTERPOLATION:
$this->lexInterpolation();
break;
}
}
$this->pushToken(
-1
);
if (!empty($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);
}
private function lexData() : void
{
// 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(
0,
\substr($this->code, $this->cursor)
);
$this->cursor = $this->end;
return;
}
// Find the first token after the current cursor
$position = $this->positions[0][++$this->position];
while ($position[1] < $this->cursor) {
if ($this->position == \count($this->positions[0]) - 1) {
return;
}
$position = $this->positions[0][++$this->position];
}
// push the template text first
$text = $textContent = \substr($this->code, $this->cursor, $position[1] - $this->cursor);
// trim?
if (isset($this->positions[2][$this->position][0])) {
if ($this->options['whitespace_trim'] === $this->positions[2][$this->position][0]) {
// whitespace_trim detected ({%-, {{- or {#-)
$text = \rtrim($text);
} elseif ($this->options['whitespace_line_trim'] === $this->positions[2][$this->position][0]) {
// whitespace_line_trim detected ({%~, {{~ or {#~)
// don't trim \r and \n
$text = \rtrim($text, " \t\x00\v");
}
}
$this->pushToken(
0,
$text
);
$this->moveCursor($textContent . $position[0]);
switch ($this->positions[1][$this->position][0]) {
case $this->options['tag_comment'][0]:
$this->lexComment();
break;
case $this->options['tag_block'][0]:
// raw data?
if (\preg_match($this->regexes['lex_block_raw'], $this->code, $match, 0, $this->cursor)) {
$this->moveCursor($match[0]);
$this->lexRawData();
// {% line \d+ %}
} elseif (\preg_match($this->regexes['lex_block_line'], $this->code, $match, 0, $this->cursor)) {
$this->moveCursor($match[0]);
$this->lineno = (int) $match[1];
} else {
$this->pushToken(
1
);
$this->pushState(self::STATE_BLOCK);
$this->currentVarBlockLine = $this->lineno;
}
break;
case $this->options['tag_variable'][0]:
$this->pushToken(
2
);
$this->pushState(self::STATE_VAR);
$this->currentVarBlockLine = $this->lineno;
break;
}
}
private function lexBlock() : void
{
if (empty($this->brackets) && \preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(
3
);
$this->moveCursor($match[0]);
$this->popState();
} else {
$this->lexExpression();
}
}
private function lexVar() : void
{
if (empty($this->brackets) && \preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(
4
);
$this->moveCursor($match[0]);
$this->popState();
} else {
$this->lexExpression();
}
}
private function lexExpression() : void
{
// whitespace
if (\preg_match('/\\s+/A', $this->code, $match, 0, $this->cursor)) {
$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);
}
}
// spread operator
if ('.' === $this->code[$this->cursor] && $this->cursor + 2 < $this->end && '.' === $this->code[$this->cursor + 1] && '.' === $this->code[$this->cursor + 2]) {
$this->pushToken(Token::SPREAD_TYPE, '...');
$this->moveCursor('...');
} elseif ('=' === $this->code[$this->cursor] && $this->cursor + 1 < $this->end && '>' === $this->code[$this->cursor + 1]) {
$this->pushToken(Token::ARROW_TYPE, '=>');
$this->moveCursor('=>');
} elseif (\preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(
8,
\preg_replace('/\\s+/', ' ', $match[0])
);
$this->moveCursor($match[0]);
} elseif (\preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) {
$this->pushToken(
5,
$match[0]
);
$this->moveCursor($match[0]);
} elseif (\preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) {
$number = (float) $match[0];
// floats
if (\ctype_digit($match[0]) && $number <= \PHP_INT_MAX) {
$number = (int) $match[0];
// integers lower than the maximum
}
$this->pushToken(
6,
$number
);
$this->moveCursor($match[0]);
} elseif (\str_contains(self::PUNCTUATION, $this->code[$this->cursor])) {
// opening bracket
if (\str_contains('([{', $this->code[$this->cursor])) {
$this->brackets[] = [$this->code[$this->cursor], $this->lineno];
} 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);
}
[$expect, $lineno] = \array_pop($this->brackets);
if ($this->code[$this->cursor] != \strtr($expect, '([{', ')]}')) {
throw new SyntaxError(\sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
}
}
$this->pushToken(
9,
$this->code[$this->cursor]
);
++$this->cursor;
} elseif (\preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) {
$this->pushToken(
7,
\stripcslashes(\substr($match[0], 1, -1))
);
$this->moveCursor($match[0]);
} elseif (\preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {
$this->brackets[] = ['"', $this->lineno];
$this->pushState(self::STATE_STRING);
$this->moveCursor($match[0]);
} else {
throw new SyntaxError(\sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source);
}
}
private function lexRawData() : void
{
if (!\preg_match($this->regexes['lex_raw_data'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new SyntaxError('Unexpected end of file: Unclosed "verbatim" block.', $this->lineno, $this->source);
}
$text = \substr($this->code, $this->cursor, $match[0][1] - $this->cursor);
$this->moveCursor($text . $match[0][0]);
// trim?
if (isset($match[1][0])) {
if ($this->options['whitespace_trim'] === $match[1][0]) {
// whitespace_trim detected ({%-, {{- or {#-)
$text = \rtrim($text);
} else {
// whitespace_line_trim detected ({%~, {{~ or {#~)
// don't trim \r and \n
$text = \rtrim($text, " \t\x00\v");
}
}
$this->pushToken(
0,
$text
);
}
private function lexComment() : void
{
if (!\preg_match($this->regexes['lex_comment'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new SyntaxError('Unclosed comment.', $this->lineno, $this->source);
}
$this->moveCursor(\substr($this->code, $this->cursor, $match[0][1] - $this->cursor) . $match[0][0]);
}
private function lexString() : void
{
if (\preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) {
$this->brackets[] = [$this->options['interpolation'][0], $this->lineno];
$this->pushToken(
10
);
$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(
7,
\stripcslashes($match[0])
);
$this->moveCursor($match[0]);
} elseif (\preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {
[$expect, $lineno] = \array_pop($this->brackets);
if ('"' != $this->code[$this->cursor]) {
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);
}
}
private function lexInterpolation() : void
{
$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(
11
);
$this->moveCursor($match[0]);
$this->popState();
} else {
$this->lexExpression();
}
}
private function pushToken($type, $value = '') : void
{
// do not push empty text tokens
if (0 === $type && '' === $value) {
return;
}
$this->tokens[] = new Token($type, $value, $this->lineno);
}
private function moveCursor($text) : void
{
$this->cursor += \strlen($text);
$this->lineno += \substr_count($text, "\n");
}
private function getOperatorRegex() : string
{
$operators = \array_merge(['='], \array_keys($this->env->getUnaryOperators()), \array_keys($this->env->getBinaryOperators()));
$operators = \array_combine($operators, \array_map('strlen', $operators));
\arsort($operators);
$regex = [];
foreach ($operators as $operator => $length) {
// an operator that ends with a character must be followed by
// a whitespace, a parenthesis, an opening map [ or sequence {
$r = \preg_quote($operator, '/');
if (\ctype_alpha($operator[$length - 1])) {
$r .= '(?=[\\s()\\[{])';
}
// an operator that begins with a character must not have a dot or pipe before
if (\ctype_alpha($operator[0])) {
$r = '(?<![\\.\\|])' . $r;
}
// an operator with a space can be any amount of whitespaces
$r = \preg_replace('/\\s+/', '\\s+', $r);
$regex[] = $r;
}
return '/' . \implode('|', $regex) . '/A';
}
private function pushState($state) : void
{
$this->states[] = $this->state;
$this->state = $state;
}
private function popState() : void
{
if (0 === \count($this->states)) {
throw new \LogicException('Cannot pop state without a previous state.');
}
$this->state = \array_pop($this->states);
}
}
@@ -0,0 +1,42 @@
<?php
namespace MailPoetVendor\Twig\Loader;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Error\LoaderError;
use MailPoetVendor\Twig\Source;
final class ArrayLoader implements LoaderInterface
{
private $templates = [];
public function __construct(array $templates = [])
{
$this->templates = $templates;
}
public function setTemplate(string $name, string $template) : void
{
$this->templates[$name] = $template;
}
public function getSourceContext(string $name) : Source
{
if (!isset($this->templates[$name])) {
throw new LoaderError(\sprintf('Template "%s" is not defined.', $name));
}
return new Source($this->templates[$name], $name);
}
public function exists(string $name) : bool
{
return isset($this->templates[$name]);
}
public function getCacheKey(string $name) : string
{
if (!isset($this->templates[$name])) {
throw new LoaderError(\sprintf('Template "%s" is not defined.', $name));
}
return $name . ':' . $this->templates[$name];
}
public function isFresh(string $name, int $time) : bool
{
if (!isset($this->templates[$name])) {
throw new LoaderError(\sprintf('Template "%s" is not defined.', $name));
}
return \true;
}
}
@@ -0,0 +1,82 @@
<?php
namespace MailPoetVendor\Twig\Loader;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Error\LoaderError;
use MailPoetVendor\Twig\Source;
final class ChainLoader implements LoaderInterface
{
private $hasSourceCache = [];
private $loaders = [];
public function __construct(array $loaders = [])
{
foreach ($loaders as $loader) {
$this->addLoader($loader);
}
}
public function addLoader(LoaderInterface $loader) : void
{
$this->loaders[] = $loader;
$this->hasSourceCache = [];
}
public function getLoaders() : array
{
return $this->loaders;
}
public function getSourceContext(string $name) : Source
{
$exceptions = [];
foreach ($this->loaders as $loader) {
if (!$loader->exists($name)) {
continue;
}
try {
return $loader->getSourceContext($name);
} catch (LoaderError $e) {
$exceptions[] = $e->getMessage();
}
}
throw new LoaderError(\sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' (' . \implode(', ', $exceptions) . ')' : ''));
}
public function exists(string $name) : bool
{
if (isset($this->hasSourceCache[$name])) {
return $this->hasSourceCache[$name];
}
foreach ($this->loaders as $loader) {
if ($loader->exists($name)) {
return $this->hasSourceCache[$name] = \true;
}
}
return $this->hasSourceCache[$name] = \false;
}
public function getCacheKey(string $name) : string
{
$exceptions = [];
foreach ($this->loaders as $loader) {
if (!$loader->exists($name)) {
continue;
}
try {
return $loader->getCacheKey($name);
} catch (LoaderError $e) {
$exceptions[] = \get_class($loader) . ': ' . $e->getMessage();
}
}
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) {
if (!$loader->exists($name)) {
continue;
}
try {
return $loader->isFresh($name, $time);
} catch (LoaderError $e) {
$exceptions[] = \get_class($loader) . ': ' . $e->getMessage();
}
}
throw new LoaderError(\sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' (' . \implode(', ', $exceptions) . ')' : ''));
}
}
@@ -0,0 +1,184 @@
<?php
namespace MailPoetVendor\Twig\Loader;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Error\LoaderError;
use MailPoetVendor\Twig\Source;
class FilesystemLoader implements LoaderInterface
{
public const MAIN_NAMESPACE = '__main__';
protected $paths = [];
protected $cache = [];
protected $errorCache = [];
private $rootPath;
public function __construct($paths = [], ?string $rootPath = null)
{
$this->rootPath = ($rootPath ?? \getcwd()) . \DIRECTORY_SEPARATOR;
if (null !== $rootPath && \false !== ($realPath = \realpath($rootPath))) {
$this->rootPath = $realPath . \DIRECTORY_SEPARATOR;
}
if ($paths) {
$this->setPaths($paths);
}
}
public function getPaths(string $namespace = self::MAIN_NAMESPACE) : array
{
return $this->paths[$namespace] ?? [];
}
public function getNamespaces() : array
{
return \array_keys($this->paths);
}
public function setPaths($paths, string $namespace = self::MAIN_NAMESPACE) : void
{
if (!\is_array($paths)) {
$paths = [$paths];
}
$this->paths[$namespace] = [];
foreach ($paths as $path) {
$this->addPath($path, $namespace);
}
}
public function addPath(string $path, string $namespace = self::MAIN_NAMESPACE) : void
{
// invalidate the cache
$this->cache = $this->errorCache = [];
$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));
}
$this->paths[$namespace][] = \rtrim($path, '/\\');
}
public function prependPath(string $path, string $namespace = self::MAIN_NAMESPACE) : void
{
// invalidate the cache
$this->cache = $this->errorCache = [];
$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));
}
$path = \rtrim($path, '/\\');
if (!isset($this->paths[$namespace])) {
$this->paths[$namespace][] = $path;
} else {
\array_unshift($this->paths[$namespace], $path);
}
}
public function getSourceContext(string $name) : Source
{
if (null === ($path = $this->findTemplate($name))) {
return new Source('', $name, '');
}
return new Source(\file_get_contents($path), $name, $path);
}
public function getCacheKey(string $name) : string
{
if (null === ($path = $this->findTemplate($name))) {
return '';
}
$len = \strlen($this->rootPath);
if (0 === \strncmp($this->rootPath, $path, $len)) {
return \substr($path, $len);
}
return $path;
}
public function exists(string $name)
{
$name = $this->normalizeName($name);
if (isset($this->cache[$name])) {
return \true;
}
return null !== $this->findTemplate($name, \false);
}
public function isFresh(string $name, int $time) : bool
{
// false support to be removed in 3.0
if (null === ($path = $this->findTemplate($name))) {
return \false;
}
return \filemtime($path) < $time;
}
protected function findTemplate(string $name, bool $throw = \true)
{
$name = $this->normalizeName($name);
if (isset($this->cache[$name])) {
return $this->cache[$name];
}
if (isset($this->errorCache[$name])) {
if (!$throw) {
return null;
}
throw new LoaderError($this->errorCache[$name]);
}
try {
[$namespace, $shortname] = $this->parseName($name);
$this->validateName($shortname);
} catch (LoaderError $e) {
if (!$throw) {
return null;
}
throw $e;
}
if (!isset($this->paths[$namespace])) {
$this->errorCache[$name] = \sprintf('There are no registered paths for namespace "%s".', $namespace);
if (!$throw) {
return null;
}
throw new LoaderError($this->errorCache[$name]);
}
foreach ($this->paths[$namespace] as $path) {
if (!$this->isAbsolutePath($path)) {
$path = $this->rootPath . $path;
}
if (\is_file($path . '/' . $shortname)) {
if (\false !== ($realpath = \realpath($path . '/' . $shortname))) {
return $this->cache[$name] = $realpath;
}
return $this->cache[$name] = $path . '/' . $shortname;
}
}
$this->errorCache[$name] = \sprintf('Unable to find template "%s" (looked into: %s).', $name, \implode(', ', $this->paths[$namespace]));
if (!$throw) {
return null;
}
throw new LoaderError($this->errorCache[$name]);
}
private function normalizeName(string $name) : string
{
return \preg_replace('#/{2,}#', '/', \str_replace('\\', '/', $name));
}
private function parseName(string $name, string $default = self::MAIN_NAMESPACE) : array
{
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));
}
$namespace = \substr($name, 1, $pos - 1);
$shortname = \substr($name, $pos + 1);
return [$namespace, $shortname];
}
return [$default, $name];
}
private function validateName(string $name) : void
{
if (\str_contains($name, "\x00")) {
throw new LoaderError('A template name cannot contain NUL bytes.');
}
$name = \ltrim($name, '/');
$parts = \explode('/', $name);
$level = 0;
foreach ($parts as $part) {
if ('..' === $part) {
--$level;
} elseif ('.' !== $part) {
++$level;
}
if ($level < 0) {
throw new LoaderError(\sprintf('Looks like you try to load a template outside configured directories (%s).', $name));
}
}
}
private function isAbsolutePath(string $file) : bool
{
return \strspn($file, '/\\', 0, 1) || \strlen($file) > 3 && \ctype_alpha($file[0]) && ':' === $file[1] && \strspn($file, '/\\', 2, 1) || null !== \parse_url($file, \PHP_URL_SCHEME);
}
}
@@ -0,0 +1,12 @@
<?php
namespace MailPoetVendor\Twig\Loader;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Error\LoaderError;
use MailPoetVendor\Twig\Source;
interface LoaderInterface
{
public function getSourceContext(string $name) : Source;
public function getCacheKey(string $name) : string;
public function isFresh(string $name, int $time) : bool;
public function exists(string $name);
}
@@ -0,0 +1,27 @@
<?php
namespace MailPoetVendor\Twig;
if (!defined('ABSPATH')) exit;
class Markup implements \Countable, \JsonSerializable
{
private $content;
private $charset;
public function __construct($content, $charset)
{
$this->content = (string) $content;
$this->charset = $charset;
}
public function __toString()
{
return $this->content;
}
#[\ReturnTypeWillChange]
public function count()
{
return \mb_strlen($this->content, $this->charset);
}
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->content;
}
}
@@ -0,0 +1,17 @@
<?php
namespace MailPoetVendor\Twig\Node;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Attribute\YieldReady;
use MailPoetVendor\Twig\Compiler;
#[YieldReady]
class AutoEscapeNode extends Node
{
public function __construct($value, Node $body, int $lineno, string $tag = 'autoescape')
{
parent::__construct(['body' => $body], ['value' => $value], $lineno, $tag);
}
public function compile(Compiler $compiler) : void
{
$compiler->subcompile($this->getNode('body'));
}
}
@@ -0,0 +1,18 @@
<?php
namespace MailPoetVendor\Twig\Node;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Attribute\YieldReady;
use MailPoetVendor\Twig\Compiler;
#[YieldReady]
class BlockNode extends Node
{
public function __construct(string $name, Node $body, int $lineno, ?string $tag = null)
{
parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag);
}
public function compile(Compiler $compiler) : void
{
$compiler->addDebugInfo($this)->write(\sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n")->indent()->write("\$macros = \$this->macros;\n");
$compiler->subcompile($this->getNode('body'))->write("return; yield '';\n")->outdent()->write("}\n\n");
}
}
@@ -0,0 +1,17 @@
<?php
namespace MailPoetVendor\Twig\Node;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Attribute\YieldReady;
use MailPoetVendor\Twig\Compiler;
#[YieldReady]
class BlockReferenceNode extends Node implements NodeOutputInterface
{
public function __construct(string $name, int $lineno, ?string $tag = null)
{
parent::__construct([], ['name' => $name], $lineno, $tag);
}
public function compile(Compiler $compiler) : void
{
$compiler->addDebugInfo($this)->write(\sprintf("yield from \$this->unwrap()->yieldBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name')));
}
}
@@ -0,0 +1,8 @@
<?php
namespace MailPoetVendor\Twig\Node;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Attribute\YieldReady;
#[YieldReady]
class BodyNode extends Node
{
}
@@ -0,0 +1,31 @@
<?php
namespace MailPoetVendor\Twig\Node;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Attribute\YieldReady;
use MailPoetVendor\Twig\Compiler;
#[YieldReady]
class CaptureNode extends Node
{
public function __construct(Node $body, int $lineno, ?string $tag = null)
{
parent::__construct(['body' => $body], ['raw' => \false], $lineno, $tag);
}
public function compile(Compiler $compiler) : void
{
$useYield = $compiler->getEnvironment()->useYield();
if (!$this->getAttribute('raw')) {
$compiler->raw("('' === \$tmp = ");
}
$compiler->raw($useYield ? "implode('', iterator_to_array(" : '\\MailPoetVendor\\Twig\\Extension\\CoreExtension::captureOutput(')->raw("(function () use (&\$context, \$macros, \$blocks) {\n")->indent()->subcompile($this->getNode('body'))->write("return; yield '';\n")->outdent()->write('})()');
if ($useYield) {
$compiler->raw(', false))');
} else {
$compiler->raw(')');
}
if (!$this->getAttribute('raw')) {
$compiler->raw(") ? '' : new Markup(\$tmp, \$this->env->getCharset());");
} else {
$compiler->raw(';');
}
}
}
@@ -0,0 +1,13 @@
<?php
namespace MailPoetVendor\Twig\Node;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Attribute\YieldReady;
use MailPoetVendor\Twig\Compiler;
#[YieldReady]
class CheckSecurityCallNode extends Node
{
public function compile(Compiler $compiler)
{
$compiler->write("\$this->sandbox = \$this->extensions[SandboxExtension::class];\n")->write("\$this->checkSecurity();\n");
}
}
@@ -0,0 +1,23 @@
<?php
namespace MailPoetVendor\Twig\Node;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Attribute\YieldReady;
use MailPoetVendor\Twig\Compiler;
#[YieldReady]
class CheckSecurityNode extends Node
{
private $usedFilters;
private $usedTags;
private $usedFunctions;
public function __construct(array $usedFilters, array $usedTags, array $usedFunctions)
{
$this->usedFilters = $usedFilters;
$this->usedTags = $usedTags;
$this->usedFunctions = $usedFunctions;
parent::__construct();
}
public function compile(Compiler $compiler) : void
{
$compiler->write("\n")->write("public function checkSecurity()\n")->write("{\n")->indent()->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(!$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()->write("} catch (SecurityError \$e) {\n")->indent()->write("\$e->setSourceContext(\$this->source);\n\n")->write("if (\$e instanceof SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n")->indent()->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n")->outdent()->write("} elseif (\$e instanceof SecurityNotAllowedFilterError && isset(\$filters[\$e->getFilterName()])) {\n")->indent()->write("\$e->setTemplateLine(\$filters[\$e->getFilterName()]);\n")->outdent()->write("} elseif (\$e instanceof SecurityNotAllowedFunctionError && isset(\$functions[\$e->getFunctionName()])) {\n")->indent()->write("\$e->setTemplateLine(\$functions[\$e->getFunctionName()]);\n")->outdent()->write("}\n\n")->write("throw \$e;\n")->outdent()->write("}\n\n")->outdent()->write("}\n");
}
}
@@ -0,0 +1,19 @@
<?php
namespace MailPoetVendor\Twig\Node;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Attribute\YieldReady;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Node\Expression\AbstractExpression;
#[YieldReady]
class CheckToStringNode extends AbstractExpression
{
public function __construct(AbstractExpression $expr)
{
parent::__construct(['expr' => $expr], [], $expr->getTemplateLine(), $expr->getNodeTag());
}
public function compile(Compiler $compiler) : void
{
$expr = $this->getNode('expr');
$compiler->raw('$this->sandbox->ensureToStringAllowed(')->subcompile($expr)->raw(', ')->repr($expr->getTemplateLine())->raw(', $this->source)');
}
}
@@ -0,0 +1,43 @@
<?php
namespace MailPoetVendor\Twig\Node;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Attribute\YieldReady;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Node\Expression\AbstractExpression;
use MailPoetVendor\Twig\Node\Expression\ConstantExpression;
#[YieldReady]
class DeprecatedNode extends Node
{
public function __construct(AbstractExpression $expr, int $lineno, ?string $tag = null)
{
parent::__construct(['expr' => $expr], [], $lineno, $tag);
}
public function compile(Compiler $compiler) : void
{
$compiler->addDebugInfo($this);
$expr = $this->getNode('expr');
if (!$expr instanceof ConstantExpression) {
$varName = $compiler->getVarName();
$compiler->write(\sprintf('$%s = ', $varName))->subcompile($expr)->raw(";\n");
}
$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(' in "%s" at line %d.', $this->getTemplateName(), $this->getTemplateLine()))->raw(");\n");
}
}
@@ -0,0 +1,18 @@
<?php
namespace MailPoetVendor\Twig\Node;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Attribute\YieldReady;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Node\Expression\AbstractExpression;
#[YieldReady]
class DoNode extends Node
{
public function __construct(AbstractExpression $expr, int $lineno, ?string $tag = null)
{
parent::__construct(['expr' => $expr], [], $lineno, $tag);
}
public function compile(Compiler $compiler) : void
{
$compiler->addDebugInfo($this)->write('')->subcompile($this->getNode('expr'))->raw(";\n");
}
}
@@ -0,0 +1,22 @@
<?php
namespace MailPoetVendor\Twig\Node;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Attribute\YieldReady;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Node\Expression\AbstractExpression;
use MailPoetVendor\Twig\Node\Expression\ConstantExpression;
#[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)
{
parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag);
$this->setAttribute('name', $name);
$this->setAttribute('index', $index);
}
protected function addGetTemplate(Compiler $compiler) : void
{
$compiler->write('$this->loadTemplate(')->string($this->getAttribute('name'))->raw(', ')->repr($this->getTemplateName())->raw(', ')->repr($this->getTemplateLine())->raw(', ')->string($this->getAttribute('index'))->raw(')');
}
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Node\Node;
abstract class AbstractExpression extends Node
{
public function isGenerator() : bool
{
return $this->hasAttribute('is_generator') && $this->getAttribute('is_generator');
}
}
@@ -0,0 +1,102 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class ArrayExpression extends AbstractExpression
{
private $index;
public function __construct(array $elements, int $lineno)
{
parent::__construct($elements, [], $lineno);
$this->index = -1;
foreach ($this->getKeyValuePairs() as $pair) {
if ($pair['key'] instanceof ConstantExpression && \ctype_digit((string) $pair['key']->getAttribute('value')) && $pair['key']->getAttribute('value') > $this->index) {
$this->index = $pair['key']->getAttribute('value');
}
}
}
public function getKeyValuePairs() : array
{
$pairs = [];
foreach (\array_chunk($this->nodes, 2) as $pair) {
$pairs[] = ['key' => $pair[0], 'value' => $pair[1]];
}
return $pairs;
}
public function hasElement(AbstractExpression $key) : bool
{
foreach ($this->getKeyValuePairs() as $pair) {
// we compare the string representation of the keys
// to avoid comparing the line numbers which are not relevant here.
if ((string) $key === (string) $pair['key']) {
return \true;
}
}
return \false;
}
public function addElement(AbstractExpression $value, ?AbstractExpression $key = null) : void
{
if (null === $key) {
$key = new ConstantExpression(++$this->index, $value->getTemplateLine());
}
\array_push($this->nodes, $key, $value);
}
public function compile(Compiler $compiler) : void
{
$keyValuePairs = $this->getKeyValuePairs();
$needsArrayMergeSpread = \PHP_VERSION_ID < 80100 && $this->hasSpreadItem($keyValuePairs);
if ($needsArrayMergeSpread) {
$compiler->raw('CoreExtension::merge(');
}
$compiler->raw('[');
$first = \true;
$reopenAfterMergeSpread = \false;
$nextIndex = 0;
foreach ($keyValuePairs as $pair) {
if ($reopenAfterMergeSpread) {
$compiler->raw(', [');
$reopenAfterMergeSpread = \false;
}
if ($needsArrayMergeSpread && $pair['value']->hasAttribute('spread')) {
$compiler->raw('], ')->subcompile($pair['value']);
$first = \true;
$reopenAfterMergeSpread = \true;
continue;
}
if (!$first) {
$compiler->raw(', ');
}
$first = \false;
if ($pair['value']->hasAttribute('spread') && !$needsArrayMergeSpread) {
$compiler->raw('...')->subcompile($pair['value']);
++$nextIndex;
} else {
$key = $pair['key'] instanceof ConstantExpression ? $pair['key']->getAttribute('value') : null;
if ($nextIndex !== $key) {
if (\is_int($key)) {
$nextIndex = $key + 1;
}
$compiler->subcompile($pair['key'])->raw(' => ');
} else {
++$nextIndex;
}
$compiler->subcompile($pair['value']);
}
}
if (!$reopenAfterMergeSpread) {
$compiler->raw(']');
}
if ($needsArrayMergeSpread) {
$compiler->raw(')');
}
}
private function hasSpreadItem(array $pairs) : bool
{
foreach ($pairs as $pair) {
if ($pair['value']->hasAttribute('spread')) {
return \true;
}
}
return \false;
}
}
@@ -0,0 +1,27 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Node\Node;
class ArrowFunctionExpression extends AbstractExpression
{
public function __construct(AbstractExpression $expr, Node $names, $lineno, $tag = null)
{
parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno, $tag);
}
public function compile(Compiler $compiler) : void
{
$compiler->addDebugInfo($this)->raw('function (');
foreach ($this->getNode('names') as $i => $name) {
if ($i) {
$compiler->raw(', ');
}
$compiler->raw('$__')->raw($name->getAttribute('name'))->raw('__');
}
$compiler->raw(') use ($context, $macros) { ');
foreach ($this->getNode('names') as $name) {
$compiler->raw('$context["')->raw($name->getAttribute('name'))->raw('"] = $__')->raw($name->getAttribute('name'))->raw('__; ');
}
$compiler->raw('return ')->subcompile($this->getNode('expr'))->raw('; }');
}
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class AssignNameExpression extends NameExpression
{
public function compile(Compiler $compiler) : void
{
$compiler->raw('$context[')->string($this->getAttribute('name'))->raw(']');
}
}
@@ -0,0 +1,20 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Node\Expression\AbstractExpression;
use MailPoetVendor\Twig\Node\Node;
abstract class AbstractBinary extends AbstractExpression
{
public function __construct(Node $left, Node $right, int $lineno)
{
parent::__construct(['left' => $left, 'right' => $right], [], $lineno);
}
public function compile(Compiler $compiler) : void
{
$compiler->raw('(')->subcompile($this->getNode('left'))->raw(' ');
$this->operator($compiler);
$compiler->raw(' ')->subcompile($this->getNode('right'))->raw(')');
}
public abstract function operator(Compiler $compiler) : Compiler;
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class AddBinary extends AbstractBinary
{
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('+');
}
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class AndBinary extends AbstractBinary
{
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('&&');
}
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class BitwiseAndBinary extends AbstractBinary
{
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('&');
}
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class BitwiseOrBinary extends AbstractBinary
{
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('|');
}
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class BitwiseXorBinary extends AbstractBinary
{
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('^');
}
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class ConcatBinary extends AbstractBinary
{
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('.');
}
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class DivBinary extends AbstractBinary
{
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('/');
}
}
@@ -0,0 +1,17 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class EndsWithBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
$left = $compiler->getVarName();
$right = $compiler->getVarName();
$compiler->raw(\sprintf('(is_string($%s = ', $left))->subcompile($this->getNode('left'))->raw(\sprintf(') && is_string($%s = ', $right))->subcompile($this->getNode('right'))->raw(\sprintf(') && str_ends_with($%1$s, $%2$s))', $left, $right));
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('');
}
}
@@ -0,0 +1,19 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class EqualBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
if (\PHP_VERSION_ID >= 80000) {
parent::compile($compiler);
return;
}
$compiler->raw('(0 === CoreExtension::compare(')->subcompile($this->getNode('left'))->raw(', ')->subcompile($this->getNode('right'))->raw('))');
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('==');
}
}
@@ -0,0 +1,17 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class FloorDivBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
$compiler->raw('(int) floor(');
parent::compile($compiler);
$compiler->raw(')');
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('/');
}
}
@@ -0,0 +1,19 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class GreaterBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
if (\PHP_VERSION_ID >= 80000) {
parent::compile($compiler);
return;
}
$compiler->raw('(1 === CoreExtension::compare(')->subcompile($this->getNode('left'))->raw(', ')->subcompile($this->getNode('right'))->raw('))');
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('>');
}
}
@@ -0,0 +1,19 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class GreaterEqualBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
if (\PHP_VERSION_ID >= 80000) {
parent::compile($compiler);
return;
}
$compiler->raw('(0 <= CoreExtension::compare(')->subcompile($this->getNode('left'))->raw(', ')->subcompile($this->getNode('right'))->raw('))');
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('>=');
}
}
@@ -0,0 +1,15 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class HasEveryBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
$compiler->raw('CoreExtension::arrayEvery($this->env, ')->subcompile($this->getNode('left'))->raw(', ')->subcompile($this->getNode('right'))->raw(')');
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('');
}
}
@@ -0,0 +1,15 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class HasSomeBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
$compiler->raw('CoreExtension::arraySome($this->env, ')->subcompile($this->getNode('left'))->raw(', ')->subcompile($this->getNode('right'))->raw(')');
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('');
}
}
@@ -0,0 +1,15 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class InBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
$compiler->raw('CoreExtension::inFilter(')->subcompile($this->getNode('left'))->raw(', ')->subcompile($this->getNode('right'))->raw(')');
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('in');
}
}
@@ -0,0 +1,19 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class LessBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
if (\PHP_VERSION_ID >= 80000) {
parent::compile($compiler);
return;
}
$compiler->raw('(-1 === CoreExtension::compare(')->subcompile($this->getNode('left'))->raw(', ')->subcompile($this->getNode('right'))->raw('))');
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('<');
}
}
@@ -0,0 +1,19 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class LessEqualBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
if (\PHP_VERSION_ID >= 80000) {
parent::compile($compiler);
return;
}
$compiler->raw('(0 >= CoreExtension::compare(')->subcompile($this->getNode('left'))->raw(', ')->subcompile($this->getNode('right'))->raw('))');
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('<=');
}
}
@@ -0,0 +1,15 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class MatchesBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
$compiler->raw('CoreExtension::matches(')->subcompile($this->getNode('right'))->raw(', ')->subcompile($this->getNode('left'))->raw(')');
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('');
}
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class ModBinary extends AbstractBinary
{
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('%');
}
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class MulBinary extends AbstractBinary
{
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('*');
}
}
@@ -0,0 +1,19 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class NotEqualBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
if (\PHP_VERSION_ID >= 80000) {
parent::compile($compiler);
return;
}
$compiler->raw('(0 !== CoreExtension::compare(')->subcompile($this->getNode('left'))->raw(', ')->subcompile($this->getNode('right'))->raw('))');
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('!=');
}
}
@@ -0,0 +1,15 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class NotInBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
$compiler->raw('!CoreExtension::inFilter(')->subcompile($this->getNode('left'))->raw(', ')->subcompile($this->getNode('right'))->raw(')');
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('not in');
}
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class OrBinary extends AbstractBinary
{
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('||');
}
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class PowerBinary extends AbstractBinary
{
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('**');
}
}
@@ -0,0 +1,15 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class RangeBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
$compiler->raw('range(')->subcompile($this->getNode('left'))->raw(', ')->subcompile($this->getNode('right'))->raw(')');
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('..');
}
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class SpaceshipBinary extends AbstractBinary
{
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('<=>');
}
}
@@ -0,0 +1,17 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class StartsWithBinary extends AbstractBinary
{
public function compile(Compiler $compiler) : void
{
$left = $compiler->getVarName();
$right = $compiler->getVarName();
$compiler->raw(\sprintf('(is_string($%s = ', $left))->subcompile($this->getNode('left'))->raw(\sprintf(') && is_string($%s = ', $right))->subcompile($this->getNode('right'))->raw(\sprintf(') && str_starts_with($%1$s, $%2$s))', $left, $right));
}
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('');
}
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Binary;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class SubBinary extends AbstractBinary
{
public function operator(Compiler $compiler) : Compiler
{
return $compiler->raw('-');
}
}
@@ -0,0 +1,48 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Node\Node;
class BlockReferenceExpression extends AbstractExpression
{
public function __construct(Node $name, ?Node $template, int $lineno, ?string $tag = null)
{
$nodes = ['name' => $name];
if (null !== $template) {
$nodes['template'] = $template;
}
parent::__construct($nodes, ['is_defined_test' => \false, 'output' => \false], $lineno, $tag);
}
public function compile(Compiler $compiler) : void
{
if ($this->getAttribute('is_defined_test')) {
$this->compileTemplateCall($compiler, 'hasBlock');
} else {
if ($this->getAttribute('output')) {
$compiler->addDebugInfo($this);
$compiler->write('yield from ');
$this->compileTemplateCall($compiler, 'yieldBlock')->raw(";\n");
} else {
$this->compileTemplateCall($compiler, 'renderBlock');
}
}
}
private function compileTemplateCall(Compiler $compiler, string $method) : Compiler
{
if (!$this->hasNode('template')) {
$compiler->write('$this');
} else {
$compiler->write('$this->loadTemplate(')->subcompile($this->getNode('template'))->raw(', ')->repr($this->getTemplateName())->raw(', ')->repr($this->getTemplateLine())->raw(')');
}
$compiler->raw(\sprintf('->unwrap()->%s', $method));
return $this->compileBlockArguments($compiler);
}
private function compileBlockArguments(Compiler $compiler) : Compiler
{
$compiler->raw('(')->subcompile($this->getNode('name'))->raw(', $context');
if (!$this->hasNode('template')) {
$compiler->raw(', $blocks');
}
return $compiler->raw(')');
}
}
@@ -0,0 +1,246 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Error\SyntaxError;
use MailPoetVendor\Twig\Extension\ExtensionInterface;
use MailPoetVendor\Twig\Node\Node;
use MailPoetVendor\Twig\Util\ReflectionCallable;
abstract class CallExpression extends AbstractExpression
{
private $reflector = null;
protected function compileCallable(Compiler $compiler)
{
$callable = $this->getAttribute('callable');
if (\is_string($callable) && !\str_contains($callable, '::')) {
$compiler->raw($callable);
} else {
$rc = $this->reflectCallable($callable);
$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]));
} else {
$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));
} else {
$compiler->raw(\sprintf('$this->extensions[\'%s\']', \ltrim($class, '\\')));
}
$compiler->raw(\sprintf('->%s', $callable[1]));
} else {
$compiler->raw(\sprintf('$this->env->get%s(\'%s\')->getCallable()', \ucfirst($this->getAttribute('type')), $this->getAttribute('name')));
}
}
$this->compileArguments($compiler);
}
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_charset') && $this->getAttribute('needs_charset')) {
$compiler->raw('$this->env->getCharset()');
$first = \false;
}
if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
if (!$first) {
$compiler->raw(', ');
}
$compiler->raw('$this->env');
$first = \false;
}
if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
if (!$first) {
$compiler->raw(', ');
}
$compiler->raw('$context');
$first = \false;
}
if ($this->hasAttribute('arguments')) {
foreach ($this->getAttribute('arguments') as $argument) {
if (!$first) {
$compiler->raw(', ');
}
$compiler->string($argument);
$first = \false;
}
}
if ($this->hasNode('node')) {
if (!$first) {
$compiler->raw(', ');
}
$compiler->subcompile($this->getNode('node'));
$first = \false;
}
if ($this->hasNode('arguments')) {
$callable = $this->getAttribute('callable');
$arguments = $this->getArguments($callable, $this->getNode('arguments'));
foreach ($arguments as $node) {
if (!$first) {
$compiler->raw(', ');
}
$compiler->subcompile($node);
$first = \false;
}
}
$compiler->raw($isArray ? ']' : ')');
}
protected function getArguments($callable, $arguments)
{
$callType = $this->getAttribute('type');
$callName = $this->getAttribute('name');
$parameters = [];
$named = \false;
foreach ($arguments as $name => $node) {
if (!\is_int($name)) {
$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());
}
$parameters[$name] = $node;
}
$isVariadic = $this->hasAttribute('is_variadic') && $this->getAttribute('is_variadic');
if (!$named && !$isVariadic) {
return $parameters;
}
if (!$callable) {
if ($named) {
$message = \sprintf('Named arguments are not supported for %s "%s".', $callType, $callName);
} else {
$message = \sprintf('Arbitrary positional arguments are not supported for %s "%s".', $callType, $callName);
}
throw new \LogicException($message);
}
[$callableParameters, $isPhpVariadic] = $this->getCallableParameters($callable, $isVariadic);
$arguments = [];
$names = [];
$missingArguments = [];
$optionalArguments = [];
$pos = 0;
foreach ($callableParameters as $callableParameter) {
$name = $this->normalizeName($callableParameter->name);
if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) {
if ('start' === $name) {
$name = 'low';
} elseif ('end' === $name) {
$name = 'high';
}
}
$names[] = $name;
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());
}
if (\count($missingArguments)) {
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());
}
$arguments = \array_merge($arguments, $optionalArguments);
$arguments[] = $parameters[$name];
unset($parameters[$name]);
$optionalArguments = [];
} elseif (\array_key_exists($pos, $parameters)) {
$arguments = \array_merge($arguments, $optionalArguments);
$arguments[] = $parameters[$pos];
unset($parameters[$pos]);
$optionalArguments = [];
++$pos;
} elseif ($callableParameter->isDefaultValueAvailable()) {
$optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), -1);
} elseif ($callableParameter->isOptional()) {
if (empty($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());
}
}
if ($isVariadic) {
$arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], -1) : new ArrayExpression([], -1);
foreach ($parameters as $key => $value) {
if (\is_int($key)) {
$arbitraryArguments->addElement($value);
} else {
$arbitraryArguments->addElement($value, new ConstantExpression($key, -1));
}
unset($parameters[$key]);
}
if ($arbitraryArguments->count()) {
$arguments = \array_merge($arguments, $optionalArguments);
$arguments[] = $arbitraryArguments;
}
}
if (!empty($parameters)) {
$unknownParameter = null;
foreach ($parameters as $parameter) {
if ($parameter instanceof Node) {
$unknownParameter = $parameter;
break;
}
}
throw new SyntaxError(\sprintf('Unknown argument%s "%s" for %s "%s(%s)".', \count($parameters) > 1 ? 's' : '', \implode('", "', \array_keys($parameters)), $callType, $callName, \implode(', ', $names)), $unknownParameter ? $unknownParameter->getTemplateLine() : $this->getTemplateLine(), $unknownParameter ? $unknownParameter->getSourceContext() : $this->getSourceContext());
}
return $arguments;
}
protected function normalizeName(string $name) : string
{
return \strtolower(\preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name));
}
private function getCallableParameters($callable, bool $isVariadic) : array
{
$rc = $this->reflectCallable($callable);
$r = $rc->getReflector();
$callableName = $rc->getName();
$parameters = $r->getParameters();
if ($this->hasNode('node')) {
\array_shift($parameters);
}
if ($this->hasAttribute('needs_charset') && $this->getAttribute('needs_charset')) {
\array_shift($parameters);
}
if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
\array_shift($parameters);
}
if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
\array_shift($parameters);
}
if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) {
foreach ($this->getAttribute('arguments') as $argument) {
\array_shift($parameters);
}
}
$isPhpVariadic = \false;
if ($isVariadic) {
$argument = \end($parameters);
$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')));
}
}
return [$parameters, $isPhpVariadic];
}
private function reflectCallable($callable) : ReflectionCallable
{
if (!$this->reflector) {
$this->reflector = new ReflectionCallable($callable, $this->getAttribute('type'), $this->getAttribute('name'));
}
return $this->reflector;
}
}
@@ -0,0 +1,20 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class ConditionalExpression extends AbstractExpression
{
public function __construct(AbstractExpression $expr1, AbstractExpression $expr2, AbstractExpression $expr3, int $lineno)
{
parent::__construct(['expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3], [], $lineno);
}
public function compile(Compiler $compiler) : void
{
// Ternary with no then uses Elvis operator
if ($this->getNode('expr1') === $this->getNode('expr2')) {
$compiler->raw('((')->subcompile($this->getNode('expr1'))->raw(') ?: (')->subcompile($this->getNode('expr3'))->raw('))');
} else {
$compiler->raw('((')->subcompile($this->getNode('expr1'))->raw(') ? (')->subcompile($this->getNode('expr2'))->raw(') : (')->subcompile($this->getNode('expr3'))->raw('))');
}
}
}
@@ -0,0 +1,15 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class ConstantExpression extends AbstractExpression
{
public function __construct($value, int $lineno)
{
parent::__construct([], ['value' => $value], $lineno);
}
public function compile(Compiler $compiler) : void
{
$compiler->repr($this->getAttribute('value'));
}
}
@@ -0,0 +1,30 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Filter;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Node\Expression\ConditionalExpression;
use MailPoetVendor\Twig\Node\Expression\ConstantExpression;
use MailPoetVendor\Twig\Node\Expression\FilterExpression;
use MailPoetVendor\Twig\Node\Expression\GetAttrExpression;
use MailPoetVendor\Twig\Node\Expression\NameExpression;
use MailPoetVendor\Twig\Node\Expression\Test\DefinedTest;
use MailPoetVendor\Twig\Node\Node;
class DefaultFilter extends FilterExpression
{
public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, ?string $tag = null)
{
$default = new FilterExpression($node, new ConstantExpression('default', $node->getTemplateLine()), $arguments, $node->getTemplateLine());
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());
$node = new ConditionalExpression($test, $default, $false, $node->getTemplateLine());
} else {
$node = $default;
}
parent::__construct($node, $filterName, $arguments, $lineno, $tag);
}
public function compile(Compiler $compiler) : void
{
$compiler->subcompile($this->getNode('node'));
}
}
@@ -0,0 +1,24 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression\Filter;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Node\Expression\ConstantExpression;
use MailPoetVendor\Twig\Node\Expression\FilterExpression;
use MailPoetVendor\Twig\Node\Node;
class RawFilter extends FilterExpression
{
public function __construct(Node $node, ?ConstantExpression $filterName = null, ?Node $arguments = null, int $lineno = 0, ?string $tag = null)
{
if (null === $filterName) {
$filterName = new ConstantExpression('raw', $node->getTemplateLine());
}
if (null === $arguments) {
$arguments = new Node();
}
parent::__construct($node, $filterName, $arguments, $lineno ?: $node->getTemplateLine(), $tag ?: $node->getNodeTag());
}
public function compile(Compiler $compiler) : void
{
$compiler->subcompile($this->getNode('node'));
}
}
@@ -0,0 +1,33 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Node\Node;
class FilterExpression extends CallExpression
{
public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, ?string $tag = null)
{
parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], ['name' => $filterName->getAttribute('value'), 'type' => 'filter'], $lineno, $tag);
}
public function compile(Compiler $compiler) : void
{
$name = $this->getNode('filter')->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->setAttribute('name', $name);
}
if ('raw' === $name) {
trigger_deprecation('twig/twig', '3.11', 'Creating the "raw" filter via "FilterExpression" is deprecated; use "RawFilter" instead.');
$compiler->subcompile($this->getNode('node'));
return;
}
$filter = $compiler->getEnvironment()->getFilter($name);
$this->setAttribute('needs_charset', $filter->needsCharset());
$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());
$this->compileCallable($compiler);
}
}
@@ -0,0 +1,29 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Extension\CoreExtension;
use MailPoetVendor\Twig\Node\Node;
class FunctionExpression extends CallExpression
{
public function __construct(string $name, Node $arguments, int $lineno)
{
parent::__construct(['arguments' => $arguments], ['name' => $name, 'type' => 'function', 'is_defined_test' => \false], $lineno);
}
public function compile(Compiler $compiler)
{
$name = $this->getAttribute('name');
$function = $compiler->getEnvironment()->getFunction($name);
$this->setAttribute('needs_charset', $function->needsCharset());
$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 = [CoreExtension::class, 'constantIsDefined'];
}
$this->setAttribute('callable', $callable);
$this->setAttribute('is_variadic', $function->isVariadic());
$this->compileCallable($compiler);
}
}
@@ -0,0 +1,47 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Extension\SandboxExtension;
use MailPoetVendor\Twig\Template;
class GetAttrExpression extends AbstractExpression
{
public function __construct(AbstractExpression $node, AbstractExpression $attribute, ?AbstractExpression $arguments, string $type, int $lineno)
{
$nodes = ['node' => $node, 'attribute' => $attribute];
if (null !== $arguments) {
$nodes['arguments'] = $arguments;
}
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 ($this->getAttribute('optimizable') && (!$env->isStrictVariables() || $this->getAttribute('ignore_strict_check')) && !$this->getAttribute('is_defined_test') && Template::ARRAY_CALL === $this->getAttribute('type')) {
$var = '$' . $compiler->getVarName();
$compiler->raw('((' . $var . ' = ')->subcompile($this->getNode('node'))->raw(') && is_array(')->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 && in_array(')->raw('get_class(' . $var . ')')->raw(', CoreExtension::ARRAY_LIKE_CLASSES, true) ? (')->raw($var)->raw('[')->subcompile($this->getNode('attribute'))->raw('] ?? null) : ');
}
$compiler->raw('CoreExtension::getAttribute($this->env, $this->source, ');
if ($this->getAttribute('ignore_strict_check')) {
$this->getNode('node')->setAttribute('ignore_strict_check', \true);
}
$compiler->subcompile($this->getNode('node'))->raw(', ')->subcompile($this->getNode('attribute'));
if ($this->hasNode('arguments')) {
$compiler->raw(', ')->subcompile($this->getNode('arguments'));
} else {
$compiler->raw(', []');
}
$compiler->raw(', ')->repr($this->getAttribute('type'))->raw(', ')->repr($this->getAttribute('is_defined_test'))->raw(', ')->repr($this->getAttribute('ignore_strict_check'))->raw(', ')->repr($env->hasExtension(SandboxExtension::class))->raw(', ')->repr($this->getNode('node')->getTemplateLine())->raw(')');
if ($arrayAccessSandbox) {
$compiler->raw(')');
}
}
}
@@ -0,0 +1,16 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Node\Node;
final class InlinePrint extends AbstractExpression
{
public function __construct(Node $node, int $lineno)
{
parent::__construct(['node' => $node], [], $lineno);
}
public function compile(Compiler $compiler) : void
{
$compiler->raw('yield ')->subcompile($this->getNode('node'));
}
}
@@ -0,0 +1,32 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class MethodCallExpression extends AbstractExpression
{
public function __construct(AbstractExpression $node, string $method, ArrayExpression $arguments, int $lineno)
{
parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => \false, 'is_defined_test' => \false], $lineno);
if ($node instanceof NameExpression) {
$node->setAttribute('always_defined', \true);
}
}
public function compile(Compiler $compiler) : void
{
if ($this->getAttribute('is_defined_test')) {
$compiler->raw('method_exists($macros[')->repr($this->getNode('node')->getAttribute('name'))->raw('], ')->repr($this->getAttribute('method'))->raw(')');
return;
}
$compiler->raw('CoreExtension::callMacro($macros[')->repr($this->getNode('node')->getAttribute('name'))->raw('], ')->repr($this->getAttribute('method'))->raw(', [');
$first = \true;
$args = $this->getNode('arguments');
foreach ($args->getKeyValuePairs() as $pair) {
if (!$first) {
$compiler->raw(', ');
}
$first = \false;
$compiler->subcompile($pair['value']);
}
$compiler->raw('], ')->repr($this->getTemplateLine())->raw(', $context, $this->getSourceContext())');
}
}
@@ -0,0 +1,46 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
class NameExpression extends AbstractExpression
{
private $specialVars = ['_self' => '$this->getTemplateName()', '_context' => '$context', '_charset' => '$this->env->getCharset()'];
public function __construct(string $name, int $lineno)
{
parent::__construct([], ['name' => $name, 'is_defined_test' => \false, 'ignore_strict_check' => \false, 'always_defined' => \false], $lineno);
}
public function compile(Compiler $compiler) : void
{
$name = $this->getAttribute('name');
$compiler->addDebugInfo($this);
if ($this->getAttribute('is_defined_test')) {
if (isset($this->specialVars[$name])) {
$compiler->repr(\true);
} elseif (\PHP_VERSION_ID >= 70400) {
$compiler->raw('array_key_exists(')->string($name)->raw(', $context)');
} else {
$compiler->raw('(isset($context[')->string($name)->raw(']) || array_key_exists(')->string($name)->raw(', $context))');
}
} elseif (isset($this->specialVars[$name])) {
$compiler->raw($this->specialVars[$name]);
} elseif ($this->getAttribute('always_defined')) {
$compiler->raw('$context[')->string($name)->raw(']');
} else {
if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) {
$compiler->raw('($context[')->string($name)->raw('] ?? null)');
} else {
$compiler->raw('(isset($context[')->string($name)->raw(']) || array_key_exists(')->string($name)->raw(', $context) ? $context[')->string($name)->raw('] : (function () { throw new RuntimeError(\'Variable ')->string($name)->raw(' does not exist.\', ')->repr($this->lineno)->raw(', $this->source); })()')->raw(')');
}
}
}
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')]);
}
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');
}
}
@@ -0,0 +1,30 @@
<?php
namespace MailPoetVendor\Twig\Node\Expression;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Twig\Compiler;
use MailPoetVendor\Twig\Node\Expression\Binary\AndBinary;
use MailPoetVendor\Twig\Node\Expression\Test\DefinedTest;
use MailPoetVendor\Twig\Node\Expression\Test\NullTest;
use MailPoetVendor\Twig\Node\Expression\Unary\NotUnary;
use MailPoetVendor\Twig\Node\Node;
class NullCoalesceExpression extends ConditionalExpression
{
public function __construct(Node $left, Node $right, int $lineno)
{
$test = new DefinedTest(clone $left, 'defined', new Node(), $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()), $left->getTemplateLine());
}
parent::__construct($test, $left, $right, $lineno);
}
public function compile(Compiler $compiler) : void
{
if ($this->getNode('expr2') instanceof NameExpression) {
$this->getNode('expr2')->setAttribute('always_defined', \true);
$compiler->raw('((')->subcompile($this->getNode('expr2'))->raw(') ?? (')->subcompile($this->getNode('expr3'))->raw('))');
} else {
parent::compile($compiler);
}
}
}

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