Commit 4cd30ead authored by catch's avatar catch

Issue #2414255 by lauriii, Jeff Burnz, dawehner, davidhernandez: Subtheme...

Issue #2414255 by lauriii, Jeff Burnz, dawehner, davidhernandez: Subtheme template inheritance working in reverse order
parent 9cf83e9c
......@@ -1071,7 +1071,7 @@ services:
arguments: ['@app.root', '@theme_handler', '@state']
theme.registry:
class: Drupal\Core\Theme\Registry
arguments: ['@app.root', '@cache.default', '@lock', '@module_handler', '@theme_handler']
arguments: ['@app.root', '@cache.default', '@lock', '@module_handler', '@theme_handler', '@theme.initialization']
tags:
- { name: needs_destruction }
authentication:
......
......@@ -175,6 +175,9 @@ public function getStyleSheetsRemove() {
/**
* Returns an array of base theme active theme objects keyed by name.
*
* The order starts with the base theme of $this and ends with the root of
* the dependency chain.
*
* @return static[]
*/
public function getBaseThemes() {
......
......@@ -25,24 +25,10 @@ class Registry implements DestructableInterface {
/**
* The theme object representing the active theme for this registry.
*
* @var object
* @var \Drupal\Core\Theme\ActiveTheme
*/
protected $theme;
/**
* An array of base theme objects.
*
* @var array
*/
protected $baseThemes = array();
/**
* The name of the theme engine of $theme.
*
* @var string
*/
protected $engine;
/**
* The lock backend that should be used.
*
......@@ -157,16 +143,19 @@ class Registry implements DestructableInterface {
* The module handler to use to load modules.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
* @param \Drupal\Core\Theme\ThemeInitializationInterface $theme_initialization
* The theme initialization.
* @param string $theme_name
* (optional) The name of the theme for which to construct the registry.
*/
public function __construct($root, CacheBackendInterface $cache, LockBackendInterface $lock, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, $theme_name = NULL) {
public function __construct($root, CacheBackendInterface $cache, LockBackendInterface $lock, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, ThemeInitializationInterface $theme_initialization, $theme_name = NULL) {
$this->root = $root;
$this->cache = $cache;
$this->lock = $lock;
$this->moduleHandler = $module_handler;
$this->themeName = $theme_name;
$this->themeHandler = $theme_handler;
$this->themeInitialization = $theme_initialization;
}
/**
......@@ -184,38 +173,12 @@ protected function init($theme_name = NULL) {
}
// Unless instantiated for a specific theme, use globals.
if (!isset($theme_name)) {
$active_theme = \Drupal::theme()->getActiveTheme();
$this->theme = $active_theme;
$this->baseThemes = $active_theme->getBaseThemes();
$this->engine = $active_theme->getEngine();
$this->theme = \Drupal::theme()->getActiveTheme();
}
// Instead of the active theme, a specific theme was requested.
else {
$themes = $this->themeHandler->listInfo();
$this->theme = $themes[$theme_name];
// Find all base themes.
$this->baseThemes = array();
$ancestor = $theme_name;
while ($ancestor && isset($themes[$ancestor]->base_theme)) {
$ancestor = $themes[$ancestor]->base_theme;
$this->baseThemes[] = $themes[$ancestor];
if (!empty($themes[$ancestor]->owner)) {
include_once $this->root . '/' . $themes[$ancestor]->owner;
}
}
$this->baseThemes = array_reverse($this->baseThemes);
if (isset($this->theme->engine)) {
$this->engine = $this->theme->engine;
include_once $this->root . '/' . $this->theme->owner;
if (function_exists($this->theme->engine . '_init')) {
foreach ($this->baseThemes as $base) {
call_user_func($this->theme->engine . '_init', $base);
}
call_user_func($this->theme->engine . '_init', $this->theme);
}
}
$this->theme = $this->themeInitialization->getActiveThemeByName($theme_name);
$this->themeInitialization->loadActiveTheme($this->theme);
}
}
......@@ -344,18 +307,20 @@ protected function build() {
}
// Process each base theme.
foreach ($this->baseThemes as $base) {
// Ensure that we start with the root of the parents, so that both CSS files
// and preprocess functions comes first.
foreach (array_reverse($this->theme->getBaseThemes()) as $base) {
// If the base theme uses a theme engine, process its hooks.
$base_path = $base->getPath();
if ($this->engine) {
$this->processExtension($cache, $this->engine, 'base_theme_engine', $base->getName(), $base_path);
if ($this->theme->getEngine()) {
$this->processExtension($cache, $this->theme->getEngine(), 'base_theme_engine', $base->getName(), $base_path);
}
$this->processExtension($cache, $base->getName(), 'base_theme', $base->getName(), $base_path);
}
// And then the same thing, but for the theme.
if ($this->engine) {
$this->processExtension($cache, $this->engine, 'theme_engine', $this->theme->getName(), $this->theme->getPath());
if ($this->theme->getEngine()) {
$this->processExtension($cache, $this->theme->getEngine(), 'theme_engine', $this->theme->getName(), $this->theme->getPath());
}
// Finally, hooks provided by the theme itself.
......
......@@ -7,7 +7,8 @@
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\Theme\Registry;
use Drupal\simpletest\KernelTestBase;
use Drupal\Core\Utility\ThemeRegistry;
/**
......@@ -15,7 +16,7 @@
*
* @group Theme
*/
class RegistryTest extends WebTestBase {
class RegistryTest extends KernelTestBase {
/**
* Modules to enable.
......@@ -62,4 +63,37 @@ function testRaceCondition() {
$this->assertTrue($registry->get('theme_test_template_test'), 'Offset was returned correctly from the theme registry');
$this->assertTrue($registry->get('theme_test_template_test_2'), 'Offset was returned correctly from the theme registry');
}
/**
* Tests the theme registry with multiple subthemes.
*/
public function testMultipleSubThemes() {
$theme_handler = \Drupal::service('theme_handler');
$theme_handler->install(['test_basetheme', 'test_subtheme', 'test_subsubtheme']);
$registry_subsub_theme = new Registry(\Drupal::root(), \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_subsubtheme');
$registry_sub_theme = new Registry(\Drupal::root(), \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_subtheme');
$registry_base_theme = new Registry(\Drupal::root(), \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_basetheme');
$preprocess_functions = $registry_subsub_theme->get()['theme_test_template_test']['preprocess functions'];
$this->assertIdentical([
'template_preprocess',
'test_basetheme_preprocess_theme_test_template_test',
'test_subtheme_preprocess_theme_test_template_test',
'test_subsubtheme_preprocess_theme_test_template_test',
], $preprocess_functions);
$preprocess_functions = $registry_sub_theme->get()['theme_test_template_test']['preprocess functions'];
$this->assertIdentical([
'template_preprocess',
'test_basetheme_preprocess_theme_test_template_test',
'test_subtheme_preprocess_theme_test_template_test',
], $preprocess_functions);
$preprocess_functions = $registry_base_theme->get()['theme_test_template_test']['preprocess functions'];
$this->assertIdentical([
'template_preprocess',
'test_basetheme_preprocess_theme_test_template_test',
], $preprocess_functions);
}
}
......@@ -216,7 +216,8 @@ function testListThemes() {
// Check if ThemeHandlerInterface::listInfo() returns disabled themes.
// Check for base theme and subtheme lists.
$base_theme_list = array('test_basetheme' => 'Theme test base theme');
$sub_theme_list = array('test_subtheme' => 'Theme test subtheme');
$sub_theme_list = array('test_subsubtheme' => 'Theme test subsubtheme', 'test_subtheme' => 'Theme test subtheme');
$this->assertIdentical($themes['test_basetheme']->sub_themes, $sub_theme_list, 'Base theme\'s object includes list of subthemes.');
$this->assertIdentical($themes['test_subtheme']->base_themes, $base_theme_list, 'Subtheme\'s object includes list of base themes.');
// Check for theme engine in subtheme.
......
......@@ -30,7 +30,7 @@ class TwigRegistryLoaderTest extends WebTestBase {
protected function setUp() {
parent::setUp();
\Drupal::service('theme_handler')->install(array('test_theme_twig_registry_loader'));
\Drupal::service('theme_handler')->install(array('test_theme_twig_registry_loader', 'test_theme_twig_registry_loader_theme', 'test_theme_twig_registry_loader_subtheme'));
$this->twig = \Drupal::service('twig');
}
......@@ -65,6 +65,25 @@ public function testTwigNamespaces() {
$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');
// Enable overriding theme that overrides the extend and insert templates
// from the base theme.
$this->config('system.theme')
->set('default', 'test_theme_twig_registry_loader_theme')
->save();
$this->drupalGet('twig-theme-test/registry-loader');
$this->assertText('This line is from test_theme_twig_registry_loader_theme/templates/twig-registry-loader-test-extend.html.twig');
$this->assertText('This line is from test_theme_twig_registry_loader_theme/templates/twig-registry-loader-test-include.html.twig');
// Enable a subtheme for the theme that doesnt have any overrides to make
// sure that templates are being loaded from the first parent which has the
// templates.
$this->config('system.theme')
->set('default', 'test_theme_twig_registry_loader_subtheme')
->save();
$this->drupalGet('twig-theme-test/registry-loader');
$this->assertText('This line is from test_theme_twig_registry_loader_theme/templates/twig-registry-loader-test-extend.html.twig');
$this->assertText('This line is from test_theme_twig_registry_loader_theme/templates/twig-registry-loader-test-include.html.twig');
}
}
......@@ -21,3 +21,9 @@ function test_basetheme_views_post_render(ViewExecutable $view) {
// We append the function name to the title for test to check for.
$view->setTitle($view->getTitle() . ":" . __FUNCTION__);
}
/**
* Implements hook_preprocess_HOOK() for theme_test_template_test templates.
*/
function test_basetheme_preprocess_theme_test_template_test(&$variables) {
}
name: 'Theme test subsubtheme'
type: theme
description: 'Test theme which uses test_subtheme as the base theme.'
version: VERSION
core: 8.x
base theme: test_subtheme
<?php
/**
* @file
* Add hooks for tests to use.
*/
/**
* Implements hook_preprocess_HOOK() for theme_test_template_test templates.
*/
function test_subsubtheme_preprocess_theme_test_template_test(&$variables) {
}
......@@ -22,3 +22,9 @@ function test_subtheme_views_post_render(ViewExecutable $view) {
// We append the function name to the title for test to check for.
$view->setTitle($view->getTitle() . ":" . __FUNCTION__);
}
/**
* Implements hook_preprocess_HOOK() for theme_test_template_test templates.
*/
function test_subtheme_preprocess_theme_test_template_test(&$variables) {
}
name: 'Twig registry loader test subtheme'
type: theme
base theme: test_theme_twig_registry_loader_theme
description: 'Support module for Twig registry loader testing.'
version: VERSION
core: 8.x
This line is from test_theme_twig_registry_loader_theme/templates/twig-registry-loader-test-extend.html.twig
{% block content %}
This text is in a block.
{% endblock %}
This line is from test_theme_twig_registry_loader_theme/templates/twig-registry-loader-test-include.html.twig
name: 'Twig registry loader test theme'
type: theme
base theme: test_theme_twig_registry_loader
description: 'Support module for Twig registry loader testing.'
version: VERSION
core: 8.x
......@@ -8,6 +8,7 @@
namespace Drupal\Tests\Core\Theme;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Theme\ActiveTheme;
use Drupal\Core\Theme\Registry;
use Drupal\Tests\UnitTestCase;
......@@ -52,6 +53,13 @@ class RegistryTest extends UnitTestCase {
*/
protected $themeHandler;
/**
* The mocked theme initialization.
*
* @var \Drupal\Core\Theme\ThemeInitializationInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $themeInitialization;
/**
* {@inheritdoc}
*/
......@@ -62,6 +70,7 @@ protected function setUp() {
$this->lock = $this->getMock('Drupal\Core\Lock\LockBackendInterface');
$this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$this->themeHandler = $this->getMock('Drupal\Core\Extension\ThemeHandlerInterface');
$this->themeInitialization = $this->getMock('Drupal\Core\Theme\ThemeInitializationInterface');
$this->setupTheme();
}
......@@ -71,7 +80,17 @@ protected function setUp() {
*/
public function testGetRegistryForModule() {
$this->setupTheme('test_theme');
$this->registry->setTheme(new Extension('theme', 'core/modules/system/tests/themes/test_theme/test_theme.info.yml', 'test_theme.theme'));
$this->registry->setTheme(new ActiveTheme([
'name' => 'test_theme',
'path' => 'core/modules/system/tests/themes/test_theme/test_theme.info.yml',
'engine' => 'twig',
'owner' => 'twig',
'stylesheets_remove' => [],
'stylesheets_override' => [],
'libraries' => [],
'extension' => '.twig',
'base_themes' => [],
]));
$this->registry->setBaseThemes(array());
// Include the module so that hook_theme can be called.
......@@ -106,14 +125,14 @@ public function testGetRegistryForModule() {
}
protected function setupTheme($theme_name = NULL) {
$this->registry = new TestRegistry($this->root, $this->cache, $this->lock, $this->moduleHandler, $this->themeHandler, $theme_name);
$this->registry = new TestRegistry($this->root, $this->cache, $this->lock, $this->moduleHandler, $this->themeHandler, $this->themeInitialization, $theme_name);
}
}
class TestRegistry extends Registry {
public function setTheme(Extension $theme) {
public function setTheme(ActiveTheme $theme) {
$this->theme = $theme;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment