diff --git a/core/core.services.yml b/core/core.services.yml index 73c7873..41b8afd 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1119,7 +1119,7 @@ services: class: Drupal\Core\Extension\InfoParser twig: class: Drupal\Core\Template\TwigEnvironment - arguments: ['@app.root', '@twig.loader', '@module_handler', '@theme_handler', '%twig.config%'] + arguments: ['@app.root', '@twig.loader', '%twig.config%'] tags: - { name: service_collector, tag: 'twig.extension', call: addExtension } twig.extension: @@ -1136,10 +1136,24 @@ services: tags: - { name: twig.extension } twig.loader: - alias: twig.loader.filesystem + class: Twig_Loader_Chain + public: false + tags: + - { name: service_collector, tag: twig.loader, call: addLoader, required: TRUE } twig.loader.filesystem: - class: Twig_Loader_Filesystem - arguments: ['@app.root'] + class: Drupal\Core\Template\Loader\FilesystemLoader + arguments: ['@app.root', '@module_handler', '@theme_handler'] + tags: + - { name: twig.loader, priority: 100 } + twig.loader.theme_registry: + class: Drupal\Core\Template\Loader\ThemeRegistryLoader + 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/DependencyInjection/Compiler/TaggedHandlersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php index 18927ca..fb733ba 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php @@ -53,6 +53,8 @@ class TaggedHandlersPass implements CompilerPassInterface { * - Optionally the handler's priority as second argument, if the method * accepts a second parameter and its name is "priority". In any case, all * handlers registered at compile time are sorted already. + * - required: Boolean indicating if at least one handler service is required. + * Defaults to FALSE. * * Example (YAML): * @code @@ -74,12 +76,15 @@ class TaggedHandlersPass implements CompilerPassInterface { * interface. * @throws \Symfony\Component\DependencyInjection\Exception\LogicException * If a tagged handler does not implement the required interface. + * @throws \Symfony\Component\DependencyInjection\Exception\LogicException + * If at least one tagged service is required but none are found. */ public function process(ContainerBuilder $container) { foreach ($container->findTaggedServiceIds('service_collector') as $consumer_id => $passes) { foreach ($passes as $pass) { $tag = isset($pass['tag']) ? $pass['tag'] : $consumer_id; $method_name = isset($pass['call']) ? $pass['call'] : 'addHandler'; + $required = isset($pass['required']) ? $pass['required'] : FALSE; // Determine parameters. $consumer = $container->getDefinition($consumer_id); @@ -122,6 +127,9 @@ public function process(ContainerBuilder $container) { $handlers[$id] = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; } if (empty($handlers)) { + if ($required) { + throw new LogicException(sprintf("At least one service tagged with '%s' is required.", $tag)); + } continue; } // Sort all handlers by priority. diff --git a/core/lib/Drupal/Core/Template/Loader/FilesystemLoader.php b/core/lib/Drupal/Core/Template/Loader/FilesystemLoader.php new file mode 100644 index 0000000..f909809 --- /dev/null +++ b/core/lib/Drupal/Core/Template/Loader/FilesystemLoader.php @@ -0,0 +1,52 @@ +getModuleList() as $name => $extension) { + $namespaces[$name] = $extension->getPath(); + } + foreach ($theme_handler->listInfo() as $name => $extension) { + $namespaces[$name] = $extension->getPath(); + } + + foreach ($namespaces as $name => $path) { + $templatesDirectory = $path . '/templates'; + if (file_exists($templatesDirectory)) { + $this->addPath($templatesDirectory, $name); + } + } + } + +} diff --git a/core/lib/Drupal/Core/Template/Loader/ThemeRegistryLoader.php b/core/lib/Drupal/Core/Template/Loader/ThemeRegistryLoader.php new file mode 100644 index 0000000..fe1ae25 --- /dev/null +++ b/core/lib/Drupal/Core/Template/Loader/ThemeRegistryLoader.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/lib/Drupal/Core/Template/TwigEnvironment.php b/core/lib/Drupal/Core/Template/TwigEnvironment.php index 3716277..e09d9f6 100644 --- a/core/lib/Drupal/Core/Template/TwigEnvironment.php +++ b/core/lib/Drupal/Core/Template/TwigEnvironment.php @@ -8,8 +8,6 @@ namespace Drupal\Core\Template; use Drupal\Core\PhpStorage\PhpStorageFactory; -use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\Core\Extension\ThemeHandlerInterface; /** * A class that defines a Twig environment for Drupal. @@ -35,9 +33,13 @@ class TwigEnvironment extends \Twig_Environment { * internally. * * @param string $root - * The app root; + * The app root. + * @param \Twig_LoaderInterface $loader + * The Twig loader or loader chain. + * @param array $options + * The options for the Twig environment. */ - public function __construct($root, \Twig_LoaderInterface $loader = NULL, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, $options = array()) { + public function __construct($root, \Twig_LoaderInterface $loader = NULL, $options = array()) { // @todo Pass as arguments from the DIC. $this->cache_object = \Drupal::cache(); @@ -45,22 +47,6 @@ public function __construct($root, \Twig_LoaderInterface $loader = NULL, ModuleH // template because functions like twig_drupal_escape_filter are called. require_once $root . '/core/themes/engines/twig/twig.engine'; - // Set twig path namespace for themes and modules. - $namespaces = array(); - foreach ($module_handler->getModuleList() as $name => $extension) { - $namespaces[$name] = $extension->getPath(); - } - foreach ($theme_handler->listInfo() as $name => $extension) { - $namespaces[$name] = $extension->getPath(); - } - - foreach ($namespaces as $name => $path) { - $templatesDirectory = $path . '/templates'; - if (file_exists($templatesDirectory)) { - $loader->addPath($templatesDirectory, $name); - } - } - $this->templateClasses = array(); $options += array( @@ -72,7 +58,7 @@ public function __construct($root, \Twig_LoaderInterface $loader = NULL, ModuleH // 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/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..589632b --- /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. + $this->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-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..e18e68e --- /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..438fd2b --- /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/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..a3723b5 --- /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/twig_theme_test.module b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module index 4a415cd..e42b020 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,15 @@ function twig_theme_test_theme($existing, $type, $theme, $path) { 'variables' => array(), 'template' => 'twig_namespace_test', ); + $items['twig_registry_loader_test'] = array( + 'variables' => array(), + ); + $items['twig_registry_loader_test_include'] = array( + 'variables' => array(), + ); + $items['twig_registry_loader_test_extend'] = array( + 'variables' => array(), + ); $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 d2abf91..4d30bb9 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 @@ -32,3 +32,10 @@ twig_theme_test_file_url: _controller: '\Drupal\twig_theme_test\TwigThemeTestController::fileUrlRender' requirements: _access: 'TRUE' + +twig_theme_test_registry_loader: + path: '/twig-theme-test/registry-loader' + defaults: + _controller: '\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..e1a9eee --- /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..3a1f395 --- /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/modules/text/templates/field--text-long.html.twig b/core/modules/text/templates/field--text-long.html.twig index 5f201f0..07ce721 100644 --- a/core/modules/text/templates/field--text-long.html.twig +++ b/core/modules/text/templates/field--text-long.html.twig @@ -1 +1 @@ -{% extends "@text/field--text.html.twig" %} +{% extends "field--text.html.twig" %} diff --git a/core/modules/text/templates/field--text-with-summary.html.twig b/core/modules/text/templates/field--text-with-summary.html.twig index 5f201f0..07ce721 100644 --- a/core/modules/text/templates/field--text-with-summary.html.twig +++ b/core/modules/text/templates/field--text-with-summary.html.twig @@ -1 +1 @@ -{% extends "@text/field--text.html.twig" %} +{% extends "field--text.html.twig" %} diff --git a/core/modules/text/templates/field--text.html.twig b/core/modules/text/templates/field--text.html.twig index 6a63a0c..9cd1ab9 100644 --- a/core/modules/text/templates/field--text.html.twig +++ b/core/modules/text/templates/field--text.html.twig @@ -1,4 +1,4 @@ -{% extends "@system/field.html.twig" %} +{% extends "field.html.twig" %} {# /** * @file diff --git a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php index dd03a1f..db6f39f 100644 --- a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php @@ -42,6 +42,25 @@ public function testProcessNoConsumers() { } /** + * Tests a required consumer with no handlers. + * + * @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException + * @expectedExceptionMessage At least one service tagged with 'consumer_id' is required. + * @covers ::process + */ + public function testProcessRequiredHandlers() { + $container = $this->buildContainer(); + $container + ->register('consumer_id', __NAMESPACE__ . '\ValidConsumer') + ->addTag('service_collector', array( + 'required' => TRUE, + )); + + $handler_pass = new TaggedHandlersPass(); + $handler_pass->process($container); + } + + /** * Tests consumer with missing interface in non-production environment. * * @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException 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