diff --git a/core/core.services.yml b/core/core.services.yml index 9c3f606..dfa82fa 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1062,11 +1062,22 @@ services: class: Twig_Extension_Debug tags: - { name: twig.extension } - twig.loader: - alias: twig.loader.filesystem + twig.loader.chain: + class: Twig_Loader_Chain twig.loader.filesystem: class: Twig_Loader_Filesystem arguments: ['%app.root%'] + tags: + - { name: twig.loader, priority: 100 } + twig.loader.theme_registry: + class: Drupal\Core\Template\TwigThemeRegistryLoader + arguments: ['@theme.registry'] + tags: + - { name: twig.loader, priority: 0 } + twig.loader.string: + class: Twig_Loader_String + tags: + - { name: twig.loader, priority: -100 } element_info: alias: plugin.manager.element_info file.mime_type.guesser: diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index db54f91..cd2c93f 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -19,6 +19,7 @@ use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass; use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass; use Drupal\Core\DependencyInjection\Compiler\RegisterServicesForDestructionPass; +use Drupal\Core\DependencyInjection\Compiler\TwigLoaderPass; use Drupal\Core\Plugin\PluginManagerPass; use Symfony\Component\DependencyInjection\Compiler\PassConfig; @@ -57,6 +58,7 @@ public function register(ContainerBuilder $container) { // Collect tagged handler services as method calls on consumer services. $container->addCompilerPass(new TaggedHandlersPass()); $container->addCompilerPass(new RegisterStreamWrappersPass()); + $container->addCompilerPass(new TwigLoaderPass()); // Add a compiler pass for registering event subscribers. $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING); diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/TwigLoaderPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/TwigLoaderPass.php new file mode 100644 index 0000000..fee8ed4 --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/TwigLoaderPass.php @@ -0,0 +1,62 @@ +hasDefinition('twig')) { + return; + } + + // Register additional template loaders. + $loaderIds = $container->findTaggedServiceIds('twig.loader'); + + if (count($loaderIds) === 0) { + throw new LogicException('No twig loaders found. You need to tag at least one loader with "twig.loader"'); + } + + if (count($loaderIds) === 1) { + $container->setAlias('twig.loader', key($loaderIds)); + } + else { + $chainLoader = $container->getDefinition('twig.loader.chain'); + $loaders = []; + $priorities = []; + + // Sort the loaders by priority. Lower priority loaders are loaded later. + foreach ($loaderIds as $id => $attributes) { + foreach ($attributes as $attribute) { + $priorities[$id] = isset($attribute['priority']) ? $attribute['priority'] : 0; + } + $loaders[$id] = $container->getDefinition($id); + } + array_multisort($priorities, SORT_DESC, $loaders); + + foreach ($loaders as $id => $loader) { + $chainLoader->addMethodCall('addLoader', array(new Reference($id))); + } + $container->setAlias('twig.loader', 'twig.loader.chain'); + } + } + +} diff --git a/core/lib/Drupal/Core/Template/TwigEnvironment.php b/core/lib/Drupal/Core/Template/TwigEnvironment.php index 97341ac..184272d 100644 --- a/core/lib/Drupal/Core/Template/TwigEnvironment.php +++ b/core/lib/Drupal/Core/Template/TwigEnvironment.php @@ -54,7 +54,9 @@ public function __construct(\Twig_LoaderInterface $loader = NULL, ModuleHandlerI foreach ($namespaces as $name => $path) { $templatesDirectory = $path . '/templates'; if (file_exists($templatesDirectory)) { - $loader->addPath($templatesDirectory, $name); + // @todo Inject this? Might be weird injecting two TwigLoaderInterface? + // Or solve this another way. + \Drupal::service('twig.loader.filesystem')->addPath($templatesDirectory, $name); } } @@ -69,7 +71,7 @@ public function __construct(\Twig_LoaderInterface $loader = NULL, ModuleHandlerI // Ensure autoescaping is always on. $options['autoescape'] = TRUE; - $this->loader = new \Twig_Loader_Chain([$loader, new \Twig_Loader_String()]); + $this->loader = $loader; parent::__construct($this->loader, $options); } diff --git a/core/lib/Drupal/Core/Template/TwigThemeRegistryLoader.php b/core/lib/Drupal/Core/Template/TwigThemeRegistryLoader.php new file mode 100644 index 0000000..86c0c9a --- /dev/null +++ b/core/lib/Drupal/Core/Template/TwigThemeRegistryLoader.php @@ -0,0 +1,69 @@ +themeRegistry = $theme_registry; + } + + /** + * Finds the path to the requested template. + * + * @param string $name + * The name of the template to load. + * + * @return string + * The path to the template. + * + * @throws Twig_Error_Loader + * Thrown if a template matching $name cannot be found. + */ + protected function findTemplate($name) { + // Allow for loading based on the Drupal theme registry. + $hook = str_replace('.html.twig', '', strtr($name, '-', '_')); + $theme_registry = $this->themeRegistry->getRuntime(); + + if ($theme_registry->has($hook)) { + $info = $theme_registry->get($hook); + if (isset($info['path'])) { + $path = $info['path'] . '/' . $name; + } + elseif (isset($info['template'])) { + $path = $info['template'] . '.html.twig'; + } + if (isset($path) && is_file($path)) { + return $this->cache[$name] = $path; + } + } + + throw new \Twig_Error_Loader(sprintf('Unable to find template "%s" in the Drupal theme registry.', $name)); + } + +} diff --git a/core/modules/system/src/Tests/Theme/TwigLoaderTest.php b/core/modules/system/src/Tests/Theme/TwigLoaderTest.php new file mode 100644 index 0000000..91de1e4 --- /dev/null +++ b/core/modules/system/src/Tests/Theme/TwigLoaderTest.php @@ -0,0 +1,39 @@ +loadTemplate('kittens'); + $this->assertEqual($template->render(array()), 'kittens', 'Passing "kittens" to the custom Twig loader returns "kittens".'); + + $template = $environment->loadTemplate('meow'); + $this->assertEqual($template->render(array()), 'cats', 'Passing something other than "kittens" to the custom Twig loader returns "cats".'); + } + +} diff --git a/core/modules/system/src/Tests/Theme/TwigRegistryLoaderTest.php b/core/modules/system/src/Tests/Theme/TwigRegistryLoaderTest.php new file mode 100644 index 0000000..431f2c1 --- /dev/null +++ b/core/modules/system/src/Tests/Theme/TwigRegistryLoaderTest.php @@ -0,0 +1,70 @@ +install(array('test_theme_twig_registry_loader')); + $this->twig = \Drupal::service('twig'); + } + + /** + * Checks to see if a value is a twig template. + */ + public function assertTwigTemplate($value, $message = '', $group = 'Other') { + $this->assertTrue($value instanceof \Twig_Template, $message, $group); + } + + /** + * Tests template discovery using the Drupal theme registry. + */ + public function testTemplateDiscovery() { + $this->assertTwigTemplate($this->twig->resolveTemplate('block.html.twig'), 'Found block.html.twig in block module.'); + } + + /** + * Tests template extension and includes using the Drupal theme registry. + */ + public function testTwigNamespaces() { + // Test the module-provided extend and insert templates. + $this->drupalGet('twig-theme-test/registry-loader'); + $this->assertText('This line is from twig_theme_test/templates/twig_registry_loader_test_extend.html.twig'); + $this->assertText('This line is from twig_theme_test/templates/twig_registry_loader_test_include.html.twig'); + + // Enable a theme that overrides the extend and insert templates to ensure + // they are picked up by the registry loader. + \Drupal::config('system.theme') + ->set('default', 'test_theme_twig_registry_loader') + ->save(); + $this->drupalGet('twig-theme-test/registry-loader'); + $this->assertText('This line is from test_theme_twig_registry_loader/templates/twig_registry_loader_test_extend.html.twig'); + $this->assertText('This line is from test_theme_twig_registry_loader/templates/twig_registry_loader_test_include.html.twig'); + } + +} diff --git a/core/modules/system/templates/block--system-branding-block.html.twig b/core/modules/system/templates/block--system-branding-block.html.twig index 4cf0f1a..f648773 100644 --- a/core/modules/system/templates/block--system-branding-block.html.twig +++ b/core/modules/system/templates/block--system-branding-block.html.twig @@ -1,4 +1,4 @@ -{% extends "@block/block.html.twig" %} +{% extends "block.html.twig" %} {# /** * @file diff --git a/core/modules/system/tests/modules/twig_loader_test/src/Loader/TestLoader.php b/core/modules/system/tests/modules/twig_loader_test/src/Loader/TestLoader.php new file mode 100644 index 0000000..9f32969 --- /dev/null +++ b/core/modules/system/tests/modules/twig_loader_test/src/Loader/TestLoader.php @@ -0,0 +1,50 @@ + 'twig_registry_loader_test'); + } + } diff --git a/core/modules/system/tests/modules/twig_theme_test/templates/twig_registry_loader_test.html.twig b/core/modules/system/tests/modules/twig_theme_test/templates/twig_registry_loader_test.html.twig new file mode 100644 index 0000000..8776f5e --- /dev/null +++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_registry_loader_test.html.twig @@ -0,0 +1,5 @@ +{% extends "twig_registry_loader_test_extend.html.twig" %} + +{% block content %} + {% include "twig_registry_loader_test_include.html.twig" %} +{% endblock %} diff --git a/core/modules/system/tests/modules/twig_theme_test/templates/twig_registry_loader_test_extend.html.twig b/core/modules/system/tests/modules/twig_theme_test/templates/twig_registry_loader_test_extend.html.twig new file mode 100644 index 0000000..a5a5af1 --- /dev/null +++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_registry_loader_test_extend.html.twig @@ -0,0 +1,5 @@ +This line is from twig_theme_test/templates/twig_registry_loader_test_extend.html.twig + +{% block content %} +This text is in a block. +{% endblock %} diff --git a/core/modules/system/tests/modules/twig_theme_test/templates/twig_registry_loader_test_include.html.twig b/core/modules/system/tests/modules/twig_theme_test/templates/twig_registry_loader_test_include.html.twig new file mode 100644 index 0000000..bb8c056 --- /dev/null +++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_registry_loader_test_include.html.twig @@ -0,0 +1 @@ +This line is from twig_theme_test/templates/twig_registry_loader_test_include.html.twig diff --git a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module index b754ffa..ba850f7 100644 --- a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module +++ b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module @@ -19,6 +19,18 @@ function twig_theme_test_theme($existing, $type, $theme, $path) { 'variables' => array(), 'template' => 'twig_namespace_test', ); + $items['twig_registry_loader_test'] = array( + 'variables' => array(), + 'template' => 'twig_registry_loader_test', + ); + $items['twig_registry_loader_test_include'] = array( + 'variables' => array(), + 'template' => 'twig_registry_loader_test_include', + ); + $items['twig_registry_loader_test_extend'] = array( + 'variables' => array(), + 'template' => 'twig_registry_loader_test_extend', + ); $items['twig_raw_test'] = array( 'variables' => array('script' => ''), ); diff --git a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml index d631898..d7c2f39 100644 --- a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml +++ b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml @@ -25,3 +25,10 @@ twig_theme_test_link_generator: _content: '\Drupal\twig_theme_test\TwigThemeTestController::linkGeneratorRender' requirements: _access: 'TRUE' + +twig_theme_test.registry_loader: + path: '/twig-theme-test/registry-loader' + defaults: + _content: '\Drupal\twig_theme_test\TwigThemeTestController::registryLoaderRender' + requirements: + _access: 'TRUE' diff --git a/core/modules/system/tests/themes/test_theme_twig_registry_loader/templates/twig_registry_loader_test_extend.html.twig b/core/modules/system/tests/themes/test_theme_twig_registry_loader/templates/twig_registry_loader_test_extend.html.twig new file mode 100644 index 0000000..7d5cb0e --- /dev/null +++ b/core/modules/system/tests/themes/test_theme_twig_registry_loader/templates/twig_registry_loader_test_extend.html.twig @@ -0,0 +1,5 @@ +This line is from test_theme_twig_registry_loader/templates/twig_registry_loader_test_extend.html.twig + +{% block content %} +This text is in a block. +{% endblock %} diff --git a/core/modules/system/tests/themes/test_theme_twig_registry_loader/templates/twig_registry_loader_test_include.html.twig b/core/modules/system/tests/themes/test_theme_twig_registry_loader/templates/twig_registry_loader_test_include.html.twig new file mode 100644 index 0000000..7097adf --- /dev/null +++ b/core/modules/system/tests/themes/test_theme_twig_registry_loader/templates/twig_registry_loader_test_include.html.twig @@ -0,0 +1 @@ +This line is from test_theme_twig_registry_loader/templates/twig_registry_loader_test_include.html.twig diff --git a/core/modules/system/tests/themes/test_theme_twig_registry_loader/test_theme_twig_registry_loader.info.yml b/core/modules/system/tests/themes/test_theme_twig_registry_loader/test_theme_twig_registry_loader.info.yml new file mode 100644 index 0000000..9ee01c5 --- /dev/null +++ b/core/modules/system/tests/themes/test_theme_twig_registry_loader/test_theme_twig_registry_loader.info.yml @@ -0,0 +1,5 @@ +name: 'Twig registry loader test' +type: theme +description: 'Support module for Twig registry loader testing.' +version: VERSION +core: 8.x diff --git a/core/themes/bartik/templates/block--system-branding-block.html.twig b/core/themes/bartik/templates/block--system-branding-block.html.twig index f6147a6..4a4b733 100644 --- a/core/themes/bartik/templates/block--system-branding-block.html.twig +++ b/core/themes/bartik/templates/block--system-branding-block.html.twig @@ -1,4 +1,4 @@ -{% extends "@block/block.html.twig" %} +{% extends "block.html.twig" %} {# /** * @file