diff --git a/core/lib/Drupal/Core/Render/theme.api.php b/core/lib/Drupal/Core/Render/theme.api.php index 19058e34620..b9cec8dea5e 100644 --- a/core/lib/Drupal/Core/Render/theme.api.php +++ b/core/lib/Drupal/Core/Render/theme.api.php @@ -645,6 +645,8 @@ function hook_theme_suggestions_HOOK(array $variables) { * hook_theme_suggestions_HOOK_alter(). So, for each module or theme, the more * general hooks are called first followed by the more specific. * + * New suggestions must begin with the value of HOOK, followed by two underscores to be discoverable. + * * In the following example, we provide an alternative template suggestion to * node and taxonomy term templates based on the user being logged in. * @code @@ -690,11 +692,27 @@ function hook_theme_suggestions_alter(array &$suggestions, array $variables, $ho * hook called (in this case 'node__article') is available in * $variables['theme_hook_original']. * + * New suggestions must begin with the value of HOOK, followed by two underscores to be discoverable. + * For example, consider the below suggestions from hook_theme_suggestions_node_alter: + * - node__article is valid + * - node__article__custom_template is valid + * - node--article is invalid + * - article__custom_template is invalid + * * Implementations of this hook must be placed in *.module or *.theme files, or * must otherwise make sure that the hook implementation is available at * any given time. * - * @todo Add @code sample. + * In the following example, we provide an alternative template suggestion to + * node templates based on the user being logged in. + * @code + * function MYMODULE_theme_suggestions_node_alter(array &$suggestions, array $variables) { + * if (\Drupal::currentUser()->isAuthenticated()) { + * $suggestions[] = 'node__logged_in'; + * } + * } + * + * @endcode * * @param array $suggestions * An array of theme suggestions. diff --git a/core/modules/system/tests/src/Functional/Theme/TwigDebugMarkupTest.php b/core/modules/system/tests/src/Functional/Theme/TwigDebugMarkupTest.php index 311b762b45d..409a729aa4d 100644 --- a/core/modules/system/tests/src/Functional/Theme/TwigDebugMarkupTest.php +++ b/core/modules/system/tests/src/Functional/Theme/TwigDebugMarkupTest.php @@ -55,6 +55,7 @@ class TwigDebugMarkupTest extends BrowserTestBase { $this->assertStringContainsString("THEME HOOK: 'node'", $output, 'Theme call information found.'); $this->assertStringContainsString('* node--1--full' . $extension . PHP_EOL . ' x node--1' . $extension . PHP_EOL . ' * node--page--full' . $extension . PHP_EOL . ' * node--page' . $extension . PHP_EOL . ' * node--full' . $extension . PHP_EOL . ' * node' . $extension, $output, 'Suggested template files found in order and node ID specific template shown as current template.'); $this->assertStringContainsString(Html::escape('node--'), (string) $output); + $this->assertStringContainsString('', $output, 'Twig debug markup found invalid suggestions.'); $template_filename = $templates['node__1']['path'] . '/' . $templates['node__1']['template'] . $extension; $this->assertStringContainsString("BEGIN OUTPUT from '$template_filename'", $output, 'Full path to current template file found.'); diff --git a/core/modules/system/tests/themes/test_theme/test_theme.theme b/core/modules/system/tests/themes/test_theme/test_theme.theme index d5bd12ed4e9..b27a812f0c3 100644 --- a/core/modules/system/tests/themes/test_theme/test_theme.theme +++ b/core/modules/system/tests/themes/test_theme/test_theme.theme @@ -68,6 +68,15 @@ function test_theme_theme_suggestions_theme_test_suggestions_alter(array &$sugge array_unshift($suggestions, 'theme_test_suggestions__' . 'theme_override'); } +/** + * Implements hook_theme_suggestions_HOOK_alter(). + */ +function test_theme_theme_suggestions_node_alter(array &$suggestions, array $variables) { + // Add an invalid suggestion to be tested. + $suggestions[] = 'invalid_theme_suggestions'; + \Drupal::messenger()->addStatus(__FUNCTION__ . '() executed.'); +} + /** * Implements hook_theme_registry_alter(). */ diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine index 2fc9536bd4b..4ab093c4c81 100644 --- a/core/themes/engines/twig/twig.engine +++ b/core/themes/engines/twig/twig.engine @@ -93,12 +93,27 @@ function twig_render_template($template_file, array $variables) { if (strpos($variables['theme_hook_original'], '__') === FALSE) { $suggestions[] = $variables['theme_hook_original']; } - foreach ($suggestions as &$suggestion) { + $invalid_suggestions = []; + $base_hook = $base_hook ?? $variables['theme_hook_original']; + foreach ($suggestions as $key => &$suggestion) { + // Valid suggestions are $base_hook, $base_hook__*, and contain no hyphens. + if (($suggestion !== $base_hook && !str_starts_with($suggestion, $base_hook . '__')) || str_contains($suggestion, '-')) { + $invalid_suggestions[] = $suggestion; + unset($suggestions[$key]); + continue; + } $template = strtr($suggestion, '_', '-') . $extension; $prefix = ($template == $current_template) ? 'x' : '*'; $suggestion = $prefix . ' ' . $template; } $output['debug_info'] .= "\n"; + + if (!empty($invalid_suggestions)) { + $output['debug_info'] .= "\n"; + } } $output['debug_info'] .= "\n\n"; $output['debug_suffix'] .= "\n\n\n";