diff --git a/core/lib/Drupal/Core/Render/theme.api.php b/core/lib/Drupal/Core/Render/theme.api.php index 44fcdd0..3b92f2a 100644 --- a/core/lib/Drupal/Core/Render/theme.api.php +++ b/core/lib/Drupal/Core/Render/theme.api.php @@ -607,31 +607,53 @@ function hook_preprocess_HOOK(&$variables) { } /** - * Provides alternate named suggestions for a specific theme hook. + * Provides more specific templates or theme functions for a theme hook. * - * This hook allows modules to provide alternative theme function or template - * name suggestions. + * These alternative templates or theme functions can optionally be used by a + * theme to override markup or display logic based on incoming variables or + * global context. * - * HOOK is the least-specific version of the hook being called. For example, if - * '#theme' => 'node__article' is called, then hook_theme_suggestions_node() - * will be invoked, not hook_theme_suggestions_node__article(). The specific - * hook called (in this case 'node__article') is available in - * $variables['theme_hook_original']. + * For a specific theme hook like 'node', this hook allows modules to provide + * more specific template name suggestions, for example node--front.html.twig + * for a node displayed on the front page, or node--article.html.twig for an + * article node. * - * @todo Add @code sample. + * HOOK is the least-specific version of the hook being called, also known as + * the base theme hook. For example, if '#theme' => 'node__article' is called, + * then hook_theme_suggestions_node() will be invoked, not + * hook_theme_suggestions_node__article(). The specific hook called (in this + * case 'node__article') is available in $variables['theme_hook_original']. + * + * Note that this hook is not invoked for themes, even if the theme implements + * hook_theme(). Themes must use hook_theme_suggestions_HOOK_alter(). + * + * The following example provides an optional block--not-front.html.twig + * template that could be used to change the markup or display logic for blocks + * shown on every page but the front page: + * @code + * function block_theme_suggestions_block(array $variables) { + * $suggestions = array(); + * if (!drupal_is_front_page()) { + * $suggestions[] = 'block__not_front'; + * } + * return $suggestions; + * } + * @endcode * * @param array $variables * An array of variables passed to the theme hook. Note that this hook is - * invoked before any preprocessing. + * invoked before any variable preprocessing. * * @return array - * An array of theme suggestions. + * An array of alternate, more specific names for template files or theme + * functions. * * @see hook_theme_suggestions_HOOK_alter() */ function hook_theme_suggestions_HOOK(array $variables) { - $suggestions = []; - + $suggestions = array(); + // If the node language is French, this would provide node--fr.html.twig as an + // optional template override for French nodes. $suggestions[] = 'node__' . $variables['elements']['#langcode']; return $suggestions; @@ -687,25 +709,34 @@ function hook_theme_suggestions_alter(array &$suggestions, array $variables, $ho } /** - * Alters named suggestions for a specific theme hook. + * Alters available templates or theme function names for a theme hook. * * This hook allows any module or theme to provide alternative theme function or * template name suggestions and reorder or remove suggestions provided by * hook_theme_suggestions_HOOK() or by earlier invocations of this hook. * - * HOOK is the least-specific version of the hook being called. For example, if - * '#theme' => 'node__article' is called, then node_theme_suggestions_node() - * will be invoked, not node_theme_suggestions_node__article(). The specific - * hook called (in this case 'node__article') is available in - * $variables['theme_hook_original']. + * HOOK is the least-specific version of the hook being called, also known as + * the base theme hook. For example, if '#theme' => 'node__article' is called, + * then node_theme_suggestions_node() will be invoked, not + * node_theme_suggestions_node__article(). The specific hook called (in this + * case 'node__article') is available in $variables['theme_hook_original']. * - * @todo Add @code sample. + * The following example provides an optional alternative template of + * node--with-image.html.twig for nodes that an 'image' field with a value: + * @code + * function MYMODULE_theme_suggestions_node_alter(array &$suggestions, array $variables) { + * $node = $variables['elements']['#node']; + * if ($node->hasField('field_image') && $node->field_image->getValue()) { + * $suggestions[] = 'node__with_image'; + * } + * } + * @endcode * * @param array $suggestions * An array of theme suggestions. * @param array $variables * An array of variables passed to the theme hook. Note that this hook is - * invoked before any preprocessing. + * invoked before any variable preprocessing. * * @see hook_theme_suggestions_alter() * @see hook_theme_suggestions_HOOK() diff --git a/core/modules/system/src/Tests/Theme/ThemeSuggestionsAlterTest.php b/core/modules/system/src/Tests/Theme/ThemeSuggestionsAlterTest.php index c01a94f..c5050bf 100644 --- a/core/modules/system/src/Tests/Theme/ThemeSuggestionsAlterTest.php +++ b/core/modules/system/src/Tests/Theme/ThemeSuggestionsAlterTest.php @@ -64,6 +64,43 @@ public function testGeneralSuggestionsAlter() { } /** + * Tests that theme suggestion alter hooks work with an array of theme hooks. + */ + public function testArraySuggestionsAlter() { + $this->drupalGet('theme-test/array-suggestions-alter'); + $this->assertText('Template for testing suggestion alter hooks with an array of theme suggestions.'); + + // Enable test_theme, it contains a hook_theme_suggestions_HOOK_alter() + // implementation and template that we can test. + $this->config('system.theme') + ->set('default', 'test_theme') + ->save(); + + $this->drupalGet('theme-test/array-suggestions-alter'); + $this->assertText('Template overridden by adding a new suggestion via test_theme_theme_suggestions_theme_test_array_suggestions_alter().'); + } + + /** + * Tests that theme suggestion alter hooks work with an array of theme hooks. + * + * In this example no base hook is specified in the array of theme hooks/theme + * suggestions. + */ + public function testArraySuggestionsWithoutBaseAlter() { + $this->drupalGet('theme-test/array-suggestions-without-base-alter'); + $this->assertText('Template for testing suggestion alter hooks with an array of theme suggestions without specifying a base hook.'); + + // Enable test_theme, it contains a hook_theme_suggestions_HOOK_alter() + // implementation and template that we can test. + $this->config('system.theme') + ->set('default', 'test_theme') + ->save(); + + $this->drupalGet('theme-test/array-suggestions-without-base-alter'); + $this->assertText('Template overridden by adding a new suggestion via test_theme_theme_suggestions_theme_test_array_suggestions_without_base_alter().'); + } + + /** * Tests that theme suggestion alter hooks work for templates. */ public function testTemplateSuggestionsAlter() { diff --git a/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php b/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php index f1ed8f3..1533676 100644 --- a/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php +++ b/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php @@ -159,4 +159,32 @@ public function preprocessSuggestions() { ]; } + /** + * Menu callback for testing suggestion alter hooks with an array of hooks. + * + * In this example the base hook is specified as the first suggestion. + */ + public function arraySuggestionsAlter() { + return array('#theme' => array( + 'theme_test_array_suggestions', + 'theme_test_array_suggestions__first_suggestion', + 'theme_test_array_suggestions__second_suggestion', + )); + } + + /** + * Menu callback for testing suggestion alter hooks with an array of hooks. + * + * In this example no base hook is specified, but if no suggestions have been + * implemented as templates or theme functions, theme() will automatically + * determine the base hook, in this case + * 'theme_test_array_suggestions_without_base'. + */ + public function arraySuggestionsWithoutBaseAlter() { + return array('#theme' => array( + 'theme_test_array_suggestions_without_base__first_suggestion', + 'theme_test_array_suggestions_without_base__second_suggestion', + )); + } + } diff --git a/core/modules/system/tests/modules/theme_test/templates/theme-test-array-suggestions-without-base--first-suggestion.html.twig b/core/modules/system/tests/modules/theme_test/templates/theme-test-array-suggestions-without-base--first-suggestion.html.twig new file mode 100644 index 0000000..c0b4d04 --- /dev/null +++ b/core/modules/system/tests/modules/theme_test/templates/theme-test-array-suggestions-without-base--first-suggestion.html.twig @@ -0,0 +1,2 @@ +{# Output for Theme API test #} +This template should not be rendered. diff --git a/core/modules/system/tests/modules/theme_test/templates/theme-test-array-suggestions-without-base.html.twig b/core/modules/system/tests/modules/theme_test/templates/theme-test-array-suggestions-without-base.html.twig new file mode 100644 index 0000000..1f1e463 --- /dev/null +++ b/core/modules/system/tests/modules/theme_test/templates/theme-test-array-suggestions-without-base.html.twig @@ -0,0 +1,2 @@ +{# Output for Theme API test #} +Template for testing suggestion alter hooks with an array of theme suggestions without specifying a base hook. diff --git a/core/modules/system/tests/modules/theme_test/templates/theme-test-array-suggestions.html.twig b/core/modules/system/tests/modules/theme_test/templates/theme-test-array-suggestions.html.twig new file mode 100644 index 0000000..ec7c2fc --- /dev/null +++ b/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 alter hooks with an array of theme suggestions. diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module index 2e6f164..57a2a15 100644 --- a/core/modules/system/tests/modules/theme_test/theme_test.module +++ b/core/modules/system/tests/modules/theme_test/theme_test.module @@ -21,21 +21,27 @@ function theme_test_theme($existing, $type, $theme, $path) { ]; $items['theme_test_template_test_2'] = [ 'template' => 'theme_test.template_test', - ]; - $items['theme_test_suggestion_provided'] = [ - 'variables' => [], - ]; - $items['theme_test_specific_suggestions'] = [ - 'variables' => [], - ]; - $items['theme_test_suggestions'] = [ - 'variables' => [], - ]; - $items['theme_test_general_suggestions'] = [ - 'variables' => [], - ]; - $items['theme_test_function_suggestions'] = [ - 'variables' => [], + ); + $items['theme_test_suggestion_provided'] = array( + 'variables' => array(), + ); + $items['theme_test_specific_suggestions'] = array( + 'variables' => array(), + ); + $items['theme_test_suggestions'] = array( + 'variables' => array(), + ); + $items['theme_test_general_suggestions'] = array( + 'variables' => array(), + ); + $items['theme_test_array_suggestions'] = array( + 'variables' => array(), + ); + $items['theme_test_array_suggestions_without_base'] = array( + 'variables' => array(), + ); + $items['theme_test_function_suggestions'] = array( + 'variables' => array(), 'function' => 'theme_theme_test_function_suggestions', ]; $items['theme_test_suggestions_include'] = [ diff --git a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml index 1ff61cf..faae6ea 100644 --- a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml +++ b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml @@ -55,7 +55,7 @@ theme_test.request_listener: requirements: _access: 'TRUE' -suggestion_alter: +theme_test.suggestion_alter: path: '/theme-test/suggestion-alter' defaults: _controller: '\Drupal\theme_test\ThemeTestController::suggestionAlter' @@ -69,21 +69,21 @@ theme_test.general_suggestion_alter: requirements: _access: 'TRUE' -suggestion_provided: +theme_test.suggestion_provided: path: '/theme-test/suggestion-provided' defaults: _controller: '\Drupal\theme_test\ThemeTestController::suggestionProvided' requirements: _access: 'TRUE' -specific_suggestion_alter: +theme_test.specific_suggestion_alter: path: '/theme-test/specific-suggestion-alter' defaults: _controller: '\Drupal\theme_test\ThemeTestController::specificSuggestionAlter' requirements: _access: 'TRUE' -function_suggestion_alter: +theme_test.function_suggestion_alter: path: '/theme-test/function-suggestion-alter' defaults: _controller: '\Drupal\theme_test\ThemeTestController::functionSuggestionAlter' @@ -110,3 +110,17 @@ theme_test.preprocess_suggestions: _controller: '\Drupal\theme_test\ThemeTestController::preprocessSuggestions' requirements: _access: 'TRUE' + +theme_test.array_suggestions_alter: + path: '/theme-test/array-suggestions-alter' + defaults: + _controller: '\Drupal\theme_test\ThemeTestController::arraySuggestionsAlter' + requirements: + _access: 'TRUE' + +theme_test.array_suggestions_without_base_alter: + path: '/theme-test/array-suggestions-without-base-alter' + defaults: + _controller: '\Drupal\theme_test\ThemeTestController::arraySuggestionsWithoutBaseAlter' + requirements: + _access: 'TRUE' diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test-array-suggestions--first-suggestion.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test-array-suggestions--first-suggestion.html.twig new file mode 100644 index 0000000..c0b4d04 --- /dev/null +++ b/core/modules/system/tests/themes/test_theme/templates/theme-test-array-suggestions--first-suggestion.html.twig @@ -0,0 +1,2 @@ +{# Output for Theme API test #} +This template should not be rendered. diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test-array-suggestions--other-suggestion.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test-array-suggestions--other-suggestion.html.twig new file mode 100644 index 0000000..c653e56 --- /dev/null +++ b/core/modules/system/tests/themes/test_theme/templates/theme-test-array-suggestions--other-suggestion.html.twig @@ -0,0 +1,2 @@ +{# Output for Theme API test #} +Template overridden by adding a new suggestion via test_theme_theme_suggestions_theme_test_array_suggestions_alter(). diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test-array-suggestions--second-suggestion.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test-array-suggestions--second-suggestion.html.twig new file mode 100644 index 0000000..c0b4d04 --- /dev/null +++ b/core/modules/system/tests/themes/test_theme/templates/theme-test-array-suggestions--second-suggestion.html.twig @@ -0,0 +1,2 @@ +{# Output for Theme API test #} +This template should not be rendered. diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test-array-suggestions-without-base--other-suggestion.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test-array-suggestions-without-base--other-suggestion.html.twig new file mode 100644 index 0000000..a492623 --- /dev/null +++ b/core/modules/system/tests/themes/test_theme/templates/theme-test-array-suggestions-without-base--other-suggestion.html.twig @@ -0,0 +1,2 @@ +{# Output for Theme API test #} +Template overridden by adding a new suggestion via test_theme_theme_suggestions_theme_test_array_suggestions_without_base_alter(). 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 aa399b6..99ba45b 100644 --- a/core/modules/system/tests/themes/test_theme/test_theme.theme +++ b/core/modules/system/tests/themes/test_theme/test_theme.theme @@ -87,6 +87,22 @@ function test_theme_theme_suggestions_theme_test_function_suggestions_alter(arra } /** + * Implements hook_theme_suggestions_HOOK_alter(). + */ +function test_theme_theme_suggestions_theme_test_array_suggestions_alter(array &$suggestions, array $variables) { + // Add an alternative suggestion to the end. + $suggestions[] = 'theme_test_array_suggestions__' . 'other_suggestion'; +} + +/** + * Implements hook_theme_suggestions_HOOK_alter(). + */ +function test_theme_theme_suggestions_theme_test_array_suggestions_without_base_alter(array &$suggestions, array $variables) { + // Add an alternative suggestion to the end. + $suggestions[] = 'theme_test_array_suggestions_without_base__' . 'other_suggestion'; +} + +/** * Returns HTML for a theme function suggestion test. * * Implements the theme_test_function_suggestions__theme_override suggestion.