diff --git a/composer.json b/composer.json index fed4a61..fc50d86 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,8 @@ "license": "GPL-2.0+", "require": { "composer/installers": "^1.0.21", - "wikimedia/composer-merge-plugin": "~1.3" + "wikimedia/composer-merge-plugin": "~1.3", + "twig/twig": "~2.0" }, "replace": { "drupal/core": "~8.3" diff --git a/composer.lock b/composer.lock index 97c6e64..36cf29f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "1167766f8eca494239cc8a1baa1ea6bb", - "content-hash": "0cfc95231ad5eb3f8d306e45c9cc92d9", + "hash": "bca10f7f33e0e30975baeed903bcdf57", + "content-hash": "7176f673f8f5e39e5d34730466a2eb16", "packages": [ { "name": "asm89/stack-cors", @@ -1100,12 +1100,12 @@ "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "1.0.0" + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/1.0.0", - "reference": "1.0.0", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", "shasum": "" }, "type": "library", @@ -2436,29 +2436,30 @@ }, { "name": "twig/twig", - "version": "v1.25.0", + "version": "v2.0.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "f16a634ab08d87e520da5671ec52153d627f10f6" + "reference": "2a86dde1288d7270169083d0e078dc7ebe0f48b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/f16a634ab08d87e520da5671ec52153d627f10f6", - "reference": "f16a634ab08d87e520da5671ec52153d627f10f6", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/2a86dde1288d7270169083d0e078dc7ebe0f48b6", + "reference": "2a86dde1288d7270169083d0e078dc7ebe0f48b6", "shasum": "" }, "require": { - "php": ">=5.2.7" + "php": "^7.0", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { "symfony/debug": "~2.7", - "symfony/phpunit-bridge": "~2.7" + "symfony/phpunit-bridge": "~3.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.25-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -2493,7 +2494,7 @@ "keywords": [ "templating" ], - "time": "2016-09-21 23:05:12" + "time": "2017-01-05 17:12:02" }, { "name": "wikimedia/composer-merge-plugin", diff --git a/core/composer.json b/core/composer.json index 71f07d0..78e94c7 100644 --- a/core/composer.json +++ b/core/composer.json @@ -19,7 +19,7 @@ "symfony/process": "~2.8", "symfony/polyfill-iconv": "~1.0", "symfony/yaml": "~2.8", - "twig/twig": "^1.23.1", + "twig/twig": "^1.23.1|^2", "doctrine/common": "2.5.*", "doctrine/annotations": "1.2.*", "guzzlehttp/guzzle": "^6.2.1", diff --git a/core/lib/Drupal/Core/Template/Loader/StringLoader.php b/core/lib/Drupal/Core/Template/Loader/StringLoader.php index 6325b9e98a..d0590a226a 100644 --- a/core/lib/Drupal/Core/Template/Loader/StringLoader.php +++ b/core/lib/Drupal/Core/Template/Loader/StringLoader.php @@ -44,6 +44,14 @@ public function getSource($name) { /** * {@inheritdoc} */ + public function getSourceContext($name) { + $name = (string) $name; + return new \Twig_Source($name, $name); + } + + /** + * {@inheritdoc} + */ public function getCacheKey($name) { return $name; } diff --git a/core/lib/Drupal/Core/Template/TwigEnvironment.php b/core/lib/Drupal/Core/Template/TwigEnvironment.php index f5ee459767..dc44520f2d 100644 --- a/core/lib/Drupal/Core/Template/TwigEnvironment.php +++ b/core/lib/Drupal/Core/Template/TwigEnvironment.php @@ -24,6 +24,8 @@ class TwigEnvironment extends \Twig_Environment { protected $templateClasses; protected $twigCachePrefix = ''; + + protected $templateClassPrefix = '__TwigTemplate_'; /** * Constructs a TwigEnvironment object and stores cache and storage @@ -42,11 +44,7 @@ class TwigEnvironment extends \Twig_Environment { * @param array $options * The options for the Twig environment. */ - public function __construct($root, CacheBackendInterface $cache, $twig_extension_hash, StateInterface $state, \Twig_LoaderInterface $loader = NULL, $options = array()) { - // Ensure that twig.engine is loaded, given that it is needed to render a - // template because functions like TwigExtension::escapeFilter() are called. - require_once $root . '/core/themes/engines/twig/twig.engine'; - + public function __construct($root, CacheBackendInterface $cache, $twig_extension_hash, StateInterface $state, \Twig_LoaderInterface $loader = NULL, array $options = array()) { $this->templateClasses = array(); $options += array( @@ -57,11 +55,6 @@ public function __construct($root, CacheBackendInterface $cache, $twig_extension ); // Ensure autoescaping is always on. $options['autoescape'] = 'html'; - - $policy = new TwigSandboxPolicy(); - $sandbox = new \Twig_Extension_Sandbox($policy, TRUE); - $this->addExtension($sandbox); - if ($options['cache'] === TRUE) { $current = $state->get('twig_extension_hash_prefix', ['twig_extension_hash' => '']); if ($current['twig_extension_hash'] !== $twig_extension_hash || empty($current['twig_cache_prefix'])) { @@ -78,8 +71,11 @@ public function __construct($root, CacheBackendInterface $cache, $twig_extension $options['cache'] = new TwigPhpStorageCache($cache, $this->twigCachePrefix); } - $this->loader = $loader; - parent::__construct($this->loader, $options); + $this->setLoader($loader); + parent::__construct($this->getLoader(), $options); + $policy = new TwigSandboxPolicy(); + $sandbox = new \Twig_Extension_Sandbox($policy, TRUE); + $this->addExtension($sandbox); } /** @@ -110,7 +106,7 @@ public function getTemplateClass($name, $index = NULL) { // node.html.twig for the output of each node and the same compiled class. $cache_index = $name . (NULL === $index ? '' : '_' . $index); if (!isset($this->templateClasses[$cache_index])) { - $this->templateClasses[$cache_index] = $this->templateClassPrefix . hash('sha256', $this->loader->getCacheKey($name)) . (NULL === $index ? '' : '_' . $index); + $this->templateClasses[$cache_index] = $this->templateClassPrefix . hash('sha256', $this->getLoader()->getCacheKey($name)) . (NULL === $index ? '' : '_' . $index); } return $this->templateClasses[$cache_index]; } diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php index 7d67ff991c..b3edd81867 100644 --- a/core/lib/Drupal/Core/Template/TwigExtension.php +++ b/core/lib/Drupal/Core/Template/TwigExtension.php @@ -177,7 +177,7 @@ public function getFilters() { new \Twig_SimpleFilter('safe_join', [$this, 'safeJoin'], ['needs_environment' => TRUE, 'is_safe' => ['html']]), // Array filters. - new \Twig_SimpleFilter('without', 'twig_without'), + new \Twig_SimpleFilter('without', [$this, 'withoutFilter']), // CSS class and ID filters. new \Twig_SimpleFilter('clean_class', '\Drupal\Component\Utility\Html::getClass'), @@ -629,4 +629,37 @@ public function createAttribute(array $attributes = []) { return new Attribute($attributes); } + /** + * Removes child elements from a copy of the original array. + * + * Creates a copy of the renderable array and removes child elements by key + * specified through filter's arguments. The copy can be printed without these + * elements. The original renderable array is still available and can be used + * to print child elements in their entirety in the twig template. + * + * @param array|object $element + * The parent renderable array to exclude the child items. + * @param string[] $args, ... + * The string keys of $element to prevent printing. + * + * @return array + * The filtered renderable array. + */ + public function withoutFilter($element) { + if ($element instanceof \ArrayAccess) { + $filtered_element = clone $element; + } + else { + $filtered_element = $element; + } + $args = func_get_args(); + unset($args[0]); + foreach ($args as $arg) { + if (isset($filtered_element[$arg])) { + unset($filtered_element[$arg]); + } + } + return $filtered_element; + } + } diff --git a/core/lib/Drupal/Core/Template/TwigNodeTrans.php b/core/lib/Drupal/Core/Template/TwigNodeTrans.php index 21006fd1ed..0900946027 100644 --- a/core/lib/Drupal/Core/Template/TwigNodeTrans.php +++ b/core/lib/Drupal/Core/Template/TwigNodeTrans.php @@ -17,13 +17,21 @@ class TwigNodeTrans extends \Twig_Node { /** * {@inheritdoc} */ - public function __construct(\Twig_Node $body, \Twig_Node $plural = NULL, \Twig_Node_Expression $count = NULL, \Twig_Node_Expression $options = NULL, $lineno, $tag = NULL) { - parent::__construct(array( - 'count' => $count, - 'body' => $body, - 'plural' => $plural, - 'options' => $options, - ), array(), $lineno, $tag); + public function __construct(\Twig_Node $body, \Twig_Node $plural = NULL, \Twig_Node_Expression $count = NULL, \Twig_Node $options = NULL, $lineno, $tag = NULL) { + $nodes = []; + if (NULL !== $body) { + $nodes['body'] = $body; + } + if (NULL !== $count) { + $nodes['count'] = $count; + } + if (NULL !== $plural) { + $nodes['plural'] = $plural; + } + if (NULL !== $options) { + $nodes['options'] = $options; + } + parent::__construct($nodes, [], $lineno, $tag); } /** @@ -32,29 +40,27 @@ public function __construct(\Twig_Node $body, \Twig_Node $plural = NULL, \Twig_N public function compile(\Twig_Compiler $compiler) { $compiler->addDebugInfo($this); - $options = $this->getNode('options'); - list($singular, $tokens) = $this->compileString($this->getNode('body')); $plural = NULL; - if (NULL !== $this->getNode('plural')) { + if ($this->hasNode('plural')) { list($plural, $pluralTokens) = $this->compileString($this->getNode('plural')); $tokens = array_merge($tokens, $pluralTokens); } // Start writing with the function to be called. - $compiler->write('echo ' . (empty($plural) ? 't' : '\Drupal::translation()->formatPlural') . '('); + $compiler->write('echo ' . ($this->hasNode('plural') ? '\Drupal::translation()->formatPlural' : 't') . '('); // Move the count to the beginning of the parameters list. - if (!empty($plural)) { - $compiler->raw('abs(')->subcompile($this->getNode('count'))->raw('), '); + if ($this->hasNode('plural')) { + $compiler->raw('abs(')->subcompile($this->hasNode('count') ? $this->getNode('count') : NULL)->raw('), '); } // Write the singular text parameter. $compiler->subcompile($singular); // Write the plural text parameter, if necessary. - if (!empty($plural)) { + if ($this->hasNode('plural')) { $compiler->raw(', ')->subcompile($plural); } @@ -67,8 +73,8 @@ public function compile(\Twig_Compiler $compiler) { $compiler->raw(')'); // Write any options passed. - if (!empty($options)) { - $compiler->raw(', ')->subcompile($options); + if ($this->hasNode('options')) { + $compiler->raw(', ')->subcompile($this->getNode('options')); } // Write function closure. @@ -103,7 +109,7 @@ protected function compileString(\Twig_Node $body) { $text = ''; foreach ($body as $node) { - if (get_class($node) === 'Twig_Node' && $node->getNode(0) instanceof \Twig_Node_SetTemp) { + if (get_class($node) === 'Twig_Node') { $node = $node->getNode(1); } @@ -157,7 +163,7 @@ protected function compileString(\Twig_Node $body) { if (!is_null($args)) { $argName = $args->getAttribute('name'); } - $expr = new \Twig_Node_Expression_Name($argName, $n->getLine()); + $expr = new \Twig_Node_Expression_Name($argName, $n->getTemplateLine()); } $placeholder = sprintf('%s%s', $argPrefix, $argName); $text .= $placeholder; @@ -176,7 +182,7 @@ protected function compileString(\Twig_Node $body) { $text = $body->getAttribute('data'); } - return array(new \Twig_Node(array(new \Twig_Node_Expression_Constant(trim($text), $body->getLine()))), $tokens); + return array(new \Twig_Node(array(new \Twig_Node_Expression_Constant(trim($text), $body->getTemplateLine()))), $tokens); } } diff --git a/core/lib/Drupal/Core/Template/TwigNodeVisitor.php b/core/lib/Drupal/Core/Template/TwigNodeVisitor.php index 6222d69e3a..17cbb428cb 100644 --- a/core/lib/Drupal/Core/Template/TwigNodeVisitor.php +++ b/core/lib/Drupal/Core/Template/TwigNodeVisitor.php @@ -33,7 +33,7 @@ protected function doLeaveNode(\Twig_Node $node, \Twig_Environment $env) { return $node; } $class = get_class($node); - $line = $node->getLine(); + $line = $node->getTemplateLine(); return new $class( new \Twig_Node_Expression_Function('render_var', new \Twig_Node(array($node->getNode('expr'))), $line), $line diff --git a/core/lib/Drupal/Core/Template/TwigTransTokenParser.php b/core/lib/Drupal/Core/Template/TwigTransTokenParser.php index fb16017537..2e7c086ea3 100644 --- a/core/lib/Drupal/Core/Template/TwigTransTokenParser.php +++ b/core/lib/Drupal/Core/Template/TwigTransTokenParser.php @@ -21,10 +21,10 @@ class TwigTransTokenParser extends \Twig_TokenParser { public function parse(\Twig_Token $token) { $lineno = $token->getLine(); $stream = $this->parser->getStream(); - $body = NULL; - $options = NULL; - $count = NULL; $plural = NULL; + $count = NULL; + $options = NULL; + $body = NULL; if (!$stream->test(\Twig_Token::BLOCK_END_TYPE) && $stream->test(\Twig_Token::STRING_TYPE)) { $body = $this->parser->getExpressionParser()->parseExpression(); @@ -55,14 +55,14 @@ public function parse(\Twig_Token $token) { /** * Detect a 'plural' switch or the end of a 'trans' tag. */ - public function decideForFork($token) { + public function decideForFork(\Twig_Token $token) { return $token->test(array('plural', 'endtrans')); } /** * Detect the end of a 'trans' tag. */ - public function decideForEnd($token) { + public function decideForEnd(\Twig_Token $token) { return $token->test('endtrans'); } diff --git a/core/modules/system/tests/modules/twig_extension_test/src/TwigExtension/TestExtension.php b/core/modules/system/tests/modules/twig_extension_test/src/TwigExtension/TestExtension.php index 78d1020288..3baa6e2b9c 100644 --- a/core/modules/system/tests/modules/twig_extension_test/src/TwigExtension/TestExtension.php +++ b/core/modules/system/tests/modules/twig_extension_test/src/TwigExtension/TestExtension.php @@ -2,11 +2,10 @@ namespace Drupal\twig_extension_test\TwigExtension; - /** * A test Twig extension that adds a custom function and a custom filter. */ -class TestExtension extends \Twig_Extension { +class TestExtension extends \TwigExtension { /** * Generates a list of all Twig functions that this extension defines. @@ -21,9 +20,9 @@ class TestExtension extends \Twig_Extension { * The value is a standard PHP callback that defines what the function does. */ public function getFunctions() { - return array( - 'testfunc' => new \Twig_Function_Function(array('Drupal\twig_extension_test\TwigExtension\TestExtension', 'testFunction')), - ); + return [ + new \Twig_SimpleFunction('testfunc', [$this, 'testFunction']), + ]; } /** @@ -39,9 +38,9 @@ public function getFunctions() { * The value is a standard PHP callback that defines what the filter does. */ public function getFilters() { - return array( - 'testfilter' => new \Twig_Filter_Function(array('Drupal\twig_extension_test\TwigExtension\TestExtension', 'testFilter')), - ); + return [ + new \Twig_SimpleFilter('testfilter', [$this, 'testFilter']), + ]; } /** diff --git a/core/modules/system/tests/modules/twig_extension_test/twig_extension_test.services.yml b/core/modules/system/tests/modules/twig_extension_test/twig_extension_test.services.yml index 491d1e8c5b..8784c0f341 100644 --- a/core/modules/system/tests/modules/twig_extension_test/twig_extension_test.services.yml +++ b/core/modules/system/tests/modules/twig_extension_test/twig_extension_test.services.yml @@ -1,6 +1,5 @@ services: twig_extension_test.twig.test_extension: - arguments: ['@renderer'] class: Drupal\twig_extension_test\TwigExtension\TestExtension tags: - { name: twig.extension } diff --git a/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php b/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php index c3e7af6a3c..3804e1072c 100644 --- a/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php +++ b/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php @@ -115,13 +115,18 @@ public function testCacheFilename() { // Note: Later we refetch the twig service in order to bypass its internal // static cache. $environment = \Drupal::service('twig'); + $template_path = 'core/modules/system/templates/container.html.twig'; - $original_filename = $environment->getCacheFilename('core/modules/system/templates/container.html.twig'); + $cache = $environment->getCache(); + $class = $environment->getTemplateClass($template_path); + $original_filename = $cache->generateKey($template_path, $class); \Drupal::getContainer()->set('twig', NULL); \Drupal::service('module_installer')->install(['twig_extension_test']); $environment = \Drupal::service('twig'); - $new_extension_filename = $environment->getCacheFilename('core/modules/system/templates/container.html.twig'); + $cache = $environment->getCache(); + $class = $environment->getTemplateClass($template_path); + $new_extension_filename = $cache->generateKey($template_path, $class); \Drupal::getContainer()->set('twig', NULL); $this->assertNotEqual($new_extension_filename, $original_filename); diff --git a/core/tests/Drupal/Tests/Core/Template/AttributeTest.php b/core/tests/Drupal/Tests/Core/Template/AttributeTest.php index 09ec9894dc..3ffd46da3b 100644 --- a/core/tests/Drupal/Tests/Core/Template/AttributeTest.php +++ b/core/tests/Drupal/Tests/Core/Template/AttributeTest.php @@ -263,7 +263,7 @@ public function testChainAddRemoveClasses() { * @covers ::addClass */ public function testTwigAddRemoveClasses($template, $expected, $seed_attributes = array()) { - $loader = new \Twig_Loader_String(); + $loader = new \Twig_Loader_Array($template); $twig = new \Twig_Environment($loader); $data = array('attributes' => new Attribute($seed_attributes)); $result = $twig->render($template, $data); diff --git a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php index 396b2f9765..eb8cfb34b6 100644 --- a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php +++ b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php @@ -10,6 +10,19 @@ use Drupal\Core\Template\TwigExtension; use Drupal\Tests\UnitTestCase; +if (!function_exists(__NAMESPACE__ . 't')) { + + function t($string, array $args = []) { + return strtr($string, $args); + } + +} +if (!function_exists(__NAMESPACE__ . 'file_create_url')) { + + function file_create_url() {} + +} + /** * Tests the twig extension. * @@ -74,7 +87,9 @@ public function setUp() { * @dataProvider providerTestEscaping */ public function testEscaping($template, $expected) { - $twig = new \Twig_Environment(NULL, array( + $renderer = $this->getMock('\Drupal\Core\Render\RendererInterface'); + $loader = new \Twig_Loader_Filesystem(); + $twig = new \Twig_Environment($loader, array( 'debug' => TRUE, 'cache' => FALSE, 'autoescape' => 'html', @@ -134,7 +149,7 @@ public function testActiveTheme() { ->method('getActiveTheme') ->willReturn($active_theme); - $loader = new \Twig_Loader_String(); + $loader = new \Twig_Loader_Array([]); $twig = new \Twig_Environment($loader); $twig->addExtension($this->systemUnderTest); $result = $twig->render('{{ active_theme() }}'); @@ -171,7 +186,7 @@ public function testActiveThemePath() { ->method('getActiveTheme') ->willReturn($active_theme); - $loader = new \Twig_Loader_String(); + $loader = new \Twig_Loader_Array([]); $twig = new \Twig_Environment($loader); $twig->addExtension($this->systemUnderTest); $result = $twig->render('{{ active_theme_path() }}'); @@ -184,7 +199,9 @@ public function testActiveThemePath() { * @covers ::escapeFilter */ public function testSafeStringEscaping() { - $twig = new \Twig_Environment(NULL, array( + $renderer = $this->getMock('\Drupal\Core\Render\RendererInterface'); + $loader = new \Twig_Loader_Filesystem(); + $twig = new \Twig_Environment($loader, array( 'debug' => TRUE, 'cache' => FALSE, 'autoescape' => 'html', @@ -268,7 +285,8 @@ public function providerTestRenderVar() { * @covers ::bubbleArgMetadata */ public function testEscapeWithGeneratedLink() { - $twig = new \Twig_Environment(NULL, [ + $loader = new \Twig_Loader_Filesystem(); + $twig = new \Twig_Environment($loader, [ 'debug' => TRUE, 'cache' => FALSE, 'autoescape' => 'html', @@ -326,7 +344,7 @@ public function testRenderVarWithGeneratedLink() { * @covers ::createAttribute */ public function testCreateAttribute() { - $loader = new StringLoader(); + $loader = new \Twig_Loader_Array([]); $twig = new \Twig_Environment($loader); $twig->addExtension($this->systemUnderTest);