reverted:
--- b/core/lib/Drupal/Core/Theme/ThemeManager.php
+++ a/core/lib/Drupal/Core/Theme/ThemeManager.php
@@ -140,46 +140,48 @@
}
$theme_registry = $this->themeRegistry->getRuntime();
+
+ // If an array of hook candidates were passed, use the first one that has an
+ // implementation.
+ if (is_array($hook)) {
+ foreach ($hook as $candidate) {
+ if ($theme_registry->has($candidate)) {
+ break;
+ }
- $theme_hooks = (array) $hook;
- $hook_found = NULL;
- // Check for hook implementation. If an array of hook candidates is passed,
- // use the first one that has an implementation.
- foreach ($theme_hooks as $candidate) {
- if ($theme_registry->has($candidate)) {
- $hook_found = $candidate;
- break;
}
+ $hook = $candidate;
}
+ // Save the original theme hook, so it can be supplied to theme variable
+ // preprocess callbacks.
+ $original_hook = $hook;
+
+ // If there's no implementation, check for more generic fallbacks.
+ // If there's still no implementation, log an error and return an empty
+ // string.
+ if (!$theme_registry->has($hook)) {
+ // Iteratively strip everything after the last '__' delimiter, until an
+ // implementation is found.
+ while ($pos = strrpos($hook, '__')) {
+ $hook = substr($hook, 0, $pos);
+ if ($theme_registry->has($hook)) {
+ break;
+ }
-
- $derived_hooks = [];
- // Remove the last item of the array which is the original hook and use it
- // as a pattern.
- $hook_pattern = array_pop($theme_hooks);
- // Get the generic fallbacks. If a valid hook implementation has not been
- // found, check each fallback until one is.
- while ($pos = strrpos($hook_pattern, '__')) {
- // Save all derived hooks to be used later as a theme suggestion.
- $derived_hooks[] = $hook_pattern;
- $hook_pattern = substr($hook_pattern, 0, $pos);
- if (!$hook_found && $theme_registry->has($hook_pattern)) {
- $hook_found = $hook_pattern;
}
+ if (!$theme_registry->has($hook)) {
+ // Only log a message when not trying theme suggestions ($hook being an
+ // array).
+ if (!isset($candidate)) {
+ \Drupal::logger('theme')->warning('Theme hook %hook not found.', ['%hook' => $hook]);
+ }
+ // There is no theme implementation for the hook passed. Return FALSE so
+ // the function calling
+ // \Drupal\Core\Theme\ThemeManagerInterface::render() can differentiate
+ // between a hook that exists and renders an empty string, and a hook
+ // that is not implemented.
+ return FALSE;
- }
- if (!$hook_found) {
- // Do not log a warning for arrays of theme hooks with no implemented
- // hooks. It's valid to pass an array of theme hooks where none of the
- // hooks are implemented. We only log missing theme hook implementations
- // for single (string) hooks.
- if (is_string($hook)) {
- \Drupal::logger('theme')->warning('Theme hook %hook not found.', ['%hook' => $hook]);
}
- // There is no theme implementation for the hook(s) passed. Return FALSE
- // so the calling function can differentiate between a hook that exists
- // and renders an empty string, and hooks that are not implemented.
- return FALSE;
}
+
- $hook = $hook_found;
- $theme_hooks = array_merge($theme_hooks, $derived_hooks);
$info = $theme_registry->get($hook);
// If a renderable array is passed as $variables, then set $variables to
@@ -208,11 +210,9 @@
elseif (!empty($info['render element'])) {
$variables += [$info['render element'] => []];
}
+ // Supply original caller info.
-
- // Save the original theme hook, so it can be supplied to theme variable
- // preprocess callbacks.
$variables += [
+ 'theme_hook_original' => $original_hook,
- 'theme_hook_original' => $candidate,
];
// Set base hook for later use. For example if '#theme' => 'node__article'
@@ -228,9 +228,12 @@
// Invoke hook_theme_suggestions_HOOK().
$suggestions = $this->moduleHandler->invokeAll('theme_suggestions_' . $base_theme_hook, [$variables]);
+ // If the theme implementation was invoked with a direct theme suggestion
+ // like '#theme' => 'node__article', add it to the suggestions array before
+ // invoking suggestion alter hooks.
+ if (isset($info['base hook'])) {
+ $suggestions[] = $hook;
+ }
- // Prioritize suggestions from hook_theme_suggestions_HOOK() implementations
- // above other suggestions.
- $suggestions = array_merge($suggestions, array_reverse($theme_hooks));
// Invoke hook_theme_suggestions_alter() and
// hook_theme_suggestions_HOOK_alter().
reverted:
--- b/core/modules/system/src/Tests/Theme/ThemeSuggestionsAlterTest.php
+++ a/core/modules/system/src/Tests/Theme/ThemeSuggestionsAlterTest.php
@@ -152,31 +152,6 @@
}
/**
- * Tests hook_theme_suggestions_HOOK() with direct array suggestions.
- */
- public function testArraySuggestions() {
- $this->drupalGet('theme-test/array-suggestions');
- $this->assertText('Template for testing suggestion hooks with an array of theme suggestions.');
-
- $this->drupalGet('/theme-test/array-suggestions-specific');
- $this->assertText('Template for testing suggestion hooks with an array of theme suggestions, overrides a less specific suggestion.');
-
- // Enable the theme_suggestions_test module to test modules implementing
- // suggestions hooks.
- \Drupal::service('module_installer')->install(['theme_suggestions_test']);
-
- // Install test_theme to provide a template for the suggestion added in
- // theme_suggestions_test module.
- $this->config('system.theme')
- ->set('default', 'test_theme')
- ->save();
-
- $this->resetAll();
- $this->drupalGet('theme-test/array-suggestions');
- $this->assertText('Template overridden based on new theme suggestion provided by a module via hook_theme_suggestions_HOOK().');
- }
-
- /**
* Tests execution order of theme suggestion alter hooks.
*
* hook_theme_suggestions_alter() should fire before
reverted:
--- b/core/modules/system/src/Tests/Theme/TwigDebugMarkupTest.php
+++ a/core/modules/system/src/Tests/Theme/TwigDebugMarkupTest.php
@@ -19,30 +19,22 @@
public static $modules = ['theme_test', 'node'];
/**
+ * Tests debug markup added to Twig template output.
- * {@inheritdoc}
*/
+ public function testTwigDebugMarkup() {
+ /** @var \Drupal\Core\Render\RendererInterface $renderer */
+ $renderer = $this->container->get('renderer');
+ $extension = twig_extension();
- protected function setUp() {
- parent::setUp();
-
\Drupal::service('theme_handler')->install(['test_theme']);
+ $this->config('system.theme')->set('default', 'test_theme')->save();
- \Drupal::service('theme_handler')->setDefault('test_theme');
$this->drupalCreateContentType(['type' => 'page']);
-
// Enable debug, rebuild the service container, and clear all caches.
$parameters = $this->container->getParameter('twig.config');
$parameters['debug'] = TRUE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
$this->resetAll();
- }
- /**
- * Tests debug markup added to Twig template output.
- */
- public function testTwigDebugMarkup() {
- /** @var \Drupal\Core\Render\RendererInterface $renderer */
- $renderer = $this->container->get('renderer');
- $extension = twig_extension();
$cache = $this->container->get('theme.registry')->get();
// Create array of Twig templates.
$templates = drupal_find_theme_templates($cache, $extension, drupal_get_path('theme', 'test_theme'));
@@ -88,65 +80,4 @@
$this->assertFalse(strpos($output, '') !== FALSE, 'Twig debug markup not found in theme output when debug is disabled.');
}
- /**
- * Tests debug markup for array suggestions and hook_theme_suggestions_HOOK().
- */
- public function testArraySuggestionsTwigDebugMarkup() {
- \Drupal::service('module_installer')->install(['theme_suggestions_test']);
- $extension = twig_extension();
- $this->drupalGet('theme-test/array-suggestions');
- $output = $this->getRawContent();
-
- $expected = "THEME HOOK: 'theme_test_array_suggestions'";
- $this->assertTrue(strpos($output, $expected) !== FALSE, 'Theme call information found.');
-
- $expected = '';
- $message = 'Suggested template files found in order and correct suggestion shown as current template.';
- $this->assertTrue(strpos($output, $expected) !== FALSE, $message);
- }
-
- /**
- * Tests debug markup for specific suggestions without implementation.
- */
- public function testUnimplementedSpecificSuggestionsTwigDebugMarkup() {
- $extension = twig_extension();
- $this->drupalGet('theme-test/specific-suggestion');
- $output = $this->getRawContent();
-
- $expected = "THEME HOOK: 'theme_test_specific_suggestions__not__found'";
- $this->assertTrue(strpos($output, $expected) !== FALSE, 'Theme call information found.');
-
- $message = 'Suggested template files found in order and base template shown as current template.';
- $expected = '';
- $this->assertTrue(strpos($output, $expected) !== FALSE, $message);
- }
-
- /**
- * Tests debug markup for specific suggestions.
- */
- public function testSpecificSuggestionsTwigDebugMarkup() {
- $extension = twig_extension();
- $this->drupalGet('theme-test/specific-suggestion-alter');
- $output = $this->getRawContent();
-
- $expected = "THEME HOOK: 'theme_test_specific_suggestions__variant'";
- $this->assertTrue(strpos($output, $expected) !== FALSE, 'Theme call information found.');
-
- $expected = '';
- $message = 'Suggested template files found in order and suggested template shown as current.';
- $this->assertTrue(strpos($output, $expected) !== FALSE, $message);
- }
-
}
reverted:
--- b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module
+++ a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module
@@ -55,10 +55,3 @@
function theme_suggestions_test_theme_suggestions_theme_test_suggestions_include_alter(array &$suggestions, array $variables, $hook) {
$suggestions[] = 'theme_suggestions_test_include';
}
-
-/**
- * Implements hook_theme_suggestions_HOOK().
- */
-function theme_suggestions_test_theme_suggestions_theme_test_array_suggestions(array $variables) {
- return ['theme_test_array_suggestions__suggestion_from_hook'];
-}
reverted:
--- b/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php
+++ a/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php
@@ -106,13 +106,6 @@
}
/**
- * Menu callback for testing direct suggestions without implementation.
- */
- public function specificSuggestion() {
- return ['#theme' => 'theme_test_specific_suggestions__not__found'];
- }
-
- /**
* Menu callback for testing suggestion alter hooks with specific suggestions.
*/
public function specificSuggestionAlter() {
@@ -135,33 +128,6 @@
}
/**
- * Menu callback for testing suggestion hooks with an array of theme hooks.
- */
- public function arraySuggestions() {
- return [
- '#theme' => [
- 'theme_test_array_suggestions__not_implemented',
- 'theme_test_array_suggestions__not_implemented_2',
- 'theme_test_array_suggestions',
- ],
- ];
- }
-
- /**
- * Menu callback for testing suggestion hooks with an array of theme hooks.
- */
- public function arraySuggestionsSpecific() {
- return [
- '#theme' => [
- 'theme_test_array_suggestions__implemented__not_implemented',
- 'theme_test_array_suggestions__implemented',
- 'theme_test_array_suggestions__not_implemented',
- 'theme_test_array_suggestions',
- ],
- ];
- }
-
- /**
* Controller to ensure that no theme is initialized.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
reverted:
--- b/core/modules/system/tests/modules/theme_test/templates/theme-test-array-suggestions--implemented.html.twig
+++ /dev/null
@@ -1,2 +0,0 @@
-{# Output for Theme API test #}
-Template for testing suggestion hooks with an array of theme suggestions, overrides a less specific suggestion.
reverted:
--- b/core/modules/system/tests/modules/theme_test/templates/theme-test-array-suggestions.html.twig
+++ /dev/null
@@ -1,2 +0,0 @@
-{# Output for Theme API test #}
-Template for testing suggestion hooks with an array of theme suggestions.
reverted:
--- b/core/modules/system/tests/modules/theme_test/theme_test.module
+++ a/core/modules/system/tests/modules/theme_test/theme_test.module
@@ -57,12 +57,6 @@
'variables' => [],
'function' => 'theme_theme_test_function_template_override',
];
- $items['theme_test_array_suggestions'] = [
- 'variables' => [],
- ];
- $items['theme_test_array_suggestions__implemented'] = [
- 'variables' => [],
- ];
$info['test_theme_not_existing_function'] = [
'function' => 'test_theme_not_existing_function',
];
reverted:
--- b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
+++ a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
@@ -76,13 +76,6 @@
requirements:
_access: 'TRUE'
-theme_test.specific_suggestion:
- path: '/theme-test/specific-suggestion'
- defaults:
- _controller: '\Drupal\theme_test\ThemeTestController::specificSuggestion'
- requirements:
- _access: 'TRUE'
-
specific_suggestion_alter:
path: '/theme-test/specific-suggestion-alter'
defaults:
@@ -104,20 +97,6 @@
requirements:
_access: 'TRUE'
-theme_test.array_suggestions:
- path: '/theme-test/array-suggestions'
- defaults:
- _controller: '\Drupal\theme_test\ThemeTestController::arraySuggestions'
- requirements:
- _access: 'TRUE'
-
-theme_test.array_suggestions_specific:
- path: '/theme-test/array-suggestions-specific'
- defaults:
- _controller: '\Drupal\theme_test\ThemeTestController::arraySuggestionsSpecific'
- requirements:
- _access: 'TRUE'
-
theme_test.non_html:
path: '/theme-test/non-html'
defaults:
reverted:
--- b/core/modules/system/tests/themes/test_theme/templates/theme-test-array-suggestions--suggestion-from-hook.html.twig
+++ /dev/null
@@ -1,2 +0,0 @@
-{# Output for Theme API test #}
-Template overridden based on new theme suggestion provided by a module via hook_theme_suggestions_HOOK().
reverted:
--- b/core/modules/views/tests/src/Functional/ViewsThemeIntegrationTest.php
+++ a/core/modules/views/tests/src/Functional/ViewsThemeIntegrationTest.php
@@ -78,32 +78,4 @@
$this->assertRaw('' . count($this->dataSet()) . ' items found.', 'Views group title added by test_subtheme.test_subtheme_views_post_render');
}
- /**
- * Tests the views theme suggestions in debug mode.
- */
- public function testThemeSuggestionsInDebug() {
- $parameters = $this->container->getParameter('twig.config');
- $parameters['debug'] = TRUE;
- $this->setContainerParameter('twig.config', $parameters);
- $this->rebuildContainer();
- $this->resetAll();
-
- $build = [
- '#type' => 'view',
- '#name' => 'test_page_display',
- '#display_id' => 'default',
- '#arguments' => [],
- ];
-
- /** @var \Drupal\Core\Render\RendererInterface $renderer */
- $renderer = $this->container->get('renderer');
-
- $output = $renderer->renderRoot($build);
- $expected = ' * views-view--test-page-display--default.html.twig' . PHP_EOL
- . ' * views-view--default.html.twig' . PHP_EOL
- . ' * views-view--test-page-display.html.twig' . PHP_EOL
- . ' x views-view.html.twig' . PHP_EOL;
- $this->assertTrue(strpos($output, $expected) !== FALSE);
- }
-
}
reverted:
--- b/core/themes/engines/twig/twig.engine
+++ a/core/themes/engines/twig/twig.engine
@@ -79,21 +79,31 @@
// If there are theme suggestions, reverse the array so more specific
// suggestions are shown first.
if (!empty($variables['theme_hook_suggestions'])) {
+ $variables['theme_hook_suggestions'] = array_reverse($variables['theme_hook_suggestions']);
+ }
+ // Add debug output for directly called suggestions like
+ // '#theme' => 'comment__node__article'.
+ if (strpos($variables['theme_hook_original'], '__') !== FALSE) {
+ $derived_suggestions[] = $hook = $variables['theme_hook_original'];
+ while ($pos = strrpos($hook, '__')) {
+ $hook = substr($hook, 0, $pos);
+ $derived_suggestions[] = $hook;
+ }
+ // Get the value of the base hook (last derived suggestion) and append it
+ // to the end of all theme suggestions.
+ $base_hook = array_pop($derived_suggestions);
+ $variables['theme_hook_suggestions'] = array_merge($derived_suggestions, $variables['theme_hook_suggestions']);
+ $variables['theme_hook_suggestions'][] = $base_hook;
+ }
+ if (!empty($variables['theme_hook_suggestions'])) {
- $suggestions = array_reverse($variables['theme_hook_suggestions']);
$extension = twig_extension();
$current_template = basename($template_file);
+ $suggestions = $variables['theme_hook_suggestions'];
+ // Only add the original theme hook if it wasn't a directly called
+ // suggestion.
+ if (strpos($variables['theme_hook_original'], '__') === FALSE) {
-
- $pos = strpos($variables['theme_hook_original'], '__');
- if ($pos !== FALSE) {
- // If this template was invoked directly (e.g.: '#theme' =>
- // 'links__node') include the base theme suggestion ('links' in this
- // example).
- $suggestions[] = substr($variables['theme_hook_original'], 0, $pos);
- }
- else {
$suggestions[] = $variables['theme_hook_original'];
}
-
foreach ($suggestions as &$suggestion) {
$template = strtr($suggestion, '_', '-') . $extension;
$prefix = ($template == $current_template) ? 'x' : '*';
only in patch2:
unchanged:
--- a/web/core/lib/Drupal/Core/Theme/ThemeManager.php
+++ b/web/core/lib/Drupal/Core/Theme/ThemeManager.php
@@ -141,47 +141,46 @@ public function render($hook, array $variables) {
$theme_registry = $this->themeRegistry->getRuntime();
- // If an array of hook candidates were passed, use the first one that has an
- // implementation.
- if (is_array($hook)) {
- foreach ($hook as $candidate) {
- if ($theme_registry->has($candidate)) {
- break;
- }
+ $theme_hooks = (array) $hook;
+ $hook_found = NULL;
+ // Check for hook implementation. If an array of hook candidates is passed,
+ // use the first one that has an implementation.
+ foreach ($theme_hooks as $candidate) {
+ if ($theme_registry->has($candidate)) {
+ $hook_found = $candidate;
+ break;
}
- $hook = $candidate;
}
- // Save the original theme hook, so it can be supplied to theme variable
- // preprocess callbacks.
- $original_hook = $hook;
-
- // If there's no implementation, check for more generic fallbacks.
- // If there's still no implementation, log an error and return an empty
- // string.
- if (!$theme_registry->has($hook)) {
- // Iteratively strip everything after the last '__' delimiter, until an
- // implementation is found.
- while ($pos = strrpos($hook, '__')) {
- $hook = substr($hook, 0, $pos);
- if ($theme_registry->has($hook)) {
- break;
- }
- }
- if (!$theme_registry->has($hook)) {
- // Only log a message when not trying theme suggestions ($hook being an
- // array).
- if (!isset($candidate)) {
- \Drupal::logger('theme')->warning('Theme hook %hook not found.', ['%hook' => $hook]);
- }
- // There is no theme implementation for the hook passed. Return FALSE so
- // the function calling
- // \Drupal\Core\Theme\ThemeManagerInterface::render() can differentiate
- // between a hook that exists and renders an empty string, and a hook
- // that is not implemented.
- return FALSE;
+
+ $derived_hooks = [];
+ // Remove the last item of the array which is the original hook and use it
+ // as a pattern.
+ $hook_pattern = array_pop($theme_hooks);
+ // Get the generic fallbacks. If a valid hook implementation has not been
+ // found, check each fallback until one is.
+ while ($pos = strrpos($hook_pattern, '__')) {
+ // Save all derived hooks to be used later as a theme suggestion.
+ $derived_hooks[] = $hook_pattern;
+ $hook_pattern = substr($hook_pattern, 0, $pos);
+ if (!$hook_found && $theme_registry->has($hook_pattern)) {
+ $hook_found = $hook_pattern;
}
}
-
+ if (!$hook_found) {
+ // Do not log a warning for arrays of theme hooks with no implemented
+ // hooks. It's valid to pass an array of theme hooks where none of the
+ // hooks are implemented. We only log missing theme hook implementations
+ // for single (string) hooks.
+ if (is_string($hook)) {
+ \Drupal::logger('theme')->warning('Theme hook %hook not found.', ['%hook' => $hook]);
+ }
+ // There is no theme implementation for the hook(s) passed. Return FALSE
+ // so the calling function can differentiate between a hook that exists
+ // and renders an empty string, and hooks that are not implemented.
+ return FALSE;
+ }
+ $hook = $hook_found;
+ $theme_hooks = array_merge($theme_hooks, $derived_hooks);
$info = $theme_registry->get($hook);
// If a renderable array is passed as $variables, then set $variables to
@@ -210,9 +209,11 @@ public function render($hook, array $variables) {
elseif (!empty($info['render element'])) {
$variables += [$info['render element'] => []];
}
- // Supply original caller info.
+
+ // Save the original theme hook, so it can be supplied to theme variable
+ // preprocess callbacks.
$variables += [
- 'theme_hook_original' => $original_hook,
+ 'theme_hook_original' => $candidate,
];
// Set base hook for later use. For example if '#theme' => 'node__article'
@@ -228,12 +229,9 @@ public function render($hook, array $variables) {
// Invoke hook_theme_suggestions_HOOK().
$suggestions = $this->moduleHandler->invokeAll('theme_suggestions_' . $base_theme_hook, [$variables]);
- // If the theme implementation was invoked with a direct theme suggestion
- // like '#theme' => 'node__article', add it to the suggestions array before
- // invoking suggestion alter hooks.
- if (isset($info['base hook'])) {
- $suggestions[] = $hook;
- }
+ // Prioritize suggestions from hook_theme_suggestions_HOOK() implementations
+ // above other suggestions.
+ $suggestions = array_merge($suggestions, array_reverse($theme_hooks));
// Invoke hook_theme_suggestions_alter() and
// hook_theme_suggestions_HOOK_alter().
only in patch2:
unchanged:
--- a/web/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module
+++ b/web/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module
@@ -55,3 +55,10 @@ function theme_suggestions_test_theme_suggestions_theme_test_specific_suggestion
function theme_suggestions_test_theme_suggestions_theme_test_suggestions_include_alter(array &$suggestions, array $variables, $hook) {
$suggestions[] = 'theme_suggestions_test_include';
}
+
+/**
+ * Implements hook_theme_suggestions_HOOK().
+ */
+function theme_suggestions_test_theme_suggestions_theme_test_array_suggestions(array $variables) {
+ return ['theme_test_array_suggestions__suggestion_from_hook'];
+}
only in patch2:
unchanged:
--- a/web/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php
+++ b/web/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php
@@ -105,6 +105,13 @@ public function generalSuggestionAlter() {
return ['#theme' => 'theme_test_general_suggestions'];
}
+ /**
+ * Menu callback for testing direct suggestions without implementation.
+ */
+ public function specificSuggestion() {
+ return ['#theme' => 'theme_test_specific_suggestions__not__found'];
+ }
+
/**
* Menu callback for testing suggestion alter hooks with specific suggestions.
*/
@@ -126,6 +133,32 @@ public function suggestionAlterInclude() {
return ['#theme' => 'theme_test_suggestions_include'];
}
+ /**
+ * Menu callback for testing suggestion hooks with an array of theme hooks.
+ */
+ public function arraySuggestions() {
+ return [
+ '#theme' => [
+ 'theme_test_array_suggestions__not_implemented',
+ 'theme_test_array_suggestions__not_implemented_2',
+ 'theme_test_array_suggestions',
+ ],
+ ];
+ }
+ /**
+ * Menu callback for testing suggestion hooks with an array of theme hooks.
+ */
+ public function arraySuggestionsSpecific() {
+ return [
+ '#theme' => [
+ 'theme_test_array_suggestions__implemented__not_implemented',
+ 'theme_test_array_suggestions__implemented',
+ 'theme_test_array_suggestions__not_implemented',
+ 'theme_test_array_suggestions',
+ ],
+ ];
+ }
+
/**
* Controller to ensure that no theme is initialized.
*
only in patch2:
unchanged:
--- /dev/null
+++ b/web/core/modules/system/tests/modules/theme_test/templates/theme-test-array-suggestions--implemented.html.twig
@@ -0,0 +1,2 @@
+{# Output for Theme API test #}
+Template for testing suggestion hooks with an array of theme suggestions, overrides a less specific suggestion.
\ No newline at end of file
only in patch2:
unchanged:
--- /dev/null
+++ b/web/core/modules/system/tests/modules/theme_test/templates/theme-test-array-suggestions.html.twig
@@ -0,0 +1,2 @@
+{# Output for Theme API test #}
+Template for testing suggestion hooks with an array of theme suggestions.
\ No newline at end of file
only in patch2:
unchanged:
--- a/web/core/modules/system/tests/modules/theme_test/theme_test.module
+++ b/web/core/modules/system/tests/modules/theme_test/theme_test.module
@@ -57,6 +57,12 @@ function theme_test_theme($existing, $type, $theme, $path) {
'variables' => [],
'function' => 'theme_theme_test_function_template_override',
];
+ $items['theme_test_array_suggestions'] = [
+ 'variables' => [],
+ ];
+ $items['theme_test_array_suggestions__implemented'] = [
+ 'variables' => [],
+ ];
$info['test_theme_not_existing_function'] = [
'function' => 'test_theme_not_existing_function',
];
only in patch2:
unchanged:
--- a/web/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
+++ b/web/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
@@ -76,6 +76,13 @@ suggestion_provided:
requirements:
_access: 'TRUE'
+theme_test.specific_suggestion:
+ path: '/theme-test/specific-suggestion'
+ defaults:
+ _controller: '\Drupal\theme_test\ThemeTestController::specificSuggestion'
+ requirements:
+ _access: 'TRUE'
+
specific_suggestion_alter:
path: '/theme-test/specific-suggestion-alter'
defaults:
@@ -97,6 +104,20 @@ suggestion_alter_include:
requirements:
_access: 'TRUE'
+theme_test.array_suggestions:
+ path: '/theme-test/array-suggestions'
+ defaults:
+ _controller: '\Drupal\theme_test\ThemeTestController::arraySuggestions'
+ requirements:
+ _access: 'TRUE'
+
+theme_test.array_suggestions_specific:
+ path: '/theme-test/array-suggestions-specific'
+ defaults:
+ _controller: '\Drupal\theme_test\ThemeTestController::arraySuggestionsSpecific'
+ requirements:
+ _access: 'TRUE'
+
theme_test.non_html:
path: '/theme-test/non-html'
defaults:
only in patch2:
unchanged:
--- a/web/core/modules/system/tests/src/Functional/Theme/ThemeSuggestionsAlterTest.php
+++ b/web/core/modules/system/tests/src/Functional/Theme/ThemeSuggestionsAlterTest.php
@@ -152,6 +152,31 @@ public function testSuggestionsAlterInclude() {
$this->assertText('Function suggested via suggestion alter hook found in include file.', 'Include file loaded for second request.');
}
+ /**
+ * Tests hook_theme_suggestions_HOOK() with direct array suggestions.
+ */
+ public function testArraySuggestions() {
+ $this->drupalGet('theme-test/array-suggestions');
+ $this->assertText('Template for testing suggestion hooks with an array of theme suggestions.');
+
+ $this->drupalGet('/theme-test/array-suggestions-specific');
+ $this->assertText('Template for testing suggestion hooks with an array of theme suggestions, overrides a less specific suggestion.');
+
+ // Enable the theme_suggestions_test module to test modules implementing
+ // suggestions hooks.
+ \Drupal::service('module_installer')->install(['theme_suggestions_test']);
+
+ // Install test_theme to provide a template for the suggestion added in
+ // theme_suggestions_test module.
+ $this->config('system.theme')
+ ->set('default', 'test_theme')
+ ->save();
+
+ $this->resetAll();
+ $this->drupalGet('theme-test/array-suggestions');
+ $this->assertText('Template overridden based on new theme suggestion provided by a module via hook_theme_suggestions_HOOK().');
+ }
+
/**
* Tests execution order of theme suggestion alter hooks.
*
only in patch2:
unchanged:
--- a/web/core/modules/system/tests/src/Functional/Theme/TwigDebugMarkupTest.php
+++ b/web/core/modules/system/tests/src/Functional/Theme/TwigDebugMarkupTest.php
@@ -20,22 +20,30 @@ class TwigDebugMarkupTest extends BrowserTestBase {
public static $modules = ['theme_test', 'node'];
/**
- * Tests debug markup added to Twig template output.
+ * {@inheritdoc}
*/
- public function testTwigDebugMarkup() {
- /** @var \Drupal\Core\Render\RendererInterface $renderer */
- $renderer = $this->container->get('renderer');
- $extension = twig_extension();
+ protected function setUp() {
+ parent::setUp();
+
\Drupal::service('theme_handler')->install(['test_theme']);
- $this->config('system.theme')->set('default', 'test_theme')->save();
+ \Drupal::service('theme_handler')->setDefault('test_theme');
$this->drupalCreateContentType(['type' => 'page']);
+
// Enable debug, rebuild the service container, and clear all caches.
$parameters = $this->container->getParameter('twig.config');
$parameters['debug'] = TRUE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
$this->resetAll();
+ }
+ /**
+ * Tests debug markup added to Twig template output.
+ */
+ public function testTwigDebugMarkup() {
+ /** @var \Drupal\Core\Render\RendererInterface $renderer */
+ $renderer = $this->container->get('renderer');
+ $extension = twig_extension();
$cache = $this->container->get('theme.registry')->get();
// Create array of Twig templates.
$templates = drupal_find_theme_templates($cache, $extension, drupal_get_path('theme', 'test_theme'));
@@ -80,4 +88,65 @@ public function testTwigDebugMarkup() {
$this->assertFalse(strpos($output, '') !== FALSE, 'Twig debug markup not found in theme output when debug is disabled.');
}
+ /**
+ * Tests debug markup for array suggestions and hook_theme_suggestions_HOOK().
+ */
+ public function testArraySuggestionsTwigDebugMarkup() {
+ \Drupal::service('module_installer')->install(['theme_suggestions_test']);
+ $extension = twig_extension();
+ $this->drupalGet('theme-test/array-suggestions');
+ $output = $this->getRawContent();
+
+ $expected = "THEME HOOK: 'theme_test_array_suggestions'";
+ $this->assertTrue(strpos($output, $expected) !== FALSE, 'Theme call information found.');
+
+ $expected = '';
+ $message = 'Suggested template files found in order and correct suggestion shown as current template.';
+ $this->assertTrue(strpos($output, $expected) !== FALSE, $message);
+ }
+
+ /**
+ * Tests debug markup for specific suggestions without implementation.
+ */
+ public function testUnimplementedSpecificSuggestionsTwigDebugMarkup() {
+ $extension = twig_extension();
+ $this->drupalGet('theme-test/specific-suggestion');
+ $output = $this->getRawContent();
+
+ $expected = "THEME HOOK: 'theme_test_specific_suggestions__not__found'";
+ $this->assertTrue(strpos($output, $expected) !== FALSE, 'Theme call information found.');
+
+ $message = 'Suggested template files found in order and base template shown as current template.';
+ $expected = '';
+ $this->assertTrue(strpos($output, $expected) !== FALSE, $message);
+ }
+
+ /**
+ * Tests debug markup for specific suggestions.
+ */
+ public function testSpecificSuggestionsTwigDebugMarkup() {
+ $extension = twig_extension();
+ $this->drupalGet('theme-test/specific-suggestion-alter');
+ $output = $this->getRawContent();
+
+ $expected = "THEME HOOK: 'theme_test_specific_suggestions__variant'";
+ $this->assertTrue(strpos($output, $expected) !== FALSE, 'Theme call information found.');
+
+ $expected = '';
+ $message = 'Suggested template files found in order and suggested template shown as current.';
+ $this->assertTrue(strpos($output, $expected) !== FALSE, $message);
+ }
+
}
only in patch2:
unchanged:
--- /dev/null
+++ b/web/core/modules/system/tests/themes/test_theme/templates/theme-test-array-suggestions--suggestion-from-hook.html.twig
@@ -0,0 +1,2 @@
+{# Output for Theme API test #}
+Template overridden based on new theme suggestion provided by a module via hook_theme_suggestions_HOOK().
\ No newline at end of file
only in patch2:
unchanged:
--- a/web/core/modules/views/tests/src/Functional/ViewsThemeIntegrationTest.php
+++ b/web/core/modules/views/tests/src/Functional/ViewsThemeIntegrationTest.php
@@ -78,4 +78,32 @@ public function testThemedViewPage() {
$this->assertRaw('' . count($this->dataSet()) . ' items found.', 'Views group title added by test_subtheme.test_subtheme_views_post_render');
}
+ /**
+ * Tests the views theme suggestions in debug mode.
+ */
+ public function testThemeSuggestionsInDebug() {
+ $parameters = $this->container->getParameter('twig.config');
+ $parameters['debug'] = TRUE;
+ $this->setContainerParameter('twig.config', $parameters);
+ $this->rebuildContainer();
+ $this->resetAll();
+
+ $build = [
+ '#type' => 'view',
+ '#name' => 'test_page_display',
+ '#display_id' => 'default',
+ '#arguments' => [],
+ ];
+
+ /** @var \Drupal\Core\Render\RendererInterface $renderer */
+ $renderer = $this->container->get('renderer');
+
+ $output = $renderer->renderRoot($build);
+ $expected = ' * views-view--test-page-display--default.html.twig' . PHP_EOL
+ . ' * views-view--default.html.twig' . PHP_EOL
+ . ' * views-view--test-page-display.html.twig' . PHP_EOL
+ . ' x views-view.html.twig' . PHP_EOL;
+ $this->assertTrue(strpos($output, $expected) !== FALSE);
+ }
+
}
only in patch2:
unchanged:
--- a/web/core/themes/engines/twig/twig.engine
+++ b/web/core/themes/engines/twig/twig.engine
@@ -79,29 +79,17 @@ function twig_render_template($template_file, array $variables) {
// If there are theme suggestions, reverse the array so more specific
// suggestions are shown first.
if (!empty($variables['theme_hook_suggestions'])) {
- $variables['theme_hook_suggestions'] = array_reverse($variables['theme_hook_suggestions']);
- }
- // Add debug output for directly called suggestions like
- // '#theme' => 'comment__node__article'.
- if (strpos($variables['theme_hook_original'], '__') !== FALSE) {
- $derived_suggestions[] = $hook = $variables['theme_hook_original'];
- while ($pos = strrpos($hook, '__')) {
- $hook = substr($hook, 0, $pos);
- $derived_suggestions[] = $hook;
- }
- // Get the value of the base hook (last derived suggestion) and append it
- // to the end of all theme suggestions.
- $base_hook = array_pop($derived_suggestions);
- $variables['theme_hook_suggestions'] = array_merge($derived_suggestions, $variables['theme_hook_suggestions']);
- $variables['theme_hook_suggestions'][] = $base_hook;
- }
- if (!empty($variables['theme_hook_suggestions'])) {
+ $suggestions = array_reverse($variables['theme_hook_suggestions']);
$extension = twig_extension();
$current_template = basename($template_file);
- $suggestions = $variables['theme_hook_suggestions'];
- // Only add the original theme hook if it wasn't a directly called
- // suggestion.
- if (strpos($variables['theme_hook_original'], '__') === FALSE) {
+ $pos = strpos($variables['theme_hook_original'], '__');
+ if ($pos !== FALSE) {
+ // If this template was invoked directly (e.g.: '#theme' =>
+ // 'links__node') include the base theme suggestion ('links' in this
+ // example).
+ $suggestions[] = substr($variables['theme_hook_original'], 0, $pos);
+ }
+ else {
$suggestions[] = $variables['theme_hook_original'];
}
foreach ($suggestions as &$suggestion) {