diff --git a/core/modules/system/src/Tests/Theme/ThemeSuggestionsAlterTest.php b/core/modules/system/src/Tests/Theme/ThemeSuggestionsAlterTest.php index 1db3583..d13e3a8 100644 --- a/core/modules/system/src/Tests/Theme/ThemeSuggestionsAlterTest.php +++ b/core/modules/system/src/Tests/Theme/ThemeSuggestionsAlterTest.php @@ -69,6 +69,43 @@ 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. */ 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 a8f16c6..e075066 100644 --- a/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php +++ b/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php @@ -143,4 +143,32 @@ public function nonHtml() { return new JsonResponse(['theme_initialized' => $theme_initialized]); } + /** + * 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 7ba5c2d..f43ad42 100644 --- a/core/modules/system/tests/modules/theme_test/theme_test.module +++ b/core/modules/system/tests/modules/theme_test/theme_test.module @@ -29,6 +29,12 @@ function theme_test_theme($existing, $type, $theme, $path) { $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', 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 2dbd188..eb378b2 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' @@ -103,3 +103,17 @@ theme_test.non_html: _controller: '\Drupal\theme_test\ThemeTestController::nonHtml' 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 9350755..43413fc 100644 --- a/core/modules/system/tests/themes/test_theme/test_theme.theme +++ b/core/modules/system/tests/themes/test_theme/test_theme.theme @@ -82,6 +82,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. diff --git a/core/modules/system/theme.api.php b/core/modules/system/theme.api.php index eef4f5c..8eec36a 100644 --- a/core/modules/system/theme.api.php +++ b/core/modules/system/theme.api.php @@ -510,31 +510,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 = 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; @@ -590,25 +612,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()