Update website
This commit is contained in:
parent
bb4b0f9be8
commit
011b183e28
4263 changed files with 3014 additions and 720369 deletions
161
admin/phpMyAdmin/vendor/twig/twig/CHANGELOG
vendored
161
admin/phpMyAdmin/vendor/twig/twig/CHANGELOG
vendored
|
@ -1,161 +0,0 @@
|
|||
# 3.5.0 (2022-12-27)
|
||||
|
||||
* Make Twig\ExpressionParser non internal
|
||||
* Add "some" and "every" filters
|
||||
* Add Compile::reset(
|
||||
* Throw a better runtime error when the "matches" regexp is not valid
|
||||
* Add "twig *_names" intl functions
|
||||
* Fix optimizing closures callbacks
|
||||
* Add a better exception when getting an undefined constant via `constant`
|
||||
* Fix `if` nodes when outside of a block and with an empty body
|
||||
|
||||
# 3.4.3 (2022-09-28)
|
||||
|
||||
* Fix a security issue on filesystem loader (possibility to load a template outside a configured directory)
|
||||
|
||||
# 3.4.2 (2022-08-12)
|
||||
|
||||
* Allow inherited magic method to still run with calling class
|
||||
* Fix CallExpression::reflectCallable() throwing TypeError
|
||||
* Fix typo in naming (currency_code)
|
||||
|
||||
# 3.4.1 (2022-05-17)
|
||||
|
||||
* Fix optimizing non-public named closures
|
||||
|
||||
# 3.4.0 (2022-05-22)
|
||||
|
||||
* Add support for named closures
|
||||
|
||||
# 3.3.10 (2022-04-06)
|
||||
|
||||
* Enable bytecode invalidation when auto_reload is enabled
|
||||
|
||||
# 3.3.9 (2022-03-25)
|
||||
|
||||
* Fix custom escapers when using multiple Twig environments
|
||||
* Add support for "constant('class', object)"
|
||||
* Do not reuse internally generated variable names during parsing
|
||||
|
||||
# 3.3.8 (2022-02-04)
|
||||
|
||||
* Fix a security issue when in a sandbox: the `sort` filter must require a Closure for the `arrow` parameter
|
||||
* Fix deprecation notice on `round`
|
||||
* Fix call to deprecated `convertToHtml` method
|
||||
|
||||
# 3.3.7 (2022-01-03)
|
||||
|
||||
* Allow more null support when Twig expects a string (for better 8.1 support)
|
||||
* Only use Commonmark extensions if markdown enabled
|
||||
|
||||
# 3.3.6 (2022-01-03)
|
||||
|
||||
* Only use Commonmark extensions if markdown enabled
|
||||
|
||||
# 3.3.5 (2022-01-03)
|
||||
|
||||
* Allow CommonMark extensions to easily be added
|
||||
* Allow null when Twig expects a string (for better 8.1 support)
|
||||
* Make some performance optimizations
|
||||
* Allow Symfony translation contract v3+
|
||||
|
||||
# 3.3.4 (2021-11-25)
|
||||
|
||||
* Bump minimum supported Symfony component versions
|
||||
* Fix a deprecated message
|
||||
|
||||
# 3.3.3 (2021-09-17)
|
||||
|
||||
* Allow Symfony 6
|
||||
* Improve compatibility with PHP 8.1
|
||||
* Explicitly specify the encoding for mb_ord in JS escaper
|
||||
|
||||
# 3.3.2 (2021-05-16)
|
||||
|
||||
* Revert "Throw a proper exception when a template name is an absolute path (as it has never been supported)"
|
||||
|
||||
# 3.3.1 (2021-05-12)
|
||||
|
||||
* Fix PHP 8.1 compatibility
|
||||
* Throw a proper exception when a template name is an absolute path (as it has never been supported)
|
||||
|
||||
# 3.3.0 (2021-02-08)
|
||||
|
||||
* Fix macro calls in a "cache" tag
|
||||
* Add the slug filter
|
||||
* Allow extra bundle to be compatible with Twig 2
|
||||
|
||||
# 3.2.1 (2021-01-05)
|
||||
|
||||
* Fix extra bundle compat with older versions of Symfony
|
||||
|
||||
# 3.2.0 (2021-01-05)
|
||||
|
||||
* Add the Cache extension in the "extra" repositories: "cache" tag
|
||||
* Add "registerUndefinedTokenParserCallback"
|
||||
* Mark built-in node visitors as @internal
|
||||
* Fix "odd" not working for negative numbers
|
||||
|
||||
# 3.1.1 (2020-10-27)
|
||||
|
||||
* Fix "include(template_from_string())"
|
||||
|
||||
# 3.1.0 (2020-10-21)
|
||||
|
||||
* Fix sandbox support when using "include(template_from_string())"
|
||||
* Make round brackets optional for one argument tests like "same as" or "divisible by"
|
||||
* Add support for ES2015 style object initialisation shortcut { a } is the same as { 'a': a }
|
||||
|
||||
# 3.0.5 (2020-08-05)
|
||||
|
||||
* Fix twig_compare w.r.t. whitespace trimming
|
||||
* Fix sandbox not disabled if syntax error occurs within {% sandbox %} tag
|
||||
* Fix a regression when not using a space before an operator
|
||||
* Restrict callables to closures in filters
|
||||
* Allow trailing commas in argument lists (in calls as well as definitions)
|
||||
|
||||
# 3.0.4 (2020-07-05)
|
||||
|
||||
* Fix comparison operators
|
||||
* Fix options not taken into account when using "Michelf\MarkdownExtra"
|
||||
* Fix "Twig\Extra\Intl\IntlExtension::getCountryName()" to accept "null" as a first argument
|
||||
* Throw exception in case non-Traversable data is passed to "filter"
|
||||
* Fix context optimization on PHP 7.4
|
||||
* Fix PHP 8 compatibility
|
||||
* Fix ambiguous syntax parsing
|
||||
|
||||
# 3.0.3 (2020-02-11)
|
||||
|
||||
* Add a check to ensure that iconv() is defined
|
||||
|
||||
# 3.0.2 (2020-02-11)
|
||||
|
||||
* Avoid exceptions when an intl resource is not found
|
||||
* Fix implementation of case-insensitivity for method names
|
||||
|
||||
# 3.0.1 (2019-12-28)
|
||||
|
||||
* fixed Symfony 5.0 support for the HTML extra extension
|
||||
|
||||
# 3.0.0 (2019-11-15)
|
||||
|
||||
* fixed number formatter in Intl extra extension when using a formatter prototype
|
||||
|
||||
# 3.0.0-BETA1 (2019-11-11)
|
||||
|
||||
* removed the "if" condition support on the "for" tag
|
||||
* made the in, <, >, <=, >=, ==, and != operators more strict when comparing strings and integers/floats
|
||||
* removed the "filter" tag
|
||||
* added type hints everywhere
|
||||
* changed Environment::resolveTemplate() to always return a TemplateWrapper instance
|
||||
* removed Template::__toString()
|
||||
* removed Parser::isReservedMacroName()
|
||||
* removed SanboxedPrintNode
|
||||
* removed Node::setTemplateName()
|
||||
* made classes maked as "@final" final
|
||||
* removed InitRuntimeInterface, ExistsLoaderInterface, and SourceContextLoaderInterface
|
||||
* removed the "spaceless" tag
|
||||
* removed Twig\Environment::getBaseTemplateClass() and Twig\Environment::setBaseTemplateClass()
|
||||
* removed the "base_template_class" option on Twig\Environment
|
||||
* bumped minimum PHP version to 7.2
|
||||
* removed PSR-0 classes
|
27
admin/phpMyAdmin/vendor/twig/twig/LICENSE
vendored
27
admin/phpMyAdmin/vendor/twig/twig/LICENSE
vendored
|
@ -1,27 +0,0 @@
|
|||
Copyright (c) 2009-2022 by the Twig Team.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of Twig nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
50
admin/phpMyAdmin/vendor/twig/twig/composer.json
vendored
50
admin/phpMyAdmin/vendor/twig/twig/composer.json
vendored
|
@ -1,50 +0,0 @@
|
|||
{
|
||||
"name": "twig/twig",
|
||||
"type": "library",
|
||||
"description": "Twig, the flexible, fast, and secure template language for PHP",
|
||||
"keywords": ["templating"],
|
||||
"homepage": "https://twig.symfony.com",
|
||||
"license": "BSD-3-Clause",
|
||||
"minimum-stability": "dev",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com",
|
||||
"homepage": "http://fabien.potencier.org",
|
||||
"role": "Lead Developer"
|
||||
},
|
||||
{
|
||||
"name": "Twig Team",
|
||||
"role": "Contributors"
|
||||
},
|
||||
{
|
||||
"name": "Armin Ronacher",
|
||||
"email": "armin.ronacher@active-4.com",
|
||||
"role": "Project Founder"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/polyfill-mbstring": "^1.3",
|
||||
"symfony/polyfill-ctype": "^1.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0",
|
||||
"psr/container": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4" : {
|
||||
"Twig\\" : "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4" : {
|
||||
"Twig\\Tests\\" : "tests/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.5-dev"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Cache;
|
||||
|
||||
/**
|
||||
* Interface implemented by cache classes.
|
||||
*
|
||||
* It is highly recommended to always store templates on the filesystem to
|
||||
* benefit from the PHP opcode cache. This interface is mostly useful if you
|
||||
* need to implement a custom strategy for storing templates on the filesystem.
|
||||
*
|
||||
* @author Andrew Tch <andrew@noop.lv>
|
||||
*/
|
||||
interface CacheInterface
|
||||
{
|
||||
/**
|
||||
* Generates a cache key for the given template class name.
|
||||
*/
|
||||
public function generateKey(string $name, string $className): string;
|
||||
|
||||
/**
|
||||
* Writes the compiled template to cache.
|
||||
*
|
||||
* @param string $content The template representation as a PHP class
|
||||
*/
|
||||
public function write(string $key, string $content): void;
|
||||
|
||||
/**
|
||||
* Loads a template from the cache.
|
||||
*/
|
||||
public function load(string $key): void;
|
||||
|
||||
/**
|
||||
* Returns the modification timestamp of a key.
|
||||
*/
|
||||
public function getTimestamp(string $key): int;
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Cache;
|
||||
|
||||
/**
|
||||
* Implements a cache on the filesystem.
|
||||
*
|
||||
* @author Andrew Tch <andrew@noop.lv>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Cache;
|
||||
|
||||
/**
|
||||
* Implements a no-cache strategy.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
223
admin/phpMyAdmin/vendor/twig/twig/src/Compiler.php
vendored
223
admin/phpMyAdmin/vendor/twig/twig/src/Compiler.php
vendored
|
@ -1,223 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig;
|
||||
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Compiler
|
||||
{
|
||||
private $lastLine;
|
||||
private $source;
|
||||
private $indentation;
|
||||
private $env;
|
||||
private $debugInfo = [];
|
||||
private $sourceOffset;
|
||||
private $sourceLine;
|
||||
private $varNameSalt = 0;
|
||||
|
||||
public function __construct(Environment $env)
|
||||
{
|
||||
$this->env = $env;
|
||||
}
|
||||
|
||||
public function getEnvironment(): Environment
|
||||
{
|
||||
return $this->env;
|
||||
}
|
||||
|
||||
public function getSource(): string
|
||||
{
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function compile(Node $node, int $indentation = 0)
|
||||
{
|
||||
$this->reset($indentation);
|
||||
$node->compile($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function subcompile(Node $node, bool $raw = true)
|
||||
{
|
||||
if (false === $raw) {
|
||||
$this->source .= str_repeat(' ', $this->indentation * 4);
|
||||
}
|
||||
|
||||
$node->compile($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a raw string to the compiled code.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function raw(string $string)
|
||||
{
|
||||
$this->source .= $string;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a string to the compiled code by adding indentation.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function write(...$strings)
|
||||
{
|
||||
foreach ($strings as $string) {
|
||||
$this->source .= str_repeat(' ', $this->indentation * 4).$string;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a quoted string to the compiled code.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function string(string $value)
|
||||
{
|
||||
$this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\"));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a PHP representation of a given value.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function indent(int $step = 1)
|
||||
{
|
||||
$this->indentation += $step;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*
|
||||
* @throws \LogicException When trying to outdent too much so the indentation would become negative
|
||||
*/
|
||||
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++);
|
||||
}
|
||||
}
|
|
@ -1,841 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig;
|
||||
|
||||
use Twig\Cache\CacheInterface;
|
||||
use Twig\Cache\FilesystemCache;
|
||||
use Twig\Cache\NullCache;
|
||||
use Twig\Error\Error;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Extension\CoreExtension;
|
||||
use Twig\Extension\EscaperExtension;
|
||||
use Twig\Extension\ExtensionInterface;
|
||||
use Twig\Extension\OptimizerExtension;
|
||||
use Twig\Loader\ArrayLoader;
|
||||
use Twig\Loader\ChainLoader;
|
||||
use Twig\Loader\LoaderInterface;
|
||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||
use Twig\Node\Expression\Unary\AbstractUnary;
|
||||
use Twig\Node\ModuleNode;
|
||||
use Twig\Node\Node;
|
||||
use Twig\NodeVisitor\NodeVisitorInterface;
|
||||
use Twig\RuntimeLoader\RuntimeLoaderInterface;
|
||||
use Twig\TokenParser\TokenParserInterface;
|
||||
|
||||
/**
|
||||
* Stores the Twig configuration and renders templates.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Environment
|
||||
{
|
||||
public const VERSION = '3.5.0';
|
||||
public const VERSION_ID = 30500;
|
||||
public const MAJOR_VERSION = 3;
|
||||
public const MINOR_VERSION = 5;
|
||||
public const RELEASE_VERSION = 0;
|
||||
public const EXTRA_VERSION = '';
|
||||
|
||||
private $charset;
|
||||
private $loader;
|
||||
private $debug;
|
||||
private $autoReload;
|
||||
private $cache;
|
||||
private $lexer;
|
||||
private $parser;
|
||||
private $compiler;
|
||||
/** @var array<string, mixed> */
|
||||
private $globals = [];
|
||||
private $resolvedGlobals;
|
||||
private $loadedTemplates;
|
||||
private $strictVariables;
|
||||
private $templateClassPrefix = '__TwigTemplate_';
|
||||
private $originalCache;
|
||||
private $extensionSet;
|
||||
private $runtimeLoaders = [];
|
||||
private $runtimes = [];
|
||||
private $optionsHash;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Available options:
|
||||
*
|
||||
* * debug: When set to true, it automatically set "auto_reload" to true as
|
||||
* well (default to false).
|
||||
*
|
||||
* * charset: The charset used by the templates (default to UTF-8).
|
||||
*
|
||||
* * cache: An absolute path where to store the compiled templates,
|
||||
* a \Twig\Cache\CacheInterface implementation,
|
||||
* or false to disable compilation cache (default).
|
||||
*
|
||||
* * auto_reload: Whether to reload the template if the original source changed.
|
||||
* If you don't provide the auto_reload option, it will be
|
||||
* determined automatically based on the debug value.
|
||||
*
|
||||
* * strict_variables: Whether to ignore invalid variables in templates
|
||||
* (default to false).
|
||||
*
|
||||
* * autoescape: Whether to enable auto-escaping (default to html):
|
||||
* * false: disable auto-escaping
|
||||
* * html, js: set the autoescaping to one of the supported strategies
|
||||
* * name: set the autoescaping strategy based on the template name extension
|
||||
* * PHP callback: a PHP callback that returns an escaping strategy based on the template "name"
|
||||
*
|
||||
* * optimizations: A flag that indicates which optimizations to apply
|
||||
* (default to -1 which means that all optimizations are enabled;
|
||||
* set it to 0 to disable).
|
||||
*/
|
||||
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,
|
||||
], $options);
|
||||
|
||||
$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->addExtension(new CoreExtension());
|
||||
$this->addExtension(new EscaperExtension($options['autoescape']));
|
||||
$this->addExtension(new OptimizerExtension($options['optimizations']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables debugging mode.
|
||||
*/
|
||||
public function enableDebug()
|
||||
{
|
||||
$this->debug = true;
|
||||
$this->updateOptionsHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables debugging mode.
|
||||
*/
|
||||
public function disableDebug()
|
||||
{
|
||||
$this->debug = false;
|
||||
$this->updateOptionsHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if debug mode is enabled.
|
||||
*
|
||||
* @return bool true if debug mode is enabled, false otherwise
|
||||
*/
|
||||
public function isDebug()
|
||||
{
|
||||
return $this->debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the auto_reload option.
|
||||
*/
|
||||
public function enableAutoReload()
|
||||
{
|
||||
$this->autoReload = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the auto_reload option.
|
||||
*/
|
||||
public function disableAutoReload()
|
||||
{
|
||||
$this->autoReload = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the auto_reload option is enabled.
|
||||
*
|
||||
* @return bool true if auto_reload is enabled, false otherwise
|
||||
*/
|
||||
public function isAutoReload()
|
||||
{
|
||||
return $this->autoReload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the strict_variables option.
|
||||
*/
|
||||
public function enableStrictVariables()
|
||||
{
|
||||
$this->strictVariables = true;
|
||||
$this->updateOptionsHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the strict_variables option.
|
||||
*/
|
||||
public function disableStrictVariables()
|
||||
{
|
||||
$this->strictVariables = false;
|
||||
$this->updateOptionsHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the strict_variables option is enabled.
|
||||
*
|
||||
* @return bool true if strict_variables is enabled, false otherwise
|
||||
*/
|
||||
public function isStrictVariables()
|
||||
{
|
||||
return $this->strictVariables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current cache implementation.
|
||||
*
|
||||
* @param bool $original Whether to return the original cache option or the real cache instance
|
||||
*
|
||||
* @return CacheInterface|string|false A Twig\Cache\CacheInterface implementation,
|
||||
* an absolute path to the compiled templates,
|
||||
* or false to disable cache
|
||||
*/
|
||||
public function getCache($original = true)
|
||||
{
|
||||
return $original ? $this->originalCache : $this->cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current cache implementation.
|
||||
*
|
||||
* @param CacheInterface|string|false $cache A Twig\Cache\CacheInterface implementation,
|
||||
* an absolute path to the compiled templates,
|
||||
* or false to disable 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.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the template class associated with the given string.
|
||||
*
|
||||
* The generated template class is based on the following parameters:
|
||||
*
|
||||
* * The cache key for the given template;
|
||||
* * The currently enabled extensions;
|
||||
* * Whether the Twig C extension is available or not;
|
||||
* * PHP version;
|
||||
* * Twig version;
|
||||
* * Options with what environment was created.
|
||||
*
|
||||
* @param string $name The name for which to calculate the template class name
|
||||
* @param int|null $index The index if it is an embedded template
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getTemplateClass(string $name, int $index = null): string
|
||||
{
|
||||
$key = $this->getLoader()->getCacheKey($name).$this->optionsHash;
|
||||
|
||||
return $this->templateClassPrefix.hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $key).(null === $index ? '' : '___'.$index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a template.
|
||||
*
|
||||
* @param string|TemplateWrapper $name The template name
|
||||
*
|
||||
* @throws LoaderError When the template cannot be found
|
||||
* @throws SyntaxError When an error occurred during compilation
|
||||
* @throws RuntimeError When an error occurred during rendering
|
||||
*/
|
||||
public function render($name, array $context = []): string
|
||||
{
|
||||
return $this->load($name)->render($context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a template.
|
||||
*
|
||||
* @param string|TemplateWrapper $name The template name
|
||||
*
|
||||
* @throws LoaderError When the template cannot be found
|
||||
* @throws SyntaxError When an error occurred during compilation
|
||||
* @throws RuntimeError When an error occurred during rendering
|
||||
*/
|
||||
public function display($name, array $context = []): void
|
||||
{
|
||||
$this->load($name)->display($context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a template.
|
||||
*
|
||||
* @param string|TemplateWrapper $name The template name
|
||||
*
|
||||
* @throws LoaderError When the template cannot be found
|
||||
* @throws RuntimeError When a previously generated cache is corrupted
|
||||
* @throws SyntaxError When an error occurred during compilation
|
||||
*/
|
||||
public function load($name): TemplateWrapper
|
||||
{
|
||||
if ($name instanceof TemplateWrapper) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a template internal representation.
|
||||
*
|
||||
* This method is for internal use only and should never be called
|
||||
* directly.
|
||||
*
|
||||
* @param string $name The template name
|
||||
* @param int $index The index if it is an embedded template
|
||||
*
|
||||
* @throws LoaderError When the template cannot be found
|
||||
* @throws RuntimeError When a previously generated cache is corrupted
|
||||
* @throws SyntaxError When an error occurred during compilation
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
$source = null;
|
||||
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)) {
|
||||
/* Last line of defense if either $this->bcWriteCacheFile was used,
|
||||
* $this->cache is implemented as a no-op or we have a race condition
|
||||
* where the cache was cleared between the above calls to write to and load from
|
||||
* the cache.
|
||||
*/
|
||||
eval('?>'.$content);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a template from source.
|
||||
*
|
||||
* This method should not be used as a generic way to load templates.
|
||||
*
|
||||
* @param string $template The template source
|
||||
* @param string $name An optional name of the template to be used in error messages
|
||||
*
|
||||
* @throws LoaderError When the template cannot be found
|
||||
* @throws SyntaxError When an error occurred during compilation
|
||||
*/
|
||||
public function createTemplate(string $template, string $name = null): TemplateWrapper
|
||||
{
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the template is still fresh.
|
||||
*
|
||||
* Besides checking the loader for freshness information,
|
||||
* this method also checks if the enabled extensions have
|
||||
* not changed.
|
||||
*
|
||||
* @param int $time The last modification time of the cached template
|
||||
*/
|
||||
public function isTemplateFresh(string $name, int $time): bool
|
||||
{
|
||||
return $this->extensionSet->getLastModified() <= $time && $this->getLoader()->isFresh($name, $time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to load a template consecutively from an array.
|
||||
*
|
||||
* Similar to load() but it also accepts instances of \Twig\Template and
|
||||
* \Twig\TemplateWrapper, and an array of templates where each is tried to be loaded.
|
||||
*
|
||||
* @param string|TemplateWrapper|array $names A template or an array of templates to try consecutively
|
||||
*
|
||||
* @throws LoaderError When none of the templates can be found
|
||||
* @throws SyntaxError When an error occurred during compilation
|
||||
*/
|
||||
public function resolveTemplate($names): TemplateWrapper
|
||||
{
|
||||
if (!\is_array($names)) {
|
||||
return $this->load($names);
|
||||
}
|
||||
|
||||
$count = \count($names);
|
||||
foreach ($names as $name) {
|
||||
if ($name instanceof Template) {
|
||||
return $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SyntaxError When the code is syntactically wrong
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a token stream to a node tree.
|
||||
*
|
||||
* @throws SyntaxError When the token stream is syntactically or semantically wrong
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a node and returns the PHP code.
|
||||
*/
|
||||
public function compile(Node $node): string
|
||||
{
|
||||
if (null === $this->compiler) {
|
||||
$this->compiler = new Compiler($this);
|
||||
}
|
||||
|
||||
return $this->compiler->compile($node)->getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a template source code.
|
||||
*
|
||||
* @throws SyntaxError When there was an error during tokenizing, parsing or compiling
|
||||
*/
|
||||
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 = null === $charset ? null : 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TExtension of ExtensionInterface
|
||||
*
|
||||
* @param class-string<TExtension> $class
|
||||
*
|
||||
* @return TExtension
|
||||
*/
|
||||
public function getExtension(string $class): ExtensionInterface
|
||||
{
|
||||
return $this->extensionSet->getExtension($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the runtime implementation of a Twig element (filter/function/tag/test).
|
||||
*
|
||||
* @template TRuntime of object
|
||||
*
|
||||
* @param class-string<TRuntime> $class A runtime class name
|
||||
*
|
||||
* @return TRuntime The runtime implementation
|
||||
*
|
||||
* @throws RuntimeError When the template cannot be found
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeError(sprintf('Unable to load the "%s" runtime.', $class));
|
||||
}
|
||||
|
||||
public function addExtension(ExtensionInterface $extension)
|
||||
{
|
||||
$this->extensionSet->addExtension($extension);
|
||||
$this->updateOptionsHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExtensionInterface[] $extensions An array of extensions
|
||||
*/
|
||||
public function setExtensions(array $extensions)
|
||||
{
|
||||
$this->extensionSet->setExtensions($extensions);
|
||||
$this->updateOptionsHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on)
|
||||
*/
|
||||
public function getExtensions(): array
|
||||
{
|
||||
return $this->extensionSet->getExtensions();
|
||||
}
|
||||
|
||||
public function addTokenParser(TokenParserInterface $parser)
|
||||
{
|
||||
$this->extensionSet->addTokenParser($parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TokenParserInterface[]
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getTokenParsers(): array
|
||||
{
|
||||
return $this->extensionSet->getTokenParsers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NodeVisitorInterface[]
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getNodeVisitors(): array
|
||||
{
|
||||
return $this->extensionSet->getNodeVisitors();
|
||||
}
|
||||
|
||||
public function addFilter(TwigFilter $filter)
|
||||
{
|
||||
$this->extensionSet->addFilter($filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getFilter(string $name): ?TwigFilter
|
||||
{
|
||||
return $this->extensionSet->getFilter($name);
|
||||
}
|
||||
|
||||
public function registerUndefinedFilterCallback(callable $callable): void
|
||||
{
|
||||
$this->extensionSet->registerUndefinedFilterCallback($callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the registered Filters.
|
||||
*
|
||||
* Be warned that this method cannot return filters defined with registerUndefinedFilterCallback.
|
||||
*
|
||||
* @return TwigFilter[]
|
||||
*
|
||||
* @see registerUndefinedFilterCallback
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getFilters(): array
|
||||
{
|
||||
return $this->extensionSet->getFilters();
|
||||
}
|
||||
|
||||
public function addTest(TwigTest $test)
|
||||
{
|
||||
$this->extensionSet->addTest($test);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TwigTest[]
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getTests(): array
|
||||
{
|
||||
return $this->extensionSet->getTests();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getTest(string $name): ?TwigTest
|
||||
{
|
||||
return $this->extensionSet->getTest($name);
|
||||
}
|
||||
|
||||
public function addFunction(TwigFunction $function)
|
||||
{
|
||||
$this->extensionSet->addFunction($function);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getFunction(string $name): ?TwigFunction
|
||||
{
|
||||
return $this->extensionSet->getFunction($name);
|
||||
}
|
||||
|
||||
public function registerUndefinedFunctionCallback(callable $callable): void
|
||||
{
|
||||
$this->extensionSet->registerUndefinedFunctionCallback($callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets registered functions.
|
||||
*
|
||||
* Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
|
||||
*
|
||||
* @return TwigFunction[]
|
||||
*
|
||||
* @see registerUndefinedFunctionCallback
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return $this->extensionSet->getFunctions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a Global.
|
||||
*
|
||||
* New globals can be added before compiling or rendering a template;
|
||||
* but after, you can only update existing globals.
|
||||
*
|
||||
* @param mixed $value The global value
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return array<string, array{precedence: int, class: class-string<AbstractUnary>}>
|
||||
*/
|
||||
public function getUnaryOperators(): array
|
||||
{
|
||||
return $this->extensionSet->getUnaryOperators();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}>
|
||||
*/
|
||||
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,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,227 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Error;
|
||||
|
||||
use Twig\Source;
|
||||
use Twig\Template;
|
||||
|
||||
/**
|
||||
* Twig base exception.
|
||||
*
|
||||
* This exception class and its children must only be used when
|
||||
* an error occurs during the loading of a template, when a syntax error
|
||||
* is detected in a template, or when rendering a template. Other
|
||||
* errors must use regular PHP exception classes (like when the template
|
||||
* cache directory is not writable for instance).
|
||||
*
|
||||
* To help debugging template issues, this class tracks the original template
|
||||
* name and line where the error occurred.
|
||||
*
|
||||
* Whenever possible, you must set these information (original template name
|
||||
* and line number) yourself by passing them to the constructor. If some or all
|
||||
* these information are not available from where you throw the exception, then
|
||||
* this class will guess them automatically (when the line number is set to -1
|
||||
* and/or the name is set to null). As this is a costly operation, this
|
||||
* can be disabled by passing false for both the name and the line number
|
||||
* when creating a new instance of this class.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Error extends \Exception
|
||||
{
|
||||
private $lineno;
|
||||
private $name;
|
||||
private $rawMessage;
|
||||
private $sourcePath;
|
||||
private $sourceCode;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* By default, automatic guessing is enabled.
|
||||
*
|
||||
* @param string $message The error message
|
||||
* @param int $lineno The template line where the error occurred
|
||||
* @param Source|null $source The source context where the error occurred
|
||||
*/
|
||||
public function __construct(string $message, int $lineno = -1, Source $source = null, \Exception $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 ('.' === substr($this->message, -1)) {
|
||||
$this->message = substr($this->message, 0, -1);
|
||||
$dot = true;
|
||||
}
|
||||
|
||||
$questionMark = false;
|
||||
if ('?' === substr($this->message, -1)) {
|
||||
$this->message = substr($this->message, 0, -1);
|
||||
$questionMark = true;
|
||||
}
|
||||
|
||||
if ($this->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 : 0 === strpos($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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Error;
|
||||
|
||||
/**
|
||||
* Exception thrown when an error occurs during template loading.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class LoaderError extends Error
|
||||
{
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Error;
|
||||
|
||||
/**
|
||||
* Exception thrown when an error occurs at runtime.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class RuntimeError extends Error
|
||||
{
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Error;
|
||||
|
||||
/**
|
||||
* \Exception thrown when a syntax error occurs during lexing or parsing of a template.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class SyntaxError extends Error
|
||||
{
|
||||
/**
|
||||
* Tweaks the error message to include suggestions.
|
||||
*
|
||||
* @param string $name The original name of the item that does not exist
|
||||
* @param array $items An array of possible items
|
||||
*/
|
||||
public function addSuggestions(string $name, array $items): void
|
||||
{
|
||||
$alternatives = [];
|
||||
foreach ($items as $item) {
|
||||
$lev = levenshtein($name, $item);
|
||||
if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) {
|
||||
$alternatives[$item] = $lev;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$alternatives) {
|
||||
return;
|
||||
}
|
||||
|
||||
asort($alternatives);
|
||||
|
||||
$this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', array_keys($alternatives))));
|
||||
}
|
||||
}
|
|
@ -1,827 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig;
|
||||
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\ArrowFunctionExpression;
|
||||
use Twig\Node\Expression\AssignNameExpression;
|
||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||
use Twig\Node\Expression\Binary\ConcatBinary;
|
||||
use Twig\Node\Expression\BlockReferenceExpression;
|
||||
use Twig\Node\Expression\ConditionalExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\GetAttrExpression;
|
||||
use Twig\Node\Expression\MethodCallExpression;
|
||||
use Twig\Node\Expression\NameExpression;
|
||||
use Twig\Node\Expression\ParentExpression;
|
||||
use Twig\Node\Expression\TestExpression;
|
||||
use Twig\Node\Expression\Unary\AbstractUnary;
|
||||
use Twig\Node\Expression\Unary\NegUnary;
|
||||
use Twig\Node\Expression\Unary\NotUnary;
|
||||
use Twig\Node\Expression\Unary\PosUnary;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* Parses expressions.
|
||||
*
|
||||
* This parser implements a "Precedence climbing" algorithm.
|
||||
*
|
||||
* @see https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
|
||||
* @see https://en.wikipedia.org/wiki/Operator-precedence_parser
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ExpressionParser
|
||||
{
|
||||
public const OPERATOR_LEFT = 1;
|
||||
public const OPERATOR_RIGHT = 2;
|
||||
|
||||
private $parser;
|
||||
private $env;
|
||||
/** @var array<string, array{precedence: int, class: class-string<AbstractUnary>}> */
|
||||
private $unaryOperators;
|
||||
/** @var array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: self::OPERATOR_*}> */
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrowFunctionExpression|null
|
||||
*/
|
||||
private function parseArrow()
|
||||
{
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
// short array syntax (one argument, no parentheses)?
|
||||
if ($stream->look(1)->test(/* Token::ARROW_TYPE */ 12)) {
|
||||
$line = $stream->getCurrent()->getLine();
|
||||
$token = $stream->expect(/* Token::NAME_TYPE */ 5);
|
||||
$names = [new AssignNameExpression($token->getValue(), $token->getLine())];
|
||||
$stream->expect(/* Token::ARROW_TYPE */ 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(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
|
||||
return null;
|
||||
}
|
||||
++$i;
|
||||
while (true) {
|
||||
// variable name
|
||||
++$i;
|
||||
if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
|
||||
break;
|
||||
}
|
||||
++$i;
|
||||
}
|
||||
if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
|
||||
return null;
|
||||
}
|
||||
++$i;
|
||||
if (!$stream->look($i)->test(/* Token::ARROW_TYPE */ 12)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// yes, let's parse it properly
|
||||
$token = $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(');
|
||||
$line = $token->getLine();
|
||||
|
||||
$names = [];
|
||||
while (true) {
|
||||
$token = $stream->expect(/* Token::NAME_TYPE */ 5);
|
||||
$names[] = new AssignNameExpression($token->getValue(), $token->getLine());
|
||||
|
||||
if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')');
|
||||
$stream->expect(/* Token::ARROW_TYPE */ 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(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
|
||||
$this->parser->getStream()->next();
|
||||
$expr = $this->parseExpression();
|
||||
$this->parser->getStream()->expect(/* Token::PUNCTUATION_TYPE */ 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(/* Token::PUNCTUATION_TYPE */ 9, '?')) {
|
||||
if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
|
||||
$expr2 = $this->parseExpression();
|
||||
if ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
|
||||
$expr3 = $this->parseExpression();
|
||||
} else {
|
||||
$expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine());
|
||||
}
|
||||
} else {
|
||||
$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(/* Token::OPERATOR_TYPE */ 8) && isset($this->unaryOperators[$token->getValue()]);
|
||||
}
|
||||
|
||||
private function isBinary(Token $token): bool
|
||||
{
|
||||
return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->binaryOperators[$token->getValue()]);
|
||||
}
|
||||
|
||||
public function parsePrimaryExpression()
|
||||
{
|
||||
$token = $this->parser->getCurrentToken();
|
||||
switch ($token->getType()) {
|
||||
case /* Token::NAME_TYPE */ 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 /* Token::NUMBER_TYPE */ 6:
|
||||
$this->parser->getStream()->next();
|
||||
$node = new ConstantExpression($token->getValue(), $token->getLine());
|
||||
break;
|
||||
|
||||
case /* Token::STRING_TYPE */ 7:
|
||||
case /* Token::INTERPOLATION_START_TYPE */ 10:
|
||||
$node = $this->parseStringExpression();
|
||||
break;
|
||||
|
||||
case /* Token::OPERATOR_TYPE */ 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(/* Token::PUNCTUATION_TYPE */ 9, '[')) {
|
||||
$node = $this->parseArrayExpression();
|
||||
} elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '{')) {
|
||||
$node = $this->parseHashExpression();
|
||||
} elseif ($token->test(/* Token::OPERATOR_TYPE */ 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(/* Token::STRING_TYPE */ 7)) {
|
||||
$nodes[] = new ConstantExpression($token->getValue(), $token->getLine());
|
||||
$nextCanBeString = false;
|
||||
} elseif ($stream->nextIf(/* Token::INTERPOLATION_START_TYPE */ 10)) {
|
||||
$nodes[] = $this->parseExpression();
|
||||
$stream->expect(/* Token::INTERPOLATION_END_TYPE */ 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()
|
||||
{
|
||||
$stream = $this->parser->getStream();
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '[', 'An array element was expected');
|
||||
|
||||
$node = new ArrayExpression([], $stream->getCurrent()->getLine());
|
||||
$first = true;
|
||||
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
|
||||
if (!$first) {
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'An array element must be followed by a comma');
|
||||
|
||||
// trailing ,?
|
||||
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$first = false;
|
||||
|
||||
$node->addElement($this->parseExpression());
|
||||
}
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened array is not properly closed');
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
public function parseHashExpression()
|
||||
{
|
||||
$stream = $this->parser->getStream();
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '{', 'A hash element was expected');
|
||||
|
||||
$node = new ArrayExpression([], $stream->getCurrent()->getLine());
|
||||
$first = true;
|
||||
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) {
|
||||
if (!$first) {
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A hash value must be followed by a comma');
|
||||
|
||||
// trailing ,?
|
||||
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$first = false;
|
||||
|
||||
// a hash 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(/* Token::NAME_TYPE */ 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(/* Token::STRING_TYPE */ 7)) || $token = $stream->nextIf(/* Token::NUMBER_TYPE */ 6)) {
|
||||
$key = new ConstantExpression($token->getValue(), $token->getLine());
|
||||
} elseif ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
|
||||
$key = $this->parseExpression();
|
||||
} else {
|
||||
$current = $stream->getCurrent();
|
||||
|
||||
throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext());
|
||||
}
|
||||
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A hash key must be followed by a colon (:)');
|
||||
$value = $this->parseExpression();
|
||||
|
||||
$node->addElement($value, $key);
|
||||
}
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened hash is not properly closed');
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
public function parsePostfixExpression($node)
|
||||
{
|
||||
while (true) {
|
||||
$token = $this->parser->getCurrentToken();
|
||||
if (/* Token::PUNCTUATION_TYPE */ 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 (
|
||||
/* Token::NAME_TYPE */ 5 == $token->getType()
|
||||
||
|
||||
/* Token::NUMBER_TYPE */ 6 == $token->getType()
|
||||
||
|
||||
(/* Token::OPERATOR_TYPE */ 8 == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue()))
|
||||
) {
|
||||
$arg = new ConstantExpression($token->getValue(), $lineno);
|
||||
|
||||
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 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'))) {
|
||||
if (!$arg instanceof ConstantExpression) {
|
||||
throw new SyntaxError(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext());
|
||||
}
|
||||
|
||||
$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(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
|
||||
$slice = true;
|
||||
$arg = new ConstantExpression(0, $token->getLine());
|
||||
} else {
|
||||
$arg = $this->parseExpression();
|
||||
}
|
||||
|
||||
if ($stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
|
||||
$slice = true;
|
||||
}
|
||||
|
||||
if ($slice) {
|
||||
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 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(/* Token::PUNCTUATION_TYPE */ 9, ']');
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 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(/* Token::NAME_TYPE */ 5);
|
||||
|
||||
$name = new ConstantExpression($token->getValue(), $token->getLine());
|
||||
if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 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(/* Token::PUNCTUATION_TYPE */ 9, '|')) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->parser->getStream()->next();
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses arguments.
|
||||
*
|
||||
* @param bool $namedArguments Whether to allow named arguments or not
|
||||
* @param bool $definition Whether we are parsing arguments for a function definition
|
||||
*
|
||||
* @return Node
|
||||
*
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
public function parseArguments($namedArguments = false, $definition = false, $allowArrow = false)
|
||||
{
|
||||
$args = [];
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(', 'A list of arguments must begin with an opening parenthesis');
|
||||
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
|
||||
if (!empty($args)) {
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 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(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($definition) {
|
||||
$token = $stream->expect(/* Token::NAME_TYPE */ 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(/* Token::OPERATOR_TYPE */ 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, or an array).', $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(/* Token::PUNCTUATION_TYPE */ 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(/* Token::OPERATOR_TYPE */ 8) && preg_match(Lexer::REGEX_NAME, $token->getValue())) {
|
||||
// in this context, string operators are variable names
|
||||
$this->parser->getStream()->next();
|
||||
} else {
|
||||
$stream->expect(/* Token::NAME_TYPE */ 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(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new Node($targets);
|
||||
}
|
||||
|
||||
public function parseMultitargetExpression()
|
||||
{
|
||||
$targets = [];
|
||||
while (true) {
|
||||
$targets[] = $this->parseExpression();
|
||||
if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 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();
|
||||
list($name, $test) = $this->getTest($node->getTemplateLine());
|
||||
|
||||
$class = $this->getTestNodeClass($test);
|
||||
$arguments = null;
|
||||
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 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(/* Token::NAME_TYPE */ 5)->getValue();
|
||||
|
||||
if ($test = $this->env->getTest($name)) {
|
||||
return [$name, $test];
|
||||
}
|
||||
|
||||
if ($stream->test(/* Token::NAME_TYPE */ 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->getDeprecatedVersion()) {
|
||||
$message .= sprintf(' since version %s', $test->getDeprecatedVersion());
|
||||
}
|
||||
if ($test->getAlternative()) {
|
||||
$message .= sprintf('. Use "%s" instead', $test->getAlternative());
|
||||
}
|
||||
$src = $stream->getSourceContext();
|
||||
$message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
|
||||
|
||||
@trigger_error($message, \E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
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->getDeprecatedVersion()) {
|
||||
$message .= sprintf(' since version %s', $function->getDeprecatedVersion());
|
||||
}
|
||||
if ($function->getAlternative()) {
|
||||
$message .= sprintf('. Use "%s" instead', $function->getAlternative());
|
||||
}
|
||||
$src = $this->parser->getStream()->getSourceContext();
|
||||
$message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
|
||||
|
||||
@trigger_error($message, \E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
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->getDeprecatedVersion()) {
|
||||
$message .= sprintf(' since version %s', $filter->getDeprecatedVersion());
|
||||
}
|
||||
if ($filter->getAlternative()) {
|
||||
$message .= sprintf('. Use "%s" instead', $filter->getAlternative());
|
||||
}
|
||||
$src = $this->parser->getStream()->getSourceContext();
|
||||
$message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
|
||||
|
||||
@trigger_error($message, \E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension;
|
||||
|
||||
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 [];
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,64 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension {
|
||||
use 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 means that it was not set (and the default is on) or it explicitly enabled
|
||||
&& (false === ini_get('xdebug.overload_var_dump') || ini_get('xdebug.overload_var_dump'))
|
||||
// false means that it was not set (and the default is on) or it explicitly enabled
|
||||
// xdebug.overload_var_dump produces HTML only when html_errors is also enabled
|
||||
&& (false === ini_get('html_errors') || ini_get('html_errors'))
|
||||
|| 'cli' === \PHP_SAPI
|
||||
;
|
||||
|
||||
return [
|
||||
new TwigFunction('dump', 'twig_var_dump', ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
use Twig\Environment;
|
||||
use Twig\Template;
|
||||
use Twig\TemplateWrapper;
|
||||
|
||||
function twig_var_dump(Environment $env, $context, ...$vars)
|
||||
{
|
||||
if (!$env->isDebug()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
if (!$vars) {
|
||||
$vars = [];
|
||||
foreach ($context as $key => $value) {
|
||||
if (!$value instanceof Template && !$value instanceof TemplateWrapper) {
|
||||
$vars[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
var_dump($vars);
|
||||
} else {
|
||||
var_dump(...$vars);
|
||||
}
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
|
@ -1,416 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension {
|
||||
use Twig\FileExtensionEscapingStrategy;
|
||||
use Twig\NodeVisitor\EscaperNodeVisitor;
|
||||
use Twig\TokenParser\AutoEscapeTokenParser;
|
||||
use Twig\TwigFilter;
|
||||
|
||||
final class EscaperExtension extends AbstractExtension
|
||||
{
|
||||
private $defaultStrategy;
|
||||
private $escapers = [];
|
||||
|
||||
/** @internal */
|
||||
public $safeClasses = [];
|
||||
|
||||
/** @internal */
|
||||
public $safeLookup = [];
|
||||
|
||||
/**
|
||||
* @param string|false|callable $defaultStrategy An escaping strategy
|
||||
*
|
||||
* @see setDefaultStrategy()
|
||||
*/
|
||||
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', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
|
||||
new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
|
||||
new TwigFilter('raw', 'twig_raw_filter', ['is_safe' => ['all']]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default strategy to use when not defined by the user.
|
||||
*
|
||||
* The strategy can be a valid PHP callback that takes the template
|
||||
* name as an argument and returns the strategy to use.
|
||||
*
|
||||
* @param string|false|callable $defaultStrategy An escaping strategy
|
||||
*/
|
||||
public function setDefaultStrategy($defaultStrategy): void
|
||||
{
|
||||
if ('name' === $defaultStrategy) {
|
||||
$defaultStrategy = [FileExtensionEscapingStrategy::class, 'guess'];
|
||||
}
|
||||
|
||||
$this->defaultStrategy = $defaultStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default strategy to use when not defined by the user.
|
||||
*
|
||||
* @param string $name The template name
|
||||
*
|
||||
* @return string|false The default strategy to use for the template
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a new escaper to be used via the escape filter.
|
||||
*
|
||||
* @param string $strategy The strategy name that should be used as a strategy in the escape call
|
||||
* @param callable $callable A valid PHP callable
|
||||
*/
|
||||
public function setEscaper($strategy, callable $callable)
|
||||
{
|
||||
$this->escapers[$strategy] = $callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all defined escapers.
|
||||
*
|
||||
* @return callable[] An array of escapers
|
||||
*/
|
||||
public function getEscapers()
|
||||
{
|
||||
return $this->escapers;
|
||||
}
|
||||
|
||||
public function setSafeClasses(array $safeClasses = [])
|
||||
{
|
||||
$this->safeClasses = [];
|
||||
$this->safeLookup = [];
|
||||
foreach ($safeClasses as $class => $strategies) {
|
||||
$this->addSafeClass($class, $strategies);
|
||||
}
|
||||
}
|
||||
|
||||
public function addSafeClass(string $class, array $strategies)
|
||||
{
|
||||
$class = ltrim($class, '\\');
|
||||
if (!isset($this->safeClasses[$class])) {
|
||||
$this->safeClasses[$class] = [];
|
||||
}
|
||||
$this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies);
|
||||
|
||||
foreach ($strategies as $strategy) {
|
||||
$this->safeLookup[$strategy][$class] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
use Twig\Environment;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Extension\EscaperExtension;
|
||||
use Twig\Markup;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* Marks a variable as being safe.
|
||||
*
|
||||
* @param string $string A PHP variable
|
||||
*/
|
||||
function twig_raw_filter($string)
|
||||
{
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string.
|
||||
*
|
||||
* @param mixed $string The value to be escaped
|
||||
* @param string $strategy The escaping strategy
|
||||
* @param string $charset The charset
|
||||
* @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
|
||||
{
|
||||
if ($autoescape && $string instanceof Markup) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
if (!\is_string($string)) {
|
||||
if (\is_object($string) && method_exists($string, '__toString')) {
|
||||
if ($autoescape) {
|
||||
$c = \get_class($string);
|
||||
$ext = $env->getExtension(EscaperExtension::class);
|
||||
if (!isset($ext->safeClasses[$c])) {
|
||||
$ext->safeClasses[$c] = [];
|
||||
foreach (class_parents($string) + class_implements($string) as $class) {
|
||||
if (isset($ext->safeClasses[$class])) {
|
||||
$ext->safeClasses[$c] = array_unique(array_merge($ext->safeClasses[$c], $ext->safeClasses[$class]));
|
||||
foreach ($ext->safeClasses[$class] as $s) {
|
||||
$ext->safeLookup[$s][$c] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($ext->safeLookup[$strategy][$c]) || isset($ext->safeLookup['all'][$c])) {
|
||||
return (string) $string;
|
||||
}
|
||||
}
|
||||
|
||||
$string = (string) $string;
|
||||
} elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) {
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
if ('' === $string) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (null === $charset) {
|
||||
$charset = $env->getCharset();
|
||||
}
|
||||
|
||||
switch ($strategy) {
|
||||
case 'html':
|
||||
// see https://www.php.net/htmlspecialchars
|
||||
|
||||
// Using a static variable to avoid initializing the array
|
||||
// each time the function is called. Moving the declaration on the
|
||||
// top of the function slow downs other escaping strategies.
|
||||
static $htmlspecialcharsCharsets = [
|
||||
'ISO-8859-1' => true, 'ISO8859-1' => true,
|
||||
'ISO-8859-15' => true, 'ISO8859-15' => true,
|
||||
'utf-8' => true, 'UTF-8' => true,
|
||||
'CP866' => true, 'IBM866' => true, '866' => true,
|
||||
'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true,
|
||||
'1251' => true,
|
||||
'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true,
|
||||
'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true,
|
||||
'BIG5' => true, '950' => true,
|
||||
'GB2312' => true, '936' => true,
|
||||
'BIG5-HKSCS' => true,
|
||||
'SHIFT_JIS' => true, 'SJIS' => true, '932' => true,
|
||||
'EUC-JP' => true, 'EUCJP' => true,
|
||||
'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true,
|
||||
];
|
||||
|
||||
if (isset($htmlspecialcharsCharsets[$charset])) {
|
||||
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
|
||||
}
|
||||
|
||||
if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) {
|
||||
// cache the lowercase variant for future iterations
|
||||
$htmlspecialcharsCharsets[$charset] = true;
|
||||
|
||||
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
|
||||
}
|
||||
|
||||
$string = twig_convert_encoding($string, 'UTF-8', $charset);
|
||||
$string = htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8');
|
||||
|
||||
return iconv('UTF-8', $charset, $string);
|
||||
|
||||
case 'js':
|
||||
// escape all non-alphanumeric characters
|
||||
// into their \x or \uHHHH representations
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = twig_convert_encoding($string, 'UTF-8', $charset);
|
||||
}
|
||||
|
||||
if (!preg_match('//u', $string)) {
|
||||
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
|
||||
}
|
||||
|
||||
$string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) {
|
||||
$char = $matches[0];
|
||||
|
||||
/*
|
||||
* A few characters have short escape sequences in JSON and JavaScript.
|
||||
* Escape sequences supported only by JavaScript, not JSON, are omitted.
|
||||
* \" is also supported but omitted, because the resulting string is not HTML safe.
|
||||
*/
|
||||
static $shortMap = [
|
||||
'\\' => '\\\\',
|
||||
'/' => '\\/',
|
||||
"\x08" => '\b',
|
||||
"\x0C" => '\f',
|
||||
"\x0A" => '\n',
|
||||
"\x0D" => '\r',
|
||||
"\x09" => '\t',
|
||||
];
|
||||
|
||||
if (isset($shortMap[$char])) {
|
||||
return $shortMap[$char];
|
||||
}
|
||||
|
||||
$codepoint = mb_ord($char, 'UTF-8');
|
||||
if (0x10000 > $codepoint) {
|
||||
return sprintf('\u%04X', $codepoint);
|
||||
}
|
||||
|
||||
// Split characters outside the BMP into surrogate pairs
|
||||
// https://tools.ietf.org/html/rfc2781.html#section-2.1
|
||||
$u = $codepoint - 0x10000;
|
||||
$high = 0xD800 | ($u >> 10);
|
||||
$low = 0xDC00 | ($u & 0x3FF);
|
||||
|
||||
return sprintf('\u%04X\u%04X', $high, $low);
|
||||
}, $string);
|
||||
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = iconv('UTF-8', $charset, $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
|
||||
case 'css':
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = twig_convert_encoding($string, 'UTF-8', $charset);
|
||||
}
|
||||
|
||||
if (!preg_match('//u', $string)) {
|
||||
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
|
||||
}
|
||||
|
||||
$string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) {
|
||||
$char = $matches[0];
|
||||
|
||||
return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8'));
|
||||
}, $string);
|
||||
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = iconv('UTF-8', $charset, $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
|
||||
case 'html_attr':
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = twig_convert_encoding($string, 'UTF-8', $charset);
|
||||
}
|
||||
|
||||
if (!preg_match('//u', $string)) {
|
||||
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
|
||||
}
|
||||
|
||||
$string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) {
|
||||
/**
|
||||
* This function is adapted from code coming from Zend Framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com)
|
||||
* @license https://framework.zend.com/license/new-bsd New BSD License
|
||||
*/
|
||||
$chr = $matches[0];
|
||||
$ord = \ord($chr);
|
||||
|
||||
/*
|
||||
* The following replaces characters undefined in HTML with the
|
||||
* hex entity for the Unicode replacement character.
|
||||
*/
|
||||
if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) {
|
||||
return '�';
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the current character to escape has a name entity we should
|
||||
* replace it with while grabbing the hex value of the character.
|
||||
*/
|
||||
if (1 === \strlen($chr)) {
|
||||
/*
|
||||
* While HTML supports far more named entities, the lowest common denominator
|
||||
* has become HTML5's XML Serialisation which is restricted to the those named
|
||||
* entities that XML supports. Using HTML entities would result in this error:
|
||||
* XML Parsing Error: undefined entity
|
||||
*/
|
||||
static $entityMap = [
|
||||
34 => '"', /* quotation mark */
|
||||
38 => '&', /* ampersand */
|
||||
60 => '<', /* less-than sign */
|
||||
62 => '>', /* greater-than sign */
|
||||
];
|
||||
|
||||
if (isset($entityMap[$ord])) {
|
||||
return $entityMap[$ord];
|
||||
}
|
||||
|
||||
return sprintf('&#x%02X;', $ord);
|
||||
}
|
||||
|
||||
/*
|
||||
* Per OWASP recommendations, we'll use hex entities for any other
|
||||
* characters where a named entity does not exist.
|
||||
*/
|
||||
return sprintf('&#x%04X;', mb_ord($chr, 'UTF-8'));
|
||||
}, $string);
|
||||
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = iconv('UTF-8', $charset, $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
|
||||
case 'url':
|
||||
return rawurlencode($string);
|
||||
|
||||
default:
|
||||
$escapers = $env->getExtension(EscaperExtension::class)->getEscapers();
|
||||
if (array_key_exists($strategy, $escapers)) {
|
||||
return $escapers[$strategy]($env, $string, $charset);
|
||||
}
|
||||
|
||||
$validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers)));
|
||||
|
||||
throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function twig_escape_filter_is_safe(Node $filterArgs)
|
||||
{
|
||||
foreach ($filterArgs as $arg) {
|
||||
if ($arg instanceof ConstantExpression) {
|
||||
return [$arg->getAttribute('value')];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return ['html'];
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension;
|
||||
|
||||
use Twig\ExpressionParser;
|
||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||
use Twig\Node\Expression\Unary\AbstractUnary;
|
||||
use Twig\NodeVisitor\NodeVisitorInterface;
|
||||
use Twig\TokenParser\TokenParserInterface;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
use Twig\TwigTest;
|
||||
|
||||
/**
|
||||
* Interface implemented by extension classes.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface ExtensionInterface
|
||||
{
|
||||
/**
|
||||
* Returns the token parser instances to add to the existing list.
|
||||
*
|
||||
* @return TokenParserInterface[]
|
||||
*/
|
||||
public function getTokenParsers();
|
||||
|
||||
/**
|
||||
* Returns the node visitor instances to add to the existing list.
|
||||
*
|
||||
* @return NodeVisitorInterface[]
|
||||
*/
|
||||
public function getNodeVisitors();
|
||||
|
||||
/**
|
||||
* Returns a list of filters to add to the existing list.
|
||||
*
|
||||
* @return TwigFilter[]
|
||||
*/
|
||||
public function getFilters();
|
||||
|
||||
/**
|
||||
* Returns a list of tests to add to the existing list.
|
||||
*
|
||||
* @return TwigTest[]
|
||||
*/
|
||||
public function getTests();
|
||||
|
||||
/**
|
||||
* Returns a list of functions to add to the existing list.
|
||||
*
|
||||
* @return TwigFunction[]
|
||||
*/
|
||||
public function getFunctions();
|
||||
|
||||
/**
|
||||
* Returns a list of operators to add to the existing list.
|
||||
*
|
||||
* @return array<array> First array of unary operators, second array of binary operators
|
||||
*
|
||||
* @psalm-return array{
|
||||
* array<string, array{precedence: int, class: class-string<AbstractUnary>}>,
|
||||
* array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}>
|
||||
* }
|
||||
*/
|
||||
public function getOperators();
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension;
|
||||
|
||||
/**
|
||||
* Enables usage of the deprecated Twig\Extension\AbstractExtension::getGlobals() method.
|
||||
*
|
||||
* Explicitly implement this interface if you really need to implement the
|
||||
* deprecated getGlobals() method in your extensions.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface GlobalsInterface
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getGlobals(): array;
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension;
|
||||
|
||||
use 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)];
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension;
|
||||
|
||||
use Twig\Profiler\NodeVisitor\ProfilerNodeVisitor;
|
||||
use Twig\Profiler\Profile;
|
||||
|
||||
class ProfilerExtension extends AbstractExtension
|
||||
{
|
||||
private $actives = [];
|
||||
|
||||
public function __construct(Profile $profile)
|
||||
{
|
||||
$this->actives[] = $profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function enter(Profile $profile)
|
||||
{
|
||||
$this->actives[0]->addProfile($profile);
|
||||
array_unshift($this->actives, $profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
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)];
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension;
|
||||
|
||||
/**
|
||||
* @author Grégoire Pineau <lyrixx@lyrixx.info>
|
||||
*/
|
||||
interface RuntimeExtensionInterface
|
||||
{
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension;
|
||||
|
||||
use Twig\NodeVisitor\SandboxNodeVisitor;
|
||||
use Twig\Sandbox\SecurityNotAllowedMethodError;
|
||||
use Twig\Sandbox\SecurityNotAllowedPropertyError;
|
||||
use Twig\Sandbox\SecurityPolicyInterface;
|
||||
use Twig\Source;
|
||||
use Twig\TokenParser\SandboxTokenParser;
|
||||
|
||||
final class SandboxExtension extends AbstractExtension
|
||||
{
|
||||
private $sandboxedGlobally;
|
||||
private $sandboxed;
|
||||
private $policy;
|
||||
|
||||
public function __construct(SecurityPolicyInterface $policy, $sandboxed = false)
|
||||
{
|
||||
$this->policy = $policy;
|
||||
$this->sandboxedGlobally = $sandboxed;
|
||||
}
|
||||
|
||||
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(): bool
|
||||
{
|
||||
return $this->sandboxedGlobally || $this->sandboxed;
|
||||
}
|
||||
|
||||
public function isSandboxedGlobally(): bool
|
||||
{
|
||||
return $this->sandboxedGlobally;
|
||||
}
|
||||
|
||||
public function setSecurityPolicy(SecurityPolicyInterface $policy)
|
||||
{
|
||||
$this->policy = $policy;
|
||||
}
|
||||
|
||||
public function getSecurityPolicy(): SecurityPolicyInterface
|
||||
{
|
||||
return $this->policy;
|
||||
}
|
||||
|
||||
public function checkSecurity($tags, $filters, $functions): void
|
||||
{
|
||||
if ($this->isSandboxed()) {
|
||||
$this->policy->checkSecurity($tags, $filters, $functions);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkMethodAllowed($obj, $method, int $lineno = -1, Source $source = null): void
|
||||
{
|
||||
if ($this->isSandboxed()) {
|
||||
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()) {
|
||||
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 ($this->isSandboxed() && \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;
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension;
|
||||
|
||||
use Twig\NodeVisitor\NodeVisitorInterface;
|
||||
use Twig\TokenParser\TokenParserInterface;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
use Twig\TwigTest;
|
||||
|
||||
/**
|
||||
* Used by \Twig\Environment as a staging area.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Extension {
|
||||
use Twig\TwigFunction;
|
||||
|
||||
final class StringLoaderExtension extends AbstractExtension
|
||||
{
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('template_from_string', 'twig_template_from_string', ['needs_environment' => true]),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
use Twig\Environment;
|
||||
use Twig\TemplateWrapper;
|
||||
|
||||
/**
|
||||
* Loads a template from a string.
|
||||
*
|
||||
* {{ include(template_from_string("Hello {{ name }}")) }}
|
||||
*
|
||||
* @param string $template A template as a string or object implementing __toString()
|
||||
* @param string $name An optional name of the template to be used in error messages
|
||||
*/
|
||||
function twig_template_from_string(Environment $env, $template, string $name = null): TemplateWrapper
|
||||
{
|
||||
return $env->createTemplate((string) $template, $name);
|
||||
}
|
||||
}
|
|
@ -1,480 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig;
|
||||
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Extension\ExtensionInterface;
|
||||
use Twig\Extension\GlobalsInterface;
|
||||
use Twig\Extension\StagingExtension;
|
||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||
use Twig\Node\Expression\Unary\AbstractUnary;
|
||||
use Twig\NodeVisitor\NodeVisitorInterface;
|
||||
use Twig\TokenParser\TokenParserInterface;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ExtensionSet
|
||||
{
|
||||
private $extensions;
|
||||
private $initialized = false;
|
||||
private $runtimeInitialized = false;
|
||||
private $staging;
|
||||
private $parsers;
|
||||
private $visitors;
|
||||
/** @var array<string, TwigFilter> */
|
||||
private $filters;
|
||||
/** @var array<string, TwigTest> */
|
||||
private $tests;
|
||||
/** @var array<string, TwigFunction> */
|
||||
private $functions;
|
||||
/** @var array<string, array{precedence: int, class: class-string<AbstractUnary>}> */
|
||||
private $unaryOperators;
|
||||
/** @var array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}> */
|
||||
private $binaryOperators;
|
||||
/** @var array<string, mixed> */
|
||||
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];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExtensionInterface[] $extensions
|
||||
*/
|
||||
public function setExtensions(array $extensions): void
|
||||
{
|
||||
foreach ($extensions as $extension) {
|
||||
$this->addExtension($extension);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ExtensionInterface[]
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TwigFunction[]
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TwigFilter[]
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NodeVisitorInterface[]
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TokenParserInterface[]
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TwigTest[]
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{precedence: int, class: class-string<AbstractUnary>}>
|
||||
*/
|
||||
public function getUnaryOperators(): array
|
||||
{
|
||||
if (!$this->initialized) {
|
||||
$this->initExtensions();
|
||||
}
|
||||
|
||||
return $this->unaryOperators;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}>
|
||||
*/
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig;
|
||||
|
||||
/**
|
||||
* Default autoescaping strategy based on file names.
|
||||
*
|
||||
* This strategy sets the HTML as the default autoescaping strategy,
|
||||
* but changes it based on the template name.
|
||||
*
|
||||
* Note that there is no runtime performance impact as the
|
||||
* default autoescaping strategy is set at compilation time.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class FileExtensionEscapingStrategy
|
||||
{
|
||||
/**
|
||||
* Guesses the best autoescaping strategy based on the file name.
|
||||
*
|
||||
* @param string $name The template name
|
||||
*
|
||||
* @return string|false The escaping strategy name to use or false to disable
|
||||
*/
|
||||
public static function guess(string $name)
|
||||
{
|
||||
if (\in_array(substr($name, -1), ['/', '\\'])) {
|
||||
return 'html'; // return html for directories
|
||||
}
|
||||
|
||||
if ('.twig' === substr($name, -5)) {
|
||||
$name = substr($name, 0, -5);
|
||||
}
|
||||
|
||||
$extension = pathinfo($name, \PATHINFO_EXTENSION);
|
||||
|
||||
switch ($extension) {
|
||||
case 'js':
|
||||
return 'js';
|
||||
|
||||
case 'css':
|
||||
return 'css';
|
||||
|
||||
case 'txt':
|
||||
return false;
|
||||
|
||||
default:
|
||||
return 'html';
|
||||
}
|
||||
}
|
||||
}
|
501
admin/phpMyAdmin/vendor/twig/twig/src/Lexer.php
vendored
501
admin/phpMyAdmin/vendor/twig/twig/src/Lexer.php
vendored
|
@ -1,501 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig;
|
||||
|
||||
use Twig\Error\SyntaxError;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Lexer
|
||||
{
|
||||
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);
|
||||
|
||||
// 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*'. // -}}\s*
|
||||
'|'.
|
||||
preg_quote($this->options['whitespace_line_trim'].$this->options['tag_variable'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~}}[ \t\0\x0B]*
|
||||
'|'.
|
||||
preg_quote($this->options['tag_variable'][1], '#'). // }}
|
||||
')
|
||||
}Ax',
|
||||
|
||||
// %}
|
||||
'lex_block' => '{
|
||||
\s*
|
||||
(?:'.
|
||||
preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*\n?'. // -%}\s*\n?
|
||||
'|'.
|
||||
preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]*
|
||||
'|'.
|
||||
preg_quote($this->options['tag_block'][1], '#').'\n?'. // %}\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'].']*'. // ~%}[ \t\0\x0B]*
|
||||
'|'.
|
||||
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?'. // -#}\s*\n?
|
||||
'|'.
|
||||
preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \t\0\x0B]*
|
||||
'|'.
|
||||
preg_quote($this->options['tag_comment'][1], '#').'\n?'. // #}\n?
|
||||
')
|
||||
}sx',
|
||||
|
||||
// verbatim %}
|
||||
'lex_block_raw' => '{
|
||||
\s*verbatim\s*
|
||||
(?:'.
|
||||
preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%}\s*
|
||||
'|'.
|
||||
preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]*
|
||||
'|'.
|
||||
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',
|
||||
];
|
||||
}
|
||||
|
||||
public function tokenize(Source $source): TokenStream
|
||||
{
|
||||
$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(/* Token::EOF_TYPE */ -1);
|
||||
|
||||
if (!empty($this->brackets)) {
|
||||
list($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(/* Token::TEXT_TYPE */ 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\0\x0B");
|
||||
}
|
||||
}
|
||||
$this->pushToken(/* Token::TEXT_TYPE */ 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(/* Token::BLOCK_START_TYPE */ 1);
|
||||
$this->pushState(self::STATE_BLOCK);
|
||||
$this->currentVarBlockLine = $this->lineno;
|
||||
}
|
||||
break;
|
||||
|
||||
case $this->options['tag_variable'][0]:
|
||||
$this->pushToken(/* Token::VAR_START_TYPE */ 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(/* Token::BLOCK_END_TYPE */ 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(/* Token::VAR_END_TYPE */ 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);
|
||||
}
|
||||
}
|
||||
|
||||
// arrow function
|
||||
if ('=' === $this->code[$this->cursor] && '>' === $this->code[$this->cursor + 1]) {
|
||||
$this->pushToken(Token::ARROW_TYPE, '=>');
|
||||
$this->moveCursor('=>');
|
||||
}
|
||||
// operators
|
||||
elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) {
|
||||
$this->pushToken(/* Token::OPERATOR_TYPE */ 8, preg_replace('/\s+/', ' ', $match[0]));
|
||||
$this->moveCursor($match[0]);
|
||||
}
|
||||
// names
|
||||
elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) {
|
||||
$this->pushToken(/* Token::NAME_TYPE */ 5, $match[0]);
|
||||
$this->moveCursor($match[0]);
|
||||
}
|
||||
// numbers
|
||||
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(/* Token::NUMBER_TYPE */ 6, $number);
|
||||
$this->moveCursor($match[0]);
|
||||
}
|
||||
// punctuation
|
||||
elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) {
|
||||
// opening bracket
|
||||
if (false !== strpos('([{', $this->code[$this->cursor])) {
|
||||
$this->brackets[] = [$this->code[$this->cursor], $this->lineno];
|
||||
}
|
||||
// closing bracket
|
||||
elseif (false !== strpos(')]}', $this->code[$this->cursor])) {
|
||||
if (empty($this->brackets)) {
|
||||
throw new SyntaxError(sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source);
|
||||
}
|
||||
|
||||
list($expect, $lineno) = array_pop($this->brackets);
|
||||
if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) {
|
||||
throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
|
||||
}
|
||||
}
|
||||
|
||||
$this->pushToken(/* Token::PUNCTUATION_TYPE */ 9, $this->code[$this->cursor]);
|
||||
++$this->cursor;
|
||||
}
|
||||
// strings
|
||||
elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) {
|
||||
$this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes(substr($match[0], 1, -1)));
|
||||
$this->moveCursor($match[0]);
|
||||
}
|
||||
// opening double quoted string
|
||||
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]);
|
||||
}
|
||||
// unlexable
|
||||
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\0\x0B");
|
||||
}
|
||||
}
|
||||
|
||||
$this->pushToken(/* Token::TEXT_TYPE */ 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(/* Token::INTERPOLATION_START_TYPE */ 10);
|
||||
$this->moveCursor($match[0]);
|
||||
$this->pushState(self::STATE_INTERPOLATION);
|
||||
} elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && \strlen($match[0]) > 0) {
|
||||
$this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes($match[0]));
|
||||
$this->moveCursor($match[0]);
|
||||
} elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {
|
||||
list($expect, $lineno) = array_pop($this->brackets);
|
||||
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(/* Token::INTERPOLATION_END_TYPE */ 11);
|
||||
$this->moveCursor($match[0]);
|
||||
$this->popState();
|
||||
} else {
|
||||
$this->lexExpression();
|
||||
}
|
||||
}
|
||||
|
||||
private function pushToken($type, $value = ''): void
|
||||
{
|
||||
// do not push empty text tokens
|
||||
if (/* Token::TEXT_TYPE */ 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);
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Loader;
|
||||
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Source;
|
||||
|
||||
/**
|
||||
* Loads a template from an array.
|
||||
*
|
||||
* When using this loader with a cache mechanism, you should know that a new cache
|
||||
* key is generated each time a template content "changes" (the cache key being the
|
||||
* source code of the template). If you don't want to see your cache grows out of
|
||||
* control, you need to take care of clearing the old cache file by yourself.
|
||||
*
|
||||
* This loader should only be used for unit testing.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class ArrayLoader implements LoaderInterface
|
||||
{
|
||||
private $templates = [];
|
||||
|
||||
/**
|
||||
* @param array $templates An array of templates (keys are the names, and values are the source code)
|
||||
*/
|
||||
public function __construct(array $templates = [])
|
||||
{
|
||||
$this->templates = $templates;
|
||||
}
|
||||
|
||||
public function 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;
|
||||
}
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Loader;
|
||||
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Source;
|
||||
|
||||
/**
|
||||
* Loads templates from other loaders.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class ChainLoader implements LoaderInterface
|
||||
{
|
||||
private $hasSourceCache = [];
|
||||
private $loaders = [];
|
||||
|
||||
/**
|
||||
* @param LoaderInterface[] $loaders
|
||||
*/
|
||||
public function __construct(array $loaders = [])
|
||||
{
|
||||
foreach ($loaders as $loader) {
|
||||
$this->addLoader($loader);
|
||||
}
|
||||
}
|
||||
|
||||
public function addLoader(LoaderInterface $loader): void
|
||||
{
|
||||
$this->loaders[] = $loader;
|
||||
$this->hasSourceCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LoaderInterface[]
|
||||
*/
|
||||
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).')' : ''));
|
||||
}
|
||||
}
|
|
@ -1,283 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Loader;
|
||||
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Source;
|
||||
|
||||
/**
|
||||
* Loads template from the filesystem.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class FilesystemLoader implements LoaderInterface
|
||||
{
|
||||
/** Identifier of the main namespace. */
|
||||
public const MAIN_NAMESPACE = '__main__';
|
||||
|
||||
protected $paths = [];
|
||||
protected $cache = [];
|
||||
protected $errorCache = [];
|
||||
|
||||
private $rootPath;
|
||||
|
||||
/**
|
||||
* @param string|array $paths A path or an array of paths where to look for templates
|
||||
* @param string|null $rootPath The root path common to all relative paths (null for getcwd())
|
||||
*/
|
||||
public function __construct($paths = [], string $rootPath = null)
|
||||
{
|
||||
$this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR;
|
||||
if (null !== $rootPath && false !== ($realPath = realpath($rootPath))) {
|
||||
$this->rootPath = $realPath.\DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
if ($paths) {
|
||||
$this->setPaths($paths);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paths to the templates.
|
||||
*/
|
||||
public function getPaths(string $namespace = self::MAIN_NAMESPACE): array
|
||||
{
|
||||
return $this->paths[$namespace] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path namespaces.
|
||||
*
|
||||
* The main namespace is always defined.
|
||||
*/
|
||||
public function getNamespaces(): array
|
||||
{
|
||||
return array_keys($this->paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $paths A path or an array of paths where to look for templates
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws LoaderError
|
||||
*/
|
||||
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, '/\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws LoaderError
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
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 {
|
||||
list($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 (false !== strpos($name, "\0")) {
|
||||
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)
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Loader;
|
||||
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Source;
|
||||
|
||||
/**
|
||||
* Interface all loaders must implement.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface LoaderInterface
|
||||
{
|
||||
/**
|
||||
* Returns the source context for a given template logical name.
|
||||
*
|
||||
* @throws LoaderError When $name is not found
|
||||
*/
|
||||
public function getSourceContext(string $name): Source;
|
||||
|
||||
/**
|
||||
* Gets the cache key to use for the cache for a given template name.
|
||||
*
|
||||
* @throws LoaderError When $name is not found
|
||||
*/
|
||||
public function getCacheKey(string $name): string;
|
||||
|
||||
/**
|
||||
* @param int $time Timestamp of the last modification time of the cached template
|
||||
*
|
||||
* @throws LoaderError When $name is not found
|
||||
*/
|
||||
public function isFresh(string $name, int $time): bool;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(string $name);
|
||||
}
|
52
admin/phpMyAdmin/vendor/twig/twig/src/Markup.php
vendored
52
admin/phpMyAdmin/vendor/twig/twig/src/Markup.php
vendored
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig;
|
||||
|
||||
/**
|
||||
* Marks a content as safe.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count()
|
||||
{
|
||||
return mb_strlen($this->content, $this->charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
* Represents an autoescape node.
|
||||
*
|
||||
* The value is the escaping strategy (can be html, js, ...)
|
||||
*
|
||||
* The true value is equivalent to html.
|
||||
*
|
||||
* If autoescaping is disabled, then the value is false.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
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'));
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
* Represents a block node.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
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'))
|
||||
->outdent()
|
||||
->write("}\n\n")
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
* Represents a block call node.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
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("\$this->displayBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name')))
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
/**
|
||||
* Represents a body node.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class BodyNode extends Node
|
||||
{
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class CheckSecurityCallNode extends Node
|
||||
{
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
$compiler
|
||||
->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n")
|
||||
->write("\$this->checkSecurity();\n")
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
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
|
||||
{
|
||||
$tags = $filters = $functions = [];
|
||||
foreach (['tags', 'filters', 'functions'] as $type) {
|
||||
foreach ($this->{'used'.ucfirst($type)} as $name => $node) {
|
||||
if ($node instanceof Node) {
|
||||
${$type}[$name] = $node->getTemplateLine();
|
||||
} else {
|
||||
${$type}[$node] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$compiler
|
||||
->write("\n")
|
||||
->write("public function checkSecurity()\n")
|
||||
->write("{\n")
|
||||
->indent()
|
||||
->write('static $tags = ')->repr(array_filter($tags))->raw(";\n")
|
||||
->write('static $filters = ')->repr(array_filter($filters))->raw(";\n")
|
||||
->write('static $functions = ')->repr(array_filter($functions))->raw(";\n\n")
|
||||
->write("try {\n")
|
||||
->indent()
|
||||
->write("\$this->sandbox->checkSecurity(\n")
|
||||
->indent()
|
||||
->write(!$tags ? "[],\n" : "['".implode("', '", array_keys($tags))."'],\n")
|
||||
->write(!$filters ? "[],\n" : "['".implode("', '", array_keys($filters))."'],\n")
|
||||
->write(!$functions ? "[]\n" : "['".implode("', '", array_keys($functions))."']\n")
|
||||
->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")
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
|
||||
/**
|
||||
* Checks if casting an expression to __toString() is allowed by the sandbox.
|
||||
*
|
||||
* For instance, when there is a simple Print statement, like {{ article }},
|
||||
* and if the sandbox is enabled, we need to check that the __toString()
|
||||
* method is allowed if 'article' is an object. The same goes for {{ article|upper }}
|
||||
* or {{ random(article) }}
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
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)')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
|
||||
/**
|
||||
* Represents a deprecated node.
|
||||
*
|
||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
||||
*/
|
||||
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) {
|
||||
$compiler->write('@trigger_error(')
|
||||
->subcompile($expr);
|
||||
} else {
|
||||
$varName = $compiler->getVarName();
|
||||
$compiler->write(sprintf('$%s = ', $varName))
|
||||
->subcompile($expr)
|
||||
->raw(";\n")
|
||||
->write(sprintf('@trigger_error($%s', $varName));
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('.')
|
||||
->string(sprintf(' ("%s" at line %d).', $this->getTemplateName(), $this->getTemplateLine()))
|
||||
->raw(", E_USER_DEPRECATED);\n")
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
|
||||
/**
|
||||
* Represents a do node.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
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")
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
|
||||
/**
|
||||
* Represents an embed node.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
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(')')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* Abstract class for all nodes that represents an expression.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
abstract class AbstractExpression extends Node
|
||||
{
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use 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
|
||||
{
|
||||
$compiler->raw('[');
|
||||
$first = true;
|
||||
foreach ($this->getKeyValuePairs() as $pair) {
|
||||
if (!$first) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
$first = false;
|
||||
|
||||
$compiler
|
||||
->subcompile($pair['key'])
|
||||
->raw(' => ')
|
||||
->subcompile($pair['value'])
|
||||
;
|
||||
}
|
||||
$compiler->raw(']');
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* Represents an arrow function.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
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('; }')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class AssignNameExpression extends NameExpression
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('$context[')
|
||||
->string($this->getAttribute('name'))
|
||||
->raw(']')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use 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(')')
|
||||
;
|
||||
}
|
||||
|
||||
abstract public function operator(Compiler $compiler): Compiler;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class AddBinary extends AbstractBinary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('+');
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class AndBinary extends AbstractBinary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('&&');
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class BitwiseAndBinary extends AbstractBinary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('&');
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class BitwiseOrBinary extends AbstractBinary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('|');
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class BitwiseXorBinary extends AbstractBinary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('^');
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class ConcatBinary extends AbstractBinary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('.');
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class DivBinary extends AbstractBinary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('/');
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use 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(') && (\'\' === $%2$s || $%2$s === substr($%1$s, -strlen($%2$s))))', $left, $right))
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('');
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class EqualBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
parent::compile($compiler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('(0 === twig_compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw('))')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('==');
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use 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('/');
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class GreaterBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
parent::compile($compiler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('(1 === twig_compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw('))')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('>');
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class GreaterEqualBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
parent::compile($compiler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('(0 <= twig_compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw('))')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('>=');
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class HasEveryBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('twig_array_every($this->env, ')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('');
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class HasSomeBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('twig_array_some($this->env, ')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('');
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class InBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('twig_in_filter(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('in');
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class LessBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
parent::compile($compiler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('(-1 === twig_compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw('))')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('<');
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class LessEqualBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
parent::compile($compiler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('(0 >= twig_compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw('))')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('<=');
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class MatchesBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('twig_matches(')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('');
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class ModBinary extends AbstractBinary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('%');
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class MulBinary extends AbstractBinary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('*');
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class NotEqualBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if (\PHP_VERSION_ID >= 80000) {
|
||||
parent::compile($compiler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw('(0 !== twig_compare(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw('))')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('!=');
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class NotInBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('!twig_in_filter(')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('not in');
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class OrBinary extends AbstractBinary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('||');
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class PowerBinary extends AbstractBinary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('**');
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use 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('..');
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class SpaceshipBinary extends AbstractBinary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('<=>');
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use 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(') && (\'\' === $%2$s || 0 === strpos($%1$s, $%2$s)))', $left, $right))
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('');
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class SubBinary extends AbstractBinary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('-');
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* Represents a block call node.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
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);
|
||||
|
||||
$this
|
||||
->compileTemplateCall($compiler, 'displayBlock')
|
||||
->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('->%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(')');
|
||||
}
|
||||
}
|
|
@ -1,321 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Extension\ExtensionInterface;
|
||||
use Twig\Node\Node;
|
||||
|
||||
abstract class CallExpression extends AbstractExpression
|
||||
{
|
||||
private $reflector;
|
||||
|
||||
protected function compileCallable(Compiler $compiler)
|
||||
{
|
||||
$callable = $this->getAttribute('callable');
|
||||
|
||||
if (\is_string($callable) && false === strpos($callable, '::')) {
|
||||
$compiler->raw($callable);
|
||||
} else {
|
||||
[$r, $callable] = $this->reflectCallable($callable);
|
||||
|
||||
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
|
||||
{
|
||||
$compiler->raw($isArray ? '[' : '(');
|
||||
|
||||
$first = true;
|
||||
|
||||
if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
|
||||
$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);
|
||||
}
|
||||
|
||||
list($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
|
||||
{
|
||||
[$r, , $callableName] = $this->reflectCallable($callable);
|
||||
|
||||
$parameters = $r->getParameters();
|
||||
if ($this->hasNode('node')) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) {
|
||||
foreach ($this->getAttribute('arguments') as $argument) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
}
|
||||
$isPhpVariadic = false;
|
||||
if ($isVariadic) {
|
||||
$argument = end($parameters);
|
||||
$isArray = $argument && $argument->hasType() && '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)
|
||||
{
|
||||
if (null !== $this->reflector) {
|
||||
return $this->reflector;
|
||||
}
|
||||
|
||||
if (\is_string($callable) && false !== $pos = strpos($callable, '::')) {
|
||||
$callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)];
|
||||
}
|
||||
|
||||
if (\is_array($callable) && method_exists($callable[0], $callable[1])) {
|
||||
$r = new \ReflectionMethod($callable[0], $callable[1]);
|
||||
|
||||
return $this->reflector = [$r, $callable, $r->class.'::'.$r->name];
|
||||
}
|
||||
|
||||
$checkVisibility = $callable instanceof \Closure;
|
||||
try {
|
||||
$closure = \Closure::fromCallable($callable);
|
||||
} catch (\TypeError $e) {
|
||||
throw new \LogicException(sprintf('Callback for %s "%s" is not callable in the current scope.', $this->getAttribute('type'), $this->getAttribute('name')), 0, $e);
|
||||
}
|
||||
$r = new \ReflectionFunction($closure);
|
||||
|
||||
if (false !== strpos($r->name, '{closure}')) {
|
||||
return $this->reflector = [$r, $callable, 'Closure'];
|
||||
}
|
||||
|
||||
if ($object = $r->getClosureThis()) {
|
||||
$callable = [$object, $r->name];
|
||||
$callableName = (\function_exists('get_debug_type') ? get_debug_type($object) : \get_class($object)).'::'.$r->name;
|
||||
} elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) {
|
||||
$callableName = $class->name.'::'.$r->name;
|
||||
} elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) {
|
||||
$callableName = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name;
|
||||
} else {
|
||||
$callable = $callableName = $r->name;
|
||||
}
|
||||
|
||||
if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) {
|
||||
$callable = $r->getClosure();
|
||||
}
|
||||
|
||||
return $this->reflector = [$r, $callable, $callableName];
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use 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
|
||||
{
|
||||
$compiler
|
||||
->raw('((')
|
||||
->subcompile($this->getNode('expr1'))
|
||||
->raw(') ? (')
|
||||
->subcompile($this->getNode('expr2'))
|
||||
->raw(') : (')
|
||||
->subcompile($this->getNode('expr3'))
|
||||
->raw('))')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use 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'));
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Filter;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\ConditionalExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\FilterExpression;
|
||||
use Twig\Node\Expression\GetAttrExpression;
|
||||
use Twig\Node\Expression\NameExpression;
|
||||
use Twig\Node\Expression\Test\DefinedTest;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* Returns the value or the default value when it is undefined or empty.
|
||||
*
|
||||
* {{ var.foo|default('foo item on var is not defined') }}
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
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'));
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use 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], [], $lineno, $tag);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$name = $this->getNode('filter')->getAttribute('value');
|
||||
$filter = $compiler->getEnvironment()->getFilter($name);
|
||||
|
||||
$this->setAttribute('name', $name);
|
||||
$this->setAttribute('type', 'filter');
|
||||
$this->setAttribute('needs_environment', $filter->needsEnvironment());
|
||||
$this->setAttribute('needs_context', $filter->needsContext());
|
||||
$this->setAttribute('arguments', $filter->getArguments());
|
||||
$this->setAttribute('callable', $filter->getCallable());
|
||||
$this->setAttribute('is_variadic', $filter->isVariadic());
|
||||
|
||||
$this->compileCallable($compiler);
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Node;
|
||||
|
||||
class FunctionExpression extends CallExpression
|
||||
{
|
||||
public function __construct(string $name, Node $arguments, int $lineno)
|
||||
{
|
||||
parent::__construct(['arguments' => $arguments], ['name' => $name, 'is_defined_test' => false], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
$name = $this->getAttribute('name');
|
||||
$function = $compiler->getEnvironment()->getFunction($name);
|
||||
|
||||
$this->setAttribute('name', $name);
|
||||
$this->setAttribute('type', 'function');
|
||||
$this->setAttribute('needs_environment', $function->needsEnvironment());
|
||||
$this->setAttribute('needs_context', $function->needsContext());
|
||||
$this->setAttribute('arguments', $function->getArguments());
|
||||
$callable = $function->getCallable();
|
||||
if ('constant' === $name && $this->getAttribute('is_defined_test')) {
|
||||
$callable = 'twig_constant_is_defined';
|
||||
}
|
||||
$this->setAttribute('callable', $callable);
|
||||
$this->setAttribute('is_variadic', $function->isVariadic());
|
||||
|
||||
$this->compileCallable($compiler);
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Extension\SandboxExtension;
|
||||
use 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();
|
||||
|
||||
// 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)
|
||||
->raw(') || ')
|
||||
->raw($var)
|
||||
->raw(' instanceof ArrayAccess ? (')
|
||||
->raw($var)
|
||||
->raw('[')
|
||||
->subcompile($this->getNode('attribute'))
|
||||
->raw('] ?? null) : null)')
|
||||
;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler->raw('twig_get_attribute($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(')')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
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('print (')
|
||||
->subcompile($this->getNode('node'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use 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('twig_call_macro($macros[')
|
||||
->repr($this->getNode('node')->getAttribute('name'))
|
||||
->raw('], ')
|
||||
->repr($this->getAttribute('method'))
|
||||
->raw(', [')
|
||||
;
|
||||
$first = true;
|
||||
foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) {
|
||||
if (!$first) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
$first = false;
|
||||
|
||||
$compiler->subcompile($pair['value']);
|
||||
}
|
||||
$compiler
|
||||
->raw('], ')
|
||||
->repr($this->getTemplateLine())
|
||||
->raw(', $context, $this->getSourceContext())');
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use 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 ($this->isSpecial()) {
|
||||
$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 ($this->isSpecial()) {
|
||||
$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()
|
||||
{
|
||||
return isset($this->specialVars[$this->getAttribute('name')]);
|
||||
}
|
||||
|
||||
public function isSimple()
|
||||
{
|
||||
return !$this->isSpecial() && !$this->getAttribute('is_defined_test');
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\Binary\AndBinary;
|
||||
use Twig\Node\Expression\Test\DefinedTest;
|
||||
use Twig\Node\Expression\Test\NullTest;
|
||||
use Twig\Node\Expression\Unary\NotUnary;
|
||||
use Twig\Node\Node;
|
||||
|
||||
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
|
||||
{
|
||||
/*
|
||||
* This optimizes only one case. PHP 7 also supports more complex expressions
|
||||
* that can return null. So, for instance, if log is defined, log("foo") ?? "..." works,
|
||||
* but log($a["foo"]) ?? "..." does not if $a["foo"] is not defined. More advanced
|
||||
* cases might be implemented as an optimizer node visitor, but has not been done
|
||||
* as benefits are probably not worth the added complexity.
|
||||
*/
|
||||
if ($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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
* Represents a parent node.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ParentExpression extends AbstractExpression
|
||||
{
|
||||
public function __construct(string $name, int $lineno, string $tag = null)
|
||||
{
|
||||
parent::__construct([], ['output' => false, 'name' => $name], $lineno, $tag);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if ($this->getAttribute('output')) {
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write('$this->displayParentBlock(')
|
||||
->string($this->getAttribute('name'))
|
||||
->raw(", \$context, \$blocks);\n")
|
||||
;
|
||||
} else {
|
||||
$compiler
|
||||
->raw('$this->renderParentBlock(')
|
||||
->string($this->getAttribute('name'))
|
||||
->raw(', $context, $blocks)')
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class TempNameExpression extends AbstractExpression
|
||||
{
|
||||
public function __construct(string $name, int $lineno)
|
||||
{
|
||||
parent::__construct([], ['name' => $name], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('$_')
|
||||
->raw($this->getAttribute('name'))
|
||||
->raw('_')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Test;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\TestExpression;
|
||||
|
||||
/**
|
||||
* Checks if a variable is the exact same value as a constant.
|
||||
*
|
||||
* {% if post.status is constant('Post::PUBLISHED') %}
|
||||
* the status attribute is exactly the same as Post::PUBLISHED
|
||||
* {% endif %}
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ConstantTest extends TestExpression
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('(')
|
||||
->subcompile($this->getNode('node'))
|
||||
->raw(' === constant(')
|
||||
;
|
||||
|
||||
if ($this->getNode('arguments')->hasNode(1)) {
|
||||
$compiler
|
||||
->raw('get_class(')
|
||||
->subcompile($this->getNode('arguments')->getNode(1))
|
||||
->raw(')."::".')
|
||||
;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->subcompile($this->getNode('arguments')->getNode(0))
|
||||
->raw('))')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Test;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\BlockReferenceExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\FunctionExpression;
|
||||
use Twig\Node\Expression\GetAttrExpression;
|
||||
use Twig\Node\Expression\MethodCallExpression;
|
||||
use Twig\Node\Expression\NameExpression;
|
||||
use Twig\Node\Expression\TestExpression;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* Checks if a variable is defined in the current context.
|
||||
*
|
||||
* {# defined works with variable names and variable attributes #}
|
||||
* {% if foo is defined %}
|
||||
* {# ... #}
|
||||
* {% endif %}
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class DefinedTest extends TestExpression
|
||||
{
|
||||
public function __construct(Node $node, string $name, ?Node $arguments, int $lineno)
|
||||
{
|
||||
if ($node instanceof NameExpression) {
|
||||
$node->setAttribute('is_defined_test', true);
|
||||
} elseif ($node instanceof GetAttrExpression) {
|
||||
$node->setAttribute('is_defined_test', true);
|
||||
$this->changeIgnoreStrictCheck($node);
|
||||
} elseif ($node instanceof BlockReferenceExpression) {
|
||||
$node->setAttribute('is_defined_test', true);
|
||||
} elseif ($node instanceof FunctionExpression && 'constant' === $node->getAttribute('name')) {
|
||||
$node->setAttribute('is_defined_test', true);
|
||||
} elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) {
|
||||
$node = new ConstantExpression(true, $node->getTemplateLine());
|
||||
} elseif ($node instanceof MethodCallExpression) {
|
||||
$node->setAttribute('is_defined_test', true);
|
||||
} else {
|
||||
throw new SyntaxError('The "defined" test only works with simple variables.', $lineno);
|
||||
}
|
||||
|
||||
parent::__construct($node, $name, $arguments, $lineno);
|
||||
}
|
||||
|
||||
private function changeIgnoreStrictCheck(GetAttrExpression $node)
|
||||
{
|
||||
$node->setAttribute('optimizable', false);
|
||||
$node->setAttribute('ignore_strict_check', true);
|
||||
|
||||
if ($node->getNode('node') instanceof GetAttrExpression) {
|
||||
$this->changeIgnoreStrictCheck($node->getNode('node'));
|
||||
}
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler->subcompile($this->getNode('node'));
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Test;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\TestExpression;
|
||||
|
||||
/**
|
||||
* Checks if a variable is divisible by a number.
|
||||
*
|
||||
* {% if loop.index is divisible by(3) %}
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class DivisiblebyTest extends TestExpression
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('(0 == ')
|
||||
->subcompile($this->getNode('node'))
|
||||
->raw(' % ')
|
||||
->subcompile($this->getNode('arguments')->getNode(0))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Test;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\TestExpression;
|
||||
|
||||
/**
|
||||
* Checks if a number is even.
|
||||
*
|
||||
* {{ var is even }}
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class EvenTest extends TestExpression
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('(')
|
||||
->subcompile($this->getNode('node'))
|
||||
->raw(' % 2 == 0')
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Test;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\TestExpression;
|
||||
|
||||
/**
|
||||
* Checks that a variable is null.
|
||||
*
|
||||
* {{ var is none }}
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class NullTest extends TestExpression
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('(null === ')
|
||||
->subcompile($this->getNode('node'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Test;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\TestExpression;
|
||||
|
||||
/**
|
||||
* Checks if a number is odd.
|
||||
*
|
||||
* {{ var is odd }}
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class OddTest extends TestExpression
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('(')
|
||||
->subcompile($this->getNode('node'))
|
||||
->raw(' % 2 != 0')
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Test;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\TestExpression;
|
||||
|
||||
/**
|
||||
* Checks if a variable is the same as another one (=== in PHP).
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class SameasTest extends TestExpression
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('(')
|
||||
->subcompile($this->getNode('node'))
|
||||
->raw(' === ')
|
||||
->subcompile($this->getNode('arguments')->getNode(0))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Node;
|
||||
|
||||
class TestExpression extends CallExpression
|
||||
{
|
||||
public function __construct(Node $node, string $name, ?Node $arguments, int $lineno)
|
||||
{
|
||||
$nodes = ['node' => $node];
|
||||
if (null !== $arguments) {
|
||||
$nodes['arguments'] = $arguments;
|
||||
}
|
||||
|
||||
parent::__construct($nodes, ['name' => $name], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$name = $this->getAttribute('name');
|
||||
$test = $compiler->getEnvironment()->getTest($name);
|
||||
|
||||
$this->setAttribute('name', $name);
|
||||
$this->setAttribute('type', 'test');
|
||||
$this->setAttribute('arguments', $test->getArguments());
|
||||
$this->setAttribute('callable', $test->getCallable());
|
||||
$this->setAttribute('is_variadic', $test->isVariadic());
|
||||
|
||||
$this->compileCallable($compiler);
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Unary;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Node;
|
||||
|
||||
abstract class AbstractUnary extends AbstractExpression
|
||||
{
|
||||
public function __construct(Node $node, int $lineno)
|
||||
{
|
||||
parent::__construct(['node' => $node], [], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler->raw(' ');
|
||||
$this->operator($compiler);
|
||||
$compiler->subcompile($this->getNode('node'));
|
||||
}
|
||||
|
||||
abstract public function operator(Compiler $compiler): Compiler;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Unary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class NegUnary extends AbstractUnary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('-');
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue