diff --git a/core/composer.json b/core/composer.json index dd5e958..c80c93c 100644 --- a/core/composer.json +++ b/core/composer.json @@ -17,7 +17,7 @@ "symfony/validator": "2.7.*", "symfony/process": "2.7.*", "symfony/yaml": "2.7.*", - "twig/twig": "^1.23.1", + "twig/twig": "2.0.x-dev", "doctrine/common": "2.5.*", "doctrine/annotations": "1.2.*", "guzzlehttp/guzzle": "~6.1", diff --git a/core/lib/Drupal/Core/Template/TwigEnvironment.php b/core/lib/Drupal/Core/Template/TwigEnvironment.php index 3a3d002..58deff1 100644 --- a/core/lib/Drupal/Core/Template/TwigEnvironment.php +++ b/core/lib/Drupal/Core/Template/TwigEnvironment.php @@ -27,6 +27,8 @@ class TwigEnvironment extends \Twig_Environment { */ protected $templateClasses; + protected $templateClassPrefix = '__TwigTemplate_'; + /** * Constructs a TwigEnvironment object and stores cache and storage * internally. @@ -43,10 +45,6 @@ class TwigEnvironment extends \Twig_Environment { * The options for the Twig environment. */ public function __construct($root, CacheBackendInterface $cache, $twig_extension_hash, \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'; - $this->templateClasses = array(); $options += array( @@ -66,8 +64,8 @@ public function __construct($root, CacheBackendInterface $cache, $twig_extension $options['cache'] = new TwigPhpStorageCache($cache, $twig_extension_hash); } - $this->loader = $loader; - parent::__construct($this->loader, $options); + $this->setLoader($loader); + parent::__construct($this->getLoader(), $options); } /** @@ -88,7 +86,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 0632db5..676641b 100644 --- a/core/lib/Drupal/Core/Template/TwigExtension.php +++ b/core/lib/Drupal/Core/Template/TwigExtension.php @@ -168,7 +168,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'), @@ -557,4 +557,37 @@ public function safeJoin(\Twig_Environment $env, $value, $glue = '') { }, (array) $value)); } + /** + * 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/modules/system/src/Tests/Theme/TwigEnvironmentTest.php b/core/modules/system/src/Tests/Theme/TwigEnvironmentTest.php index 0bc52d0..f818637 100644 --- a/core/modules/system/src/Tests/Theme/TwigEnvironmentTest.php +++ b/core/modules/system/src/Tests/Theme/TwigEnvironmentTest.php @@ -120,13 +120,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/modules/system/src/Tests/Theme/TwigSettingsTest.php b/core/modules/system/src/Tests/Theme/TwigSettingsTest.php index f581fa62..74a517b 100644 --- a/core/modules/system/src/Tests/Theme/TwigSettingsTest.php +++ b/core/modules/system/src/Tests/Theme/TwigSettingsTest.php @@ -101,7 +101,11 @@ function testTwigCacheOverride() { // theme_test.template_test.html.twig. $info = $templates->get('theme_test_template_test'); $template_filename = $info['path'] . '/' . $info['template'] . $extension; - $cache_filename = $this->container->get('twig')->getCacheFilename($template_filename); + + $environment = $this->container->get('twig'); + $cache = $environment->getCache(); + $class = $environment->getTemplateClass($template_filename); + $cache_filename = $cache->generateKey($template_filename, $class); // Navigate to the page and make sure the template gets cached. $this->drupalGet('theme-test/template-test'); 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 3c2b851..5383fab 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 @@ -7,12 +7,10 @@ namespace Drupal\twig_extension_test\TwigExtension; -use Drupal\Core\Template\TwigExtension; - /** * A test Twig extension that adds a custom function and a custom filter. */ -class TestExtension extends TwigExtension { +class TestExtension extends \TwigExtension { /** * Generates a list of all Twig functions that this extension defines. @@ -27,9 +25,9 @@ class TestExtension extends TwigExtension { * 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']), + ]; } /** @@ -45,9 +43,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 491d1e8..8784c0f 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/Tests/Core/Template/AttributeTest.php b/core/tests/Drupal/Tests/Core/Template/AttributeTest.php index 69941a9..3f7a5f1 100644 --- a/core/tests/Drupal/Tests/Core/Template/AttributeTest.php +++ b/core/tests/Drupal/Tests/Core/Template/AttributeTest.php @@ -12,6 +12,7 @@ use Drupal\Core\Template\Attribute; use Drupal\Core\Template\AttributeArray; use Drupal\Core\Template\AttributeString; +use Drupal\Core\Template\Loader\StringLoader; use Drupal\Tests\UnitTestCase; use Drupal\Component\Render\MarkupInterface; @@ -268,7 +269,7 @@ public function testChainAddRemoveClasses() { * @covers ::addClass */ public function testTwigAddRemoveClasses($template, $expected, $seed_attributes = array()) { - $loader = new \Twig_Loader_String(); + $loader = new StringLoader(); $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 cb09022..d8adbc7 100644 --- a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php +++ b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php @@ -5,7 +5,7 @@ * Contains \Drupal\Tests\Core\Template\TwigExtensionTest. */ -namespace Drupal\Tests\Core\Template; +namespace Drupal\Tests\Core\Template { use Drupal\Core\Render\RenderableInterface; use Drupal\Core\Render\RendererInterface; @@ -31,11 +31,12 @@ class TwigExtensionTest extends UnitTestCase { */ public function testEscaping($template, $expected) { $renderer = $this->getMock('\Drupal\Core\Render\RendererInterface'); - $twig = new \Twig_Environment(NULL, array( + $loader = new \Twig_Loader_Filesystem(); + $twig = new \Twig_Environment($loader, array( 'debug' => TRUE, 'cache' => FALSE, 'autoescape' => 'html', - 'optimizations' => 0 + 'optimizations' => 0, )); $twig->addExtension((new TwigExtension($renderer))->setUrlGenerator($this->getMock('Drupal\Core\Routing\UrlGeneratorInterface'))); @@ -97,7 +98,7 @@ public function testActiveTheme() { ->willReturn($active_theme); $extension->setThemeManager($theme_manager); - $loader = new \Twig_Loader_String(); + $loader = new StringLoader(); $twig = new \Twig_Environment($loader); $twig->addExtension($extension); $result = $twig->render('{{ active_theme() }}'); @@ -145,7 +146,7 @@ public function testActiveThemePath() { ->willReturn($active_theme); $extension->setThemeManager($theme_manager); - $loader = new \Twig_Loader_String(); + $loader = new StringLoader(); $twig = new \Twig_Environment($loader); $twig->addExtension($extension); $result = $twig->render('{{ active_theme_path() }}'); @@ -159,11 +160,12 @@ public function testActiveThemePath() { */ public function testSafeStringEscaping() { $renderer = $this->getMock('\Drupal\Core\Render\RendererInterface'); - $twig = new \Twig_Environment(NULL, array( + $loader = new \Twig_Loader_Filesystem(); + $twig = new \Twig_Environment($loader, array( 'debug' => TRUE, 'cache' => FALSE, 'autoescape' => 'html', - 'optimizations' => 0 + 'optimizations' => 0, )); $twig_extension = new TwigExtension($renderer); @@ -242,6 +244,7 @@ public function providerTestRenderVar() { } + class TwigExtensionTestString { protected $string; @@ -255,3 +258,15 @@ public function __toString() { } } +} + +namespace { + if (!function_exists('t')) { + function t($string, array $args = []) { + return strtr($string, $args); + } + } + if (!function_exists('file_create_url')) { + function file_create_url() {} + } +} diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine index f4e18f5..df1baaa 100644 --- a/core/themes/engines/twig/twig.engine +++ b/core/themes/engines/twig/twig.engine @@ -121,35 +121,3 @@ function twig_render_template($template_file, array $variables) { return Markup::create(implode('', $output)); } -/** - * 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. - */ -function twig_without($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; -}