diff --git a/core/2572605-50.patch b/core/2572605-50.patch new file mode 100644 index 0000000..2461d39 --- /dev/null +++ b/core/2572605-50.patch @@ -0,0 +1,551 @@ +diff --git a/composer.lock b/composer.lock +index 8694dc4..5f3a006 100644 +--- a/composer.lock ++++ b/composer.lock +@@ -2257,20 +2257,21 @@ + }, + { + "name": "twig/twig", +- "version": "v1.35.0", ++ "version": "v2.4.4", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", +- "reference": "daa657073e55b0a78cce8fdd22682fddecc6385f" ++ "reference": "eddb97148ad779f27e670e1e3f19fb323aedafeb" + }, + "dist": { + "type": "zip", +- "url": "https://api.github.com/repos/twigphp/Twig/zipball/daa657073e55b0a78cce8fdd22682fddecc6385f", +- "reference": "daa657073e55b0a78cce8fdd22682fddecc6385f", ++ "url": "https://api.github.com/repos/twigphp/Twig/zipball/eddb97148ad779f27e670e1e3f19fb323aedafeb", ++ "reference": "eddb97148ad779f27e670e1e3f19fb323aedafeb", + "shasum": "" + }, + "require": { +- "php": ">=5.3.3" ++ "php": "^7.0", ++ "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/container": "^1.0", +@@ -2280,7 +2281,7 @@ + "type": "library", + "extra": { + "branch-alias": { +- "dev-master": "1.35-dev" ++ "dev-master": "2.4-dev" + } + }, + "autoload": { +@@ -2318,7 +2319,7 @@ + "keywords": [ + "templating" + ], +- "time": "2017-09-27T18:06:46+00:00" ++ "time": "2017-09-27T18:10:31+00:00" + }, + { + "name": "wikimedia/composer-merge-plugin", +diff --git a/core/composer.json b/core/composer.json +index 31bc51a..626f6a5 100644 +--- a/core/composer.json ++++ b/core/composer.json +@@ -18,7 +18,7 @@ + "symfony/process": "~3.2.8", + "symfony/polyfill-iconv": "^1.0", + "symfony/yaml": "~3.2.8", +- "twig/twig": "^1.35.0", ++ "twig/twig": "^1.35.0|^2.4.0", + "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 6325b9e..d0590a2 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 21755a5..5271155 100644 +--- a/core/lib/Drupal/Core/Template/TwigEnvironment.php ++++ b/core/lib/Drupal/Core/Template/TwigEnvironment.php +@@ -25,6 +25,8 @@ class TwigEnvironment extends \Twig_Environment { + + protected $twigCachePrefix = ''; + ++ protected $templateClassPrefix = '__TwigTemplate_'; ++ + /** + * Constructs a TwigEnvironment object and stores cache and storage + * internally. +@@ -42,7 +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 = []) { ++ public function __construct($root, CacheBackendInterface $cache, $twig_extension_hash, StateInterface $state, \Twig_LoaderInterface $loader = NULL, array $options = []) { + // 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'; +@@ -57,11 +59,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 +75,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 +110,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]; + } +@@ -140,7 +140,7 @@ public function getTemplateClass($name, $index = NULL) { + public function renderInline($template_string, array $context = []) { + // Prefix all inline templates with a special comment. + $template_string = '{# inline_template_start #}' . $template_string; +- return Markup::create($this->loadTemplate($template_string, NULL)->render($context)); ++ return Markup::create($this->createTemplate($template_string)->render($context)); + } + + } +diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php +index 377efe6..7f2738f 100644 +--- a/core/lib/Drupal/Core/Template/TwigExtension.php ++++ b/core/lib/Drupal/Core/Template/TwigExtension.php +@@ -65,7 +65,7 @@ class TwigExtension extends \Twig_Extension { + * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter + * The date formatter. + */ +- public function __construct(RendererInterface $renderer, UrlGeneratorInterface $url_generator, ThemeManagerInterface $theme_manager, DateFormatterInterface $date_formatter) { ++ public function __construct(RendererInterface $renderer, UrlGeneratorInterface $url_generator = NULL, ThemeManagerInterface $theme_manager = NULL, DateFormatterInterface $date_formatter = NULL) { + $this->renderer = $renderer; + $this->urlGenerator = $url_generator; + $this->themeManager = $theme_manager; +@@ -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'), +@@ -630,4 +630,35 @@ 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. ++ * ++ * @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 264a511..06fc8dd 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([ +- 'count' => $count, +- 'body' => $body, +- 'plural' => $plural, +- 'options' => $options, +- ], [], $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 [new \Twig_Node([new \Twig_Node_Expression_Constant(trim($text), $body->getLine())]), $tokens]; ++ return [new \Twig_Node([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 1ebfa57..f63edee 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([$node->getNode('expr')]), $line), + $line +diff --git a/core/lib/Drupal/Core/Template/TwigTransTokenParser.php b/core/lib/Drupal/Core/Template/TwigTransTokenParser.php +index 96f5560..559319c 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(['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 93be201..9888caa 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 +@@ -21,7 +21,7 @@ class TestExtension extends \Twig_Extension { + */ + public function getFunctions() { + return [ +- 'testfunc' => new \Twig_Function_Function(['Drupal\twig_extension_test\TwigExtension\TestExtension', 'testFunction']), ++ new \Twig_SimpleFunction('testfunc', [$this, 'testFunction']), + ]; + } + +@@ -39,7 +39,7 @@ public function getFunctions() { + */ + public function getFilters() { + return [ +- 'testfilter' => new \Twig_Filter_Function(['Drupal\twig_extension_test\TwigExtension\TestExtension', 'testFilter']), ++ new \Twig_SimpleFilter('testfilter', [$this, 'testFilter']), + ]; + } + +diff --git a/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php b/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php +index 5d6f61f..d93a73d 100644 +--- a/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php ++++ b/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php +@@ -132,12 +132,17 @@ public function testCacheFilename() { + $expected = strlen($prefix) + 2 + 2 * TwigPhpStorageCache::SUFFIX_SUBSTRING_LENGTH; + $this->assertEquals($expected, strlen($key)); + +- $original_filename = $environment->getCacheFilename('core/modules/system/templates/container.html.twig'); ++ $template_path = '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 c9bccce..e5e7e9a 100644 +--- a/core/tests/Drupal/Tests/Core/Template/AttributeTest.php ++++ b/core/tests/Drupal/Tests/Core/Template/AttributeTest.php +@@ -265,10 +265,10 @@ public function testChainAddRemoveClasses() { + * @group legacy + */ + public function testTwigAddRemoveClasses($template, $expected, $seed_attributes = []) { +- $loader = new \Twig_Loader_String(); ++ $loader = new \Twig_Loader_Array([]); + $twig = new \Twig_Environment($loader); + $data = ['attributes' => new Attribute($seed_attributes)]; +- $result = $twig->render($template, $data); ++ $result = $twig->createTemplate($template)->render($data); + $this->assertEquals($expected, $result); + } + +@@ -281,7 +281,7 @@ public function testTwigAddRemoveClasses($template, $expected, $seed_attributes + */ + public function providerTestAttributeClassHelpers() { + return [ +- ["{{ attributes.class }}", ''], ++ ["{{ attributes }}", ''], + ["{{ attributes.addClass('everest').class }}", 'everest'], + ["{{ attributes.addClass(['k2', 'kangchenjunga']).class }}", 'k2 kangchenjunga'], + ["{{ attributes.addClass('lhotse', 'makalu', 'cho-oyu').class }}", 'lhotse makalu cho-oyu'], +diff --git a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php +index a872e30..4e790d8 100644 +--- a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php ++++ b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php +@@ -11,6 +11,19 @@ + use Drupal\Core\Url; + 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. + * +@@ -78,7 +91,8 @@ public function setUp() { + * @group legacy + */ + public function testEscaping($template, $expected) { +- $twig = new \Twig_Environment(NULL, [ ++ $loader = new \Twig_Loader_Filesystem(); ++ $twig = new \Twig_Environment($loader, [ + 'debug' => TRUE, + 'cache' => FALSE, + 'autoescape' => 'html', +@@ -86,7 +100,8 @@ public function testEscaping($template, $expected) { + ]); + $twig->addExtension($this->systemUnderTest); + +- $nodes = $twig->parse($twig->tokenize($template)); ++ $name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), TRUE), FALSE)); ++ $nodes = $twig->parse($twig->tokenize(new \Twig_Source($template, $name))); + + $this->assertSame($expected, $nodes->getNode('body') + ->getNode(0) +@@ -140,10 +155,11 @@ public function testActiveTheme() { + ->method('getActiveTheme') + ->willReturn($active_theme); + +- $loader = new \Twig_Loader_String(); ++ $name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), TRUE), FALSE)); ++ $loader = new \Twig_Loader_Array([$name => '{{ active_theme() }}']); + $twig = new \Twig_Environment($loader); + $twig->addExtension($this->systemUnderTest); +- $result = $twig->render('{{ active_theme() }}'); ++ $result = $twig->render($name); + $this->assertEquals('test_theme', $result); + } + +@@ -177,10 +193,11 @@ public function testActiveThemePath() { + ->method('getActiveTheme') + ->willReturn($active_theme); + +- $loader = new \Twig_Loader_String(); ++ $name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), TRUE), FALSE)); ++ $loader = new \Twig_Loader_Array([$name => '{{ active_theme_path() }}']); + $twig = new \Twig_Environment($loader); + $twig->addExtension($this->systemUnderTest); +- $result = $twig->render('{{ active_theme_path() }}'); ++ $result = $twig->render($name); + $this->assertEquals('foo/bar', $result); + } + +@@ -192,7 +209,8 @@ public function testActiveThemePath() { + * @group legacy + */ + public function testSafeStringEscaping() { +- $twig = new \Twig_Environment(NULL, [ ++ $loader = new \Twig_Loader_Filesystem(); ++ $twig = new \Twig_Environment($loader, [ + 'debug' => TRUE, + 'cache' => FALSE, + 'autoescape' => 'html', +@@ -278,7 +296,8 @@ public function providerTestRenderVar() { + * @group legacy + */ + public function testEscapeWithGeneratedLink() { +- $twig = new \Twig_Environment(NULL, [ ++ $loader = new \Twig_Loader_Filesystem(); ++ $twig = new \Twig_Environment($loader, [ + 'debug' => TRUE, + 'cache' => FALSE, + 'autoescape' => 'html', +@@ -336,7 +355,8 @@ public function testRenderVarWithGeneratedLink() { + * @covers ::createAttribute + */ + public function testCreateAttribute() { +- $loader = new StringLoader(); ++ $name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), TRUE), FALSE)); ++ $loader = new \Twig_Loader_Array([$name => "{% for iteration in iterations %}{% endfor %}"]); + $twig = new \Twig_Environment($loader); + $twig->addExtension($this->systemUnderTest); + +@@ -345,12 +365,15 @@ public function testCreateAttribute() { + ['id' => 'puppies', 'data-value' => 'foo', 'data-lang' => 'en'], + [], + ]; +- $result = $twig->render("{% for iteration in iterations %}{% endfor %}", ['iterations' => $iterations]); ++ $result = $twig->render($name, ['iterations' => $iterations]); + $expected = '
'; + $this->assertEquals($expected, $result); + + // Test default creation of empty attribute object and using its method. +- $result = $twig->render(""); ++ $name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), TRUE), FALSE)); ++ $loader = new \Twig_Loader_Array(array($name => "")); ++ $twig->setLoader($loader); ++ $result = $twig->render($name); + $expected = '
'; + $this->assertEquals($expected, $result); + } diff --git a/core/2817833-38.patch b/core/2817833-38.patch new file mode 100644 index 0000000..84009ad --- /dev/null +++ b/core/2817833-38.patch @@ -0,0 +1,458 @@ +diff --git a/core/2817833-36.patch b/core/2817833-36.patch +new file mode 100644 +index 0000000..a94cc5f +--- /dev/null ++++ b/core/2817833-36.patch +@@ -0,0 +1,226 @@ ++diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php ++index 8bc17f1..b6ca09a 100644 ++--- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +++++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php ++@@ -3,6 +3,7 @@ ++ namespace Drupal\migrate\Plugin\migrate\id_map; ++ ++ use Drupal\Component\Utility\Unicode; +++use Drupal\Core\Database\DatabaseException; ++ use Drupal\Core\Field\BaseFieldDefinition; ++ use Drupal\Core\Plugin\ContainerFactoryPluginInterface; ++ use Drupal\Core\Plugin\PluginBase; ++@@ -161,6 +162,18 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition ++ $this->migration = $migration; ++ $this->eventDispatcher = $event_dispatcher; ++ $this->message = new MigrateMessage(); +++ +++ if (!isset($this->database)) { +++ $this->database = \Drupal::database(); +++ } +++ +++ // Default generated table names, limited to 63 characters. +++ $machine_name = str_replace(':', '__', $this->migration->id()); +++ $prefix_length = strlen($this->database->tablePrefix()); +++ $this->mapTableName = 'migrate_map_' . Unicode::strtolower($machine_name); +++ $this->mapTableName = Unicode::substr($this->mapTableName, 0, 63 - $prefix_length); +++ $this->messageTableName = 'migrate_message_' . Unicode::strtolower($machine_name); +++ $this->messageTableName = Unicode::substr($this->messageTableName, 0, 63 - $prefix_length); ++ } ++ ++ /** ++@@ -246,7 +259,6 @@ protected function destinationIdFields() { ++ * The map table name. ++ */ ++ public function mapTableName() { ++- $this->init(); ++ return $this->mapTableName; ++ } ++ ++@@ -257,7 +269,6 @@ public function mapTableName() { ++ * The message table name. ++ */ ++ public function messageTableName() { ++- $this->init(); ++ return $this->messageTableName; ++ } ++ ++@@ -278,9 +289,6 @@ public function getQualifiedMapTableName() { ++ * The database connection object. ++ */ ++ public function getDatabase() { ++- if (!isset($this->database)) { ++- $this->database = \Drupal::database(); ++- } ++ $this->init(); ++ return $this->database; ++ } ++@@ -291,13 +299,6 @@ public function getDatabase() { ++ protected function init() { ++ if (!$this->initialized) { ++ $this->initialized = TRUE; ++- // Default generated table names, limited to 63 characters. ++- $machine_name = str_replace(':', '__', $this->migration->id()); ++- $prefix_length = strlen($this->getDatabase()->tablePrefix()); ++- $this->mapTableName = 'migrate_map_' . Unicode::strtolower($machine_name); ++- $this->mapTableName = Unicode::substr($this->mapTableName, 0, 63 - $prefix_length); ++- $this->messageTableName = 'migrate_message_' . Unicode::strtolower($machine_name); ++- $this->messageTableName = Unicode::substr($this->messageTableName, 0, 63 - $prefix_length); ++ $this->ensureTables(); ++ } ++ } ++@@ -696,21 +697,17 @@ public function prepareUpdate() { ++ * {@inheritdoc} ++ */ ++ public function processedCount() { ++- return $this->getDatabase()->select($this->mapTableName()) ++- ->countQuery() ++- ->execute() ++- ->fetchField(); +++ return $this->countHelper(NULL, $this->mapTableName()); ++ } ++ ++ /** ++ * {@inheritdoc} ++ */ ++ public function importedCount() { ++- return $this->getDatabase()->select($this->mapTableName()) ++- ->condition('source_row_status', [MigrateIdMapInterface::STATUS_IMPORTED, MigrateIdMapInterface::STATUS_NEEDS_UPDATE], 'IN') ++- ->countQuery() ++- ->execute() ++- ->fetchField(); +++ return $this->countHelper([ +++ MigrateIdMapInterface::STATUS_IMPORTED, +++ MigrateIdMapInterface::STATUS_NEEDS_UPDATE, +++ ]); ++ } ++ ++ /** ++@@ -737,20 +734,28 @@ public function messageCount() { ++ /** ++ * Counts records in a table. ++ * ++- * @param int $status ++- * An integer for the source_row_status column. +++ * @param int|array $status +++ * (optional) Status code(s) to filter the source_row_status column. ++ * @param string $table ++ * (optional) The table to work. Defaults to NULL. ++ * ++ * @return int ++ * The number of records. ++ */ ++- protected function countHelper($status, $table = NULL) { ++- $query = $this->getDatabase()->select($table ?: $this->mapTableName()); +++ protected function countHelper($status = NULL, $table = NULL) { +++ // Use database directly to avoid creating tables. +++ $query = $this->database->select($table ?: $this->mapTableName()); ++ if (isset($status)) { ++- $query->condition('source_row_status', $status); +++ $query->condition('source_row_status', $status, is_array($status) ? 'IN' : '='); +++ } +++ try { +++ $count = $query->countQuery()->execute()->fetchField(); +++ } +++ catch (DatabaseException $e) { +++ // The table does not exist, therefore there are no records. +++ $count = 0; ++ } ++- return $query->countQuery()->execute()->fetchField(); +++ return $count; ++ } ++ ++ /** ++@@ -977,7 +982,7 @@ function (array $id) { ++ $ids = [0]; ++ foreach ($map_tables as $map_table) { ++ if (!$this->getDatabase()->schema()->tableExists($map_table)) { ++- break; +++ continue; ++ } ++ ++ $query = $this->getDatabase()->select($map_table, 'map') ++diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php ++index 2ad2b3d..63649c0 100644 ++--- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php +++++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php ++@@ -1010,4 +1010,23 @@ private function getIdMapContents() { ++ return $contents; ++ } ++ +++ public function testMapTableCreation() { +++ $id_map = $this->getIdMap(); +++ +++ $map_table_name = $id_map->mapTableName(); +++ $message_table_name = $id_map->messageTableName(); +++ $this->assertEquals('migrate_map_sql_idmap_test', $map_table_name); +++ $this->assertEquals('migrate_message_sql_idmap_test', $message_table_name); +++ +++ // Check that tables don't exist. +++ $this->assertFalse($this->database->schema()->tableExists($map_table_name)); +++ $this->assertFalse($this->database->schema()->tableExists($message_table_name)); +++ +++ $id_map->getDatabase(); +++ +++ // Check that tables do exist. +++ $this->assertTrue($this->database->schema()->tableExists($map_table_name)); +++ $this->assertTrue($this->database->schema()->tableExists($message_table_name)); +++ } +++ ++ } ++diff --git a/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6AuditIdsTest.php b/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6AuditIdsTest.php ++index a0bb591..37c6e09 100644 ++--- a/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6AuditIdsTest.php +++++ b/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6AuditIdsTest.php ++@@ -59,8 +59,9 @@ public function testMultipleMigrationWithoutIdConflicts() { ++ ++ // Insert data in the d6_node:page migration mappping table to simulate a ++ // previously migrated node. ++- $table_name = $this->getMigration('d6_node:page')->getIdMap()->mapTableName(); ++- $this->container->get('database')->insert($table_name) +++ $id_map = $this->getMigration('d6_node:page')->getIdMap(); +++ $table_name = $id_map->mapTableName(); +++ $id_map->getDatabase()->insert($table_name) ++ ->fields([ ++ 'source_ids_hash' => 1, ++ 'sourceid1' => 1, ++@@ -156,8 +157,9 @@ public function testDraftRevisionIdConflicts() { ++ ++ // Insert data in the d6_node_revision:page migration mappping table to ++ // simulate a previously migrated node revison. ++- $table_name = $this->getMigration('d6_node_revision:page')->getIdMap()->mapTableName(); ++- $this->container->get('database')->insert($table_name) +++ $id_map = $this->getMigration('d6_node_revision:page')->getIdMap(); +++ $table_name = $id_map->mapTableName(); +++ $id_map->getDatabase()->insert($table_name) ++ ->fields([ ++ 'source_ids_hash' => 1, ++ 'sourceid1' => 1, ++diff --git a/core/modules/migrate_drupal/tests/src/Kernel/d7/MigrateDrupal7AuditIdsTest.php b/core/modules/migrate_drupal/tests/src/Kernel/d7/MigrateDrupal7AuditIdsTest.php ++index 1d74423..aa0354b 100644 ++--- a/core/modules/migrate_drupal/tests/src/Kernel/d7/MigrateDrupal7AuditIdsTest.php +++++ b/core/modules/migrate_drupal/tests/src/Kernel/d7/MigrateDrupal7AuditIdsTest.php ++@@ -59,8 +59,9 @@ public function testMultipleMigrationWithoutIdConflicts() { ++ ++ // Insert data in the d7_node:page migration mappping table to simulate a ++ // previously migrated node. ++- $table_name = $this->getMigration('d7_node:page')->getIdMap()->mapTableName(); ++- $this->container->get('database')->insert($table_name) +++ $id_map = $this->getMigration('d7_node:page')->getIdMap(); +++ $table_name = $id_map->mapTableName(); +++ $id_map->getDatabase()->insert($table_name) ++ ->fields([ ++ 'source_ids_hash' => 1, ++ 'sourceid1' => 1, ++@@ -155,8 +156,9 @@ public function testDraftRevisionIdConflicts() { ++ ++ // Insert data in the d7_node_revision:page migration mappping table to ++ // simulate a previously migrated node revison. ++- $table_name = $this->getMigration('d7_node_revision:page')->getIdMap()->mapTableName(); ++- $this->container->get('database')->insert($table_name) +++ $id_map = $this->getMigration('d7_node_revision:page')->getIdMap(); +++ $table_name = $id_map->mapTableName(); +++ $id_map->getDatabase()->insert($table_name) ++ ->fields([ ++ 'source_ids_hash' => 1, ++ 'sourceid1' => 1, +diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +index 5949b59..ec927f5 100644 +--- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php ++++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +@@ -3,6 +3,7 @@ + namespace Drupal\migrate\Plugin\migrate\id_map; + + use Drupal\Component\Utility\Unicode; ++use Drupal\Core\Database\DatabaseException; + use Drupal\Core\Field\BaseFieldDefinition; + use Drupal\Core\Plugin\ContainerFactoryPluginInterface; + use Drupal\Core\Plugin\PluginBase; +@@ -161,6 +162,18 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition + $this->migration = $migration; + $this->eventDispatcher = $event_dispatcher; + $this->message = new MigrateMessage(); ++ ++ if (!isset($this->database)) { ++ $this->database = \Drupal::database(); ++ } ++ ++ // Default generated table names, limited to 63 characters. ++ $machine_name = str_replace(':', '__', $this->migration->id()); ++ $prefix_length = strlen($this->database->tablePrefix()); ++ $this->mapTableName = 'migrate_map_' . Unicode::strtolower($machine_name); ++ $this->mapTableName = Unicode::substr($this->mapTableName, 0, 63 - $prefix_length); ++ $this->messageTableName = 'migrate_message_' . Unicode::strtolower($machine_name); ++ $this->messageTableName = Unicode::substr($this->messageTableName, 0, 63 - $prefix_length); + } + + /** +@@ -246,7 +259,6 @@ protected function destinationIdFields() { + * The map table name. + */ + public function mapTableName() { +- $this->init(); + return $this->mapTableName; + } + +@@ -257,7 +269,6 @@ public function mapTableName() { + * The message table name. + */ + public function messageTableName() { +- $this->init(); + return $this->messageTableName; + } + +@@ -278,9 +289,6 @@ public function getQualifiedMapTableName() { + * The database connection object. + */ + public function getDatabase() { +- if (!isset($this->database)) { +- $this->database = \Drupal::database(); +- } + $this->init(); + return $this->database; + } +@@ -291,13 +299,6 @@ public function getDatabase() { + protected function init() { + if (!$this->initialized) { + $this->initialized = TRUE; +- // Default generated table names, limited to 63 characters. +- $machine_name = str_replace(':', '__', $this->migration->id()); +- $prefix_length = strlen($this->getDatabase()->tablePrefix()); +- $this->mapTableName = 'migrate_map_' . Unicode::strtolower($machine_name); +- $this->mapTableName = Unicode::substr($this->mapTableName, 0, 63 - $prefix_length); +- $this->messageTableName = 'migrate_message_' . Unicode::strtolower($machine_name); +- $this->messageTableName = Unicode::substr($this->messageTableName, 0, 63 - $prefix_length); + $this->ensureTables(); + } + } +@@ -696,21 +697,17 @@ public function prepareUpdate() { + * {@inheritdoc} + */ + public function processedCount() { +- return (int) $this->getDatabase()->select($this->mapTableName()) +- ->countQuery() +- ->execute() +- ->fetchField(); ++ return $this->countHelper(NULL, $this->mapTableName()); + } + + /** + * {@inheritdoc} + */ + public function importedCount() { +- return (int) $this->getDatabase()->select($this->mapTableName()) +- ->condition('source_row_status', [MigrateIdMapInterface::STATUS_IMPORTED, MigrateIdMapInterface::STATUS_NEEDS_UPDATE], 'IN') +- ->countQuery() +- ->execute() +- ->fetchField(); ++ return $this->countHelper([ ++ MigrateIdMapInterface::STATUS_IMPORTED, ++ MigrateIdMapInterface::STATUS_NEEDS_UPDATE, ++ ]); + } + + /** +@@ -737,20 +734,28 @@ public function messageCount() { + /** + * Counts records in a table. + * +- * @param int $status +- * An integer for the source_row_status column. ++ * @param int|array $status ++ * (optional) Status code(s) to filter the source_row_status column. + * @param string $table + * (optional) The table to work. Defaults to NULL. + * + * @return int + * The number of records. + */ +- protected function countHelper($status, $table = NULL) { +- $query = $this->getDatabase()->select($table ?: $this->mapTableName()); ++ protected function countHelper($status = NULL, $table = NULL) { ++ // Use database directly to avoid creating tables. ++ $query = $this->database->select($table ?: $this->mapTableName()); + if (isset($status)) { +- $query->condition('source_row_status', $status); ++ $query->condition('source_row_status', $status, is_array($status) ? 'IN' : '='); ++ } ++ try { ++ $count = (int) $query->countQuery()->execute()->fetchField(); ++ } ++ catch (DatabaseException $e) { ++ // The table does not exist, therefore there are no records. ++ $count = 0; + } +- return (int) $query->countQuery()->execute()->fetchField(); ++ return $count; + } + + /** +@@ -977,7 +982,7 @@ function (array $id) { + $ids = [0]; + foreach ($map_tables as $map_table) { + if (!$this->getDatabase()->schema()->tableExists($map_table)) { +- break; ++ continue; + } + + $query = $this->getDatabase()->select($map_table, 'map') +diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php +index 80c55ef..18fc5a4 100644 +--- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php ++++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php +@@ -1010,4 +1010,23 @@ private function getIdMapContents() { + return $contents; + } + ++ public function testMapTableCreation() { ++ $id_map = $this->getIdMap(); ++ ++ $map_table_name = $id_map->mapTableName(); ++ $message_table_name = $id_map->messageTableName(); ++ $this->assertEquals('migrate_map_sql_idmap_test', $map_table_name); ++ $this->assertEquals('migrate_message_sql_idmap_test', $message_table_name); ++ ++ // Check that tables don't exist. ++ $this->assertFalse($this->database->schema()->tableExists($map_table_name)); ++ $this->assertFalse($this->database->schema()->tableExists($message_table_name)); ++ ++ $id_map->getDatabase(); ++ ++ // Check that tables do exist. ++ $this->assertTrue($this->database->schema()->tableExists($map_table_name)); ++ $this->assertTrue($this->database->schema()->tableExists($message_table_name)); ++ } ++ + } +diff --git a/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6AuditIdsTest.php b/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6AuditIdsTest.php +index 860f576..bee030e 100644 +--- a/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6AuditIdsTest.php ++++ b/core/modules/migrate_drupal/tests/src/Kernel/d6/MigrateDrupal6AuditIdsTest.php +@@ -60,8 +60,9 @@ public function testMultipleMigrationWithoutIdConflicts() { + + // Insert data in the d6_node:page migration mappping table to simulate a + // previously migrated node. +- $table_name = $this->getMigration('d6_node:page')->getIdMap()->mapTableName(); +- $this->container->get('database')->insert($table_name) ++ $id_map = $this->getMigration('d6_node:page')->getIdMap(); ++ $table_name = $id_map->mapTableName(); ++ $id_map->getDatabase()->insert($table_name) + ->fields([ + 'source_ids_hash' => 1, + 'sourceid1' => 1, +@@ -157,8 +158,9 @@ public function testDraftRevisionIdConflicts() { + + // Insert data in the d6_node_revision:page migration mappping table to + // simulate a previously migrated node revison. +- $table_name = $this->getMigration('d6_node_revision:page')->getIdMap()->mapTableName(); +- $this->container->get('database')->insert($table_name) ++ $id_map = $this->getMigration('d6_node_revision:page')->getIdMap(); ++ $table_name = $id_map->mapTableName(); ++ $id_map->getDatabase()->insert($table_name) + ->fields([ + 'source_ids_hash' => 1, + 'sourceid1' => 1, +diff --git a/core/modules/migrate_drupal/tests/src/Kernel/d7/MigrateDrupal7AuditIdsTest.php b/core/modules/migrate_drupal/tests/src/Kernel/d7/MigrateDrupal7AuditIdsTest.php +index 351ab43..d12445b 100644 +--- a/core/modules/migrate_drupal/tests/src/Kernel/d7/MigrateDrupal7AuditIdsTest.php ++++ b/core/modules/migrate_drupal/tests/src/Kernel/d7/MigrateDrupal7AuditIdsTest.php +@@ -60,8 +60,9 @@ public function testMultipleMigrationWithoutIdConflicts() { + + // Insert data in the d7_node:page migration mappping table to simulate a + // previously migrated node. +- $table_name = $this->getMigration('d7_node:page')->getIdMap()->mapTableName(); +- $this->container->get('database')->insert($table_name) ++ $id_map = $this->getMigration('d7_node:page')->getIdMap(); ++ $table_name = $id_map->mapTableName(); ++ $id_map->getDatabase()->insert($table_name) + ->fields([ + 'source_ids_hash' => 1, + 'sourceid1' => 1, +@@ -156,8 +157,9 @@ public function testDraftRevisionIdConflicts() { + + // Insert data in the d7_node_revision:page migration mappping table to + // simulate a previously migrated node revison. +- $table_name = $this->getMigration('d7_node_revision:page')->getIdMap()->mapTableName(); +- $this->container->get('database')->insert($table_name) ++ $id_map = $this->getMigration('d7_node_revision:page')->getIdMap(); ++ $table_name = $id_map->mapTableName(); ++ $id_map->getDatabase()->insert($table_name) + ->fields([ + 'source_ids_hash' => 1, + 'sourceid1' => 1, diff --git a/core/composer.json b/core/composer.json index 7f20fc7..e9196a6 100644 --- a/core/composer.json +++ b/core/composer.json @@ -18,7 +18,7 @@ "symfony/process": "~3.4.0", "symfony/polyfill-iconv": "^1.0", "symfony/yaml": "~3.4.0", - "twig/twig": "^1.35.0", + "twig/twig": "^1.35.0|^2.4.0", "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 6325b9e..d0590a2 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 21755a5..5271155 100644 --- a/core/lib/Drupal/Core/Template/TwigEnvironment.php +++ b/core/lib/Drupal/Core/Template/TwigEnvironment.php @@ -25,6 +25,8 @@ class TwigEnvironment extends \Twig_Environment { protected $twigCachePrefix = ''; + protected $templateClassPrefix = '__TwigTemplate_'; + /** * Constructs a TwigEnvironment object and stores cache and storage * internally. @@ -42,7 +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 = []) { + public function __construct($root, CacheBackendInterface $cache, $twig_extension_hash, StateInterface $state, \Twig_LoaderInterface $loader = NULL, array $options = []) { // 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'; @@ -57,11 +59,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 +75,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 +110,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]; } @@ -140,7 +140,7 @@ public function getTemplateClass($name, $index = NULL) { public function renderInline($template_string, array $context = []) { // Prefix all inline templates with a special comment. $template_string = '{# inline_template_start #}' . $template_string; - return Markup::create($this->loadTemplate($template_string, NULL)->render($context)); + return Markup::create($this->createTemplate($template_string)->render($context)); } } diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php index 377efe6..7f2738f 100644 --- a/core/lib/Drupal/Core/Template/TwigExtension.php +++ b/core/lib/Drupal/Core/Template/TwigExtension.php @@ -65,7 +65,7 @@ class TwigExtension extends \Twig_Extension { * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter * The date formatter. */ - public function __construct(RendererInterface $renderer, UrlGeneratorInterface $url_generator, ThemeManagerInterface $theme_manager, DateFormatterInterface $date_formatter) { + public function __construct(RendererInterface $renderer, UrlGeneratorInterface $url_generator = NULL, ThemeManagerInterface $theme_manager = NULL, DateFormatterInterface $date_formatter = NULL) { $this->renderer = $renderer; $this->urlGenerator = $url_generator; $this->themeManager = $theme_manager; @@ -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'), @@ -630,4 +630,35 @@ 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. + * + * @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 c0691db..1d308b0 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([ - 'count' => $count, - 'body' => $body, - 'plural' => $plural, - 'options' => $options, - ], [], $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); } diff --git a/core/lib/Drupal/Core/Template/TwigTransTokenParser.php b/core/lib/Drupal/Core/Template/TwigTransTokenParser.php index 96f5560..559319c 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(['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 93be201..9888caa 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 @@ -21,7 +21,7 @@ class TestExtension extends \Twig_Extension { */ public function getFunctions() { return [ - 'testfunc' => new \Twig_Function_Function(['Drupal\twig_extension_test\TwigExtension\TestExtension', 'testFunction']), + new \Twig_SimpleFunction('testfunc', [$this, 'testFunction']), ]; } @@ -39,7 +39,7 @@ public function getFunctions() { */ public function getFilters() { return [ - 'testfilter' => new \Twig_Filter_Function(['Drupal\twig_extension_test\TwigExtension\TestExtension', 'testFilter']), + new \Twig_SimpleFilter('testfilter', [$this, 'testFilter']), ]; } diff --git a/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php b/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php index 5d6f61f..d93a73d 100644 --- a/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php +++ b/core/tests/Drupal/KernelTests/Core/Theme/TwigEnvironmentTest.php @@ -132,12 +132,17 @@ public function testCacheFilename() { $expected = strlen($prefix) + 2 + 2 * TwigPhpStorageCache::SUFFIX_SUBSTRING_LENGTH; $this->assertEquals($expected, strlen($key)); - $original_filename = $environment->getCacheFilename('core/modules/system/templates/container.html.twig'); + $template_path = '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 c9bccce..e5e7e9a 100644 --- a/core/tests/Drupal/Tests/Core/Template/AttributeTest.php +++ b/core/tests/Drupal/Tests/Core/Template/AttributeTest.php @@ -265,10 +265,10 @@ public function testChainAddRemoveClasses() { * @group legacy */ public function testTwigAddRemoveClasses($template, $expected, $seed_attributes = []) { - $loader = new \Twig_Loader_String(); + $loader = new \Twig_Loader_Array([]); $twig = new \Twig_Environment($loader); $data = ['attributes' => new Attribute($seed_attributes)]; - $result = $twig->render($template, $data); + $result = $twig->createTemplate($template)->render($data); $this->assertEquals($expected, $result); } @@ -281,7 +281,7 @@ public function testTwigAddRemoveClasses($template, $expected, $seed_attributes */ public function providerTestAttributeClassHelpers() { return [ - ["{{ attributes.class }}", ''], + ["{{ attributes }}", ''], ["{{ attributes.addClass('everest').class }}", 'everest'], ["{{ attributes.addClass(['k2', 'kangchenjunga']).class }}", 'k2 kangchenjunga'], ["{{ attributes.addClass('lhotse', 'makalu', 'cho-oyu').class }}", 'lhotse makalu cho-oyu'], diff --git a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php index a872e30..4e790d8 100644 --- a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php +++ b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php @@ -11,6 +11,19 @@ use Drupal\Core\Url; 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. * @@ -78,7 +91,8 @@ public function setUp() { * @group legacy */ public function testEscaping($template, $expected) { - $twig = new \Twig_Environment(NULL, [ + $loader = new \Twig_Loader_Filesystem(); + $twig = new \Twig_Environment($loader, [ 'debug' => TRUE, 'cache' => FALSE, 'autoescape' => 'html', @@ -86,7 +100,8 @@ public function testEscaping($template, $expected) { ]); $twig->addExtension($this->systemUnderTest); - $nodes = $twig->parse($twig->tokenize($template)); + $name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), TRUE), FALSE)); + $nodes = $twig->parse($twig->tokenize(new \Twig_Source($template, $name))); $this->assertSame($expected, $nodes->getNode('body') ->getNode(0) @@ -140,10 +155,11 @@ public function testActiveTheme() { ->method('getActiveTheme') ->willReturn($active_theme); - $loader = new \Twig_Loader_String(); + $name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), TRUE), FALSE)); + $loader = new \Twig_Loader_Array([$name => '{{ active_theme() }}']); $twig = new \Twig_Environment($loader); $twig->addExtension($this->systemUnderTest); - $result = $twig->render('{{ active_theme() }}'); + $result = $twig->render($name); $this->assertEquals('test_theme', $result); } @@ -177,10 +193,11 @@ public function testActiveThemePath() { ->method('getActiveTheme') ->willReturn($active_theme); - $loader = new \Twig_Loader_String(); + $name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), TRUE), FALSE)); + $loader = new \Twig_Loader_Array([$name => '{{ active_theme_path() }}']); $twig = new \Twig_Environment($loader); $twig->addExtension($this->systemUnderTest); - $result = $twig->render('{{ active_theme_path() }}'); + $result = $twig->render($name); $this->assertEquals('foo/bar', $result); } @@ -192,7 +209,8 @@ public function testActiveThemePath() { * @group legacy */ public function testSafeStringEscaping() { - $twig = new \Twig_Environment(NULL, [ + $loader = new \Twig_Loader_Filesystem(); + $twig = new \Twig_Environment($loader, [ 'debug' => TRUE, 'cache' => FALSE, 'autoescape' => 'html', @@ -278,7 +296,8 @@ public function providerTestRenderVar() { * @group legacy */ public function testEscapeWithGeneratedLink() { - $twig = new \Twig_Environment(NULL, [ + $loader = new \Twig_Loader_Filesystem(); + $twig = new \Twig_Environment($loader, [ 'debug' => TRUE, 'cache' => FALSE, 'autoescape' => 'html', @@ -336,7 +355,8 @@ public function testRenderVarWithGeneratedLink() { * @covers ::createAttribute */ public function testCreateAttribute() { - $loader = new StringLoader(); + $name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), TRUE), FALSE)); + $loader = new \Twig_Loader_Array([$name => "{% for iteration in iterations %}{% endfor %}"]); $twig = new \Twig_Environment($loader); $twig->addExtension($this->systemUnderTest); @@ -345,12 +365,15 @@ public function testCreateAttribute() { ['id' => 'puppies', 'data-value' => 'foo', 'data-lang' => 'en'], [], ]; - $result = $twig->render("{% for iteration in iterations %}{% endfor %}", ['iterations' => $iterations]); + $result = $twig->render($name, ['iterations' => $iterations]); $expected = '
'; $this->assertEquals($expected, $result); // Test default creation of empty attribute object and using its method. - $result = $twig->render(""); + $name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), TRUE), FALSE)); + $loader = new \Twig_Loader_Array(array($name => "")); + $twig->setLoader($loader); + $result = $twig->render($name); $expected = '
'; $this->assertEquals($expected, $result); }