From 4fbb1a1be58f01c54611f368cd4df29dac10bcbf Mon Sep 17 00:00:00 2001 From: webchick <webchick@24967.no-reply.drupal.org> Date: Wed, 22 Jan 2014 00:31:03 -0800 Subject: [PATCH] Issue #2160735 by Cottser, aspilicious: Add hook_theme_suggestions_alter(). --- core/includes/theme.inc | 10 +++- .../Tests/Theme/ThemeSuggestionsAlterTest.php | 57 ++++++++++++++++++- .../theme_suggestions_test.module | 11 ++++ .../Drupal/theme_test/ThemeTestController.php | 7 +++ .../theme-test-general-suggestions.html.twig | 2 + .../theme-test-suggestions.html.twig | 2 +- .../modules/theme_test/theme_test.module | 18 ++++++ .../modules/theme_test/theme_test.routing.yml | 7 +++ ...ral-suggestions--module-override.html.twig | 2 + ...eral-suggestions--theme-override.html.twig | 2 + ...est-suggestions--module-override.html.twig | 2 +- ...test-suggestions--theme-override.html.twig | 2 +- .../tests/themes/test_theme/test_theme.theme | 15 +++++ core/modules/system/theme.api.php | 50 ++++++++++++++++ 14 files changed, 179 insertions(+), 8 deletions(-) create mode 100644 core/modules/system/tests/modules/theme_test/templates/theme-test-general-suggestions.html.twig create mode 100644 core/modules/system/tests/themes/test_theme/templates/theme-test-general-suggestions--module-override.html.twig create mode 100644 core/modules/system/tests/themes/test_theme/templates/theme-test-general-suggestions--theme-override.html.twig diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 5402daeb62f4..ef54c93c5f4b 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -627,8 +627,14 @@ function theme($hook, $variables = array()) { if (isset($info['base hook'])) { $suggestions[] = $hook; } - // Allow suggestions to be altered via hook_theme_suggestions_HOOK_alter(). - Drupal::moduleHandler()->alter('theme_suggestions_' . $base_theme_hook, $suggestions, $variables); + + // Invoke hook_theme_suggestions_alter() and + // hook_theme_suggestions_HOOK_alter(). + $hooks = array( + 'theme_suggestions', + 'theme_suggestions_' . $base_theme_hook, + ); + \Drupal::moduleHandler()->alter($hooks, $suggestions, $variables, $base_theme_hook); // Check if each suggestion exists in the theme registry, and if so, // use it instead of the hook that theme() was called with. For example, a diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeSuggestionsAlterTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeSuggestionsAlterTest.php index 00cd0081aeac..8f6e9b6f1f60 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeSuggestionsAlterTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeSuggestionsAlterTest.php @@ -51,25 +51,46 @@ function testTemplateSuggestions() { $this->assertText('Template overridden based on suggestion provided by the module declaring the theme hook.'); } + /** + * Tests hook_theme_suggestions_alter(). + */ + function testGeneralSuggestionsAlter() { + $this->drupalGet('theme-test/general-suggestion-alter'); + $this->assertText('Original template for testing hook_theme_suggestions_alter().'); + + // Enable test_theme and test that themes can alter template suggestions. + config('system.theme') + ->set('default', 'test_theme') + ->save(); + $this->drupalGet('theme-test/general-suggestion-alter'); + $this->assertText('Template overridden based on new theme suggestion provided by the test_theme theme via hook_theme_suggestions_alter().'); + + // Enable the theme_suggestions_test module to test modules implementing + // suggestions alter hooks. + \Drupal::moduleHandler()->install(array('theme_suggestions_test')); + $this->drupalGet('theme-test/general-suggestion-alter'); + $this->assertText('Template overridden based on new theme suggestion provided by a module via hook_theme_suggestions_alter().'); + } + /** * Tests that theme suggestion alter hooks work for templates. */ function testTemplateSuggestionsAlter() { $this->drupalGet('theme-test/suggestion-alter'); - $this->assertText('Original template.'); + $this->assertText('Original template for testing hook_theme_suggestions_HOOK_alter().'); // Enable test_theme and test that themes can alter template suggestions. config('system.theme') ->set('default', 'test_theme') ->save(); $this->drupalGet('theme-test/suggestion-alter'); - $this->assertText('Template overridden based on new theme suggestion provided by the test_theme theme.'); + $this->assertText('Template overridden based on new theme suggestion provided by the test_theme theme via hook_theme_suggestions_HOOK_alter().'); // Enable the theme_suggestions_test module to test modules implementing // suggestions alter hooks. \Drupal::moduleHandler()->install(array('theme_suggestions_test')); $this->drupalGet('theme-test/suggestion-alter'); - $this->assertText('Template overridden based on new theme suggestion provided by a module.'); + $this->assertText('Template overridden based on new theme suggestion provided by a module via hook_theme_suggestions_HOOK_alter().'); } /** @@ -117,4 +138,34 @@ function testThemeFunctionSuggestionsAlter() { $this->assertText('Theme function overridden based on new theme suggestion provided by a module.'); } + /** + * Tests execution order of theme suggestion alter hooks. + * + * hook_theme_suggestions_alter() should fire before + * hook_theme_suggestions_HOOK_alter() within an extension (module or theme). + */ + function testExecutionOrder() { + // Enable our test theme and module. + config('system.theme') + ->set('default', 'test_theme') + ->save(); + \Drupal::moduleHandler()->install(array('theme_suggestions_test')); + + // Send two requests so that we get all the messages we've set via + // drupal_set_message(). + $this->drupalGet('theme-test/suggestion-alter'); + // Ensure that the order is first by extension, then for a given extension, + // the hook-specific one after the generic one. + $expected = array( + 'theme_suggestions_test_theme_suggestions_alter() executed.', + 'theme_suggestions_test_theme_suggestions_theme_test_suggestions_alter() executed.', + 'theme_test_theme_suggestions_alter() executed.', + 'theme_test_theme_suggestions_theme_test_suggestions_alter() executed.', + 'test_theme_theme_suggestions_alter() executed.', + 'test_theme_theme_suggestions_theme_test_suggestions_alter() executed.', + ); + $content = preg_replace('/\s+/', ' ', filter_xss($this->content, array())); + $this->assert(strpos($content, implode(' ', $expected)) !== FALSE, 'Suggestion alter hooks executed in the expected order.'); + } + } diff --git a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module index 9ecaeebd63bc..1b19fee5edcb 100644 --- a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module +++ b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module @@ -5,10 +5,21 @@ * Support module for testing theme suggestions. */ +/** + * Implements hook_theme_suggestions_alter(). + */ +function theme_suggestions_test_theme_suggestions_alter(array &$suggestions, array $variables, $hook) { + drupal_set_message(__FUNCTION__ . '() executed.'); + if ($hook == 'theme_test_general_suggestions') { + $suggestions[] = $hook . '__module_override'; + } +} + /** * Implements hook_theme_suggestions_HOOK_alter(). */ function theme_suggestions_test_theme_suggestions_theme_test_suggestions_alter(array &$suggestions, array $variables) { + drupal_set_message(__FUNCTION__ . '() executed.'); $suggestions[] = 'theme_test_suggestions__' . 'module_override'; } diff --git a/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php b/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php index 8da6c62206c0..29bfa4eff5bd 100644 --- a/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php +++ b/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php @@ -106,6 +106,13 @@ function suggestionAlter() { return array('#theme' => 'theme_test_suggestions'); } + /** + * Menu callback for testing hook_theme_suggestions_alter(). + */ + function generalSuggestionAlter() { + return array('#theme' => 'theme_test_general_suggestions'); + } + /** * Menu callback for testing suggestion alter hooks with specific suggestions. */ diff --git a/core/modules/system/tests/modules/theme_test/templates/theme-test-general-suggestions.html.twig b/core/modules/system/tests/modules/theme_test/templates/theme-test-general-suggestions.html.twig new file mode 100644 index 000000000000..086eb47911ca --- /dev/null +++ b/core/modules/system/tests/modules/theme_test/templates/theme-test-general-suggestions.html.twig @@ -0,0 +1,2 @@ +{# Output for Theme API test #} +Original template for testing hook_theme_suggestions_alter(). diff --git a/core/modules/system/tests/modules/theme_test/templates/theme-test-suggestions.html.twig b/core/modules/system/tests/modules/theme_test/templates/theme-test-suggestions.html.twig index dfc848c94d9c..42bb3b94e93c 100644 --- a/core/modules/system/tests/modules/theme_test/templates/theme-test-suggestions.html.twig +++ b/core/modules/system/tests/modules/theme_test/templates/theme-test-suggestions.html.twig @@ -1,2 +1,2 @@ {# Output for Theme API test #} -Original template. +Original template for testing hook_theme_suggestions_HOOK_alter(). 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 b8a528d4a879..bdb1404695a6 100644 --- a/core/modules/system/tests/modules/theme_test/theme_test.module +++ b/core/modules/system/tests/modules/theme_test/theme_test.module @@ -26,6 +26,10 @@ function theme_test_theme($existing, $type, $theme, $path) { 'template' => 'theme-test-suggestions', 'variables' => array(), ); + $items['theme_test_general_suggestions'] = array( + 'template' => 'theme-test-general-suggestions', + 'variables' => array(), + ); $items['theme_test_function_suggestions'] = array( 'variables' => array(), ); @@ -152,3 +156,17 @@ function theme_theme_test_function_suggestions($variables) { function theme_test_theme_suggestions_theme_test_suggestion_provided(array $variables) { return array('theme_test_suggestion_provided__' . 'foo'); } + +/** + * Implements hook_theme_suggestions_alter(). + */ +function theme_test_theme_suggestions_alter(array &$suggestions, array $variables, $hook) { + drupal_set_message(__FUNCTION__ . '() executed.'); +} + +/** + * Implements hook_theme_suggestions_HOOK_alter(). + */ +function theme_test_theme_suggestions_theme_test_suggestions_alter(array &$suggestions, array $variables) { + drupal_set_message(__FUNCTION__ . '() executed.'); +} 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 9f08d5b0c56a..6b78b13516fc 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 @@ -65,6 +65,13 @@ suggestion_alter: requirements: _permission: 'access content' +theme_test.general_suggestion_alter: + path: '/theme-test/general-suggestion-alter' + defaults: + _content: '\Drupal\theme_test\ThemeTestController::generalSuggestionAlter' + requirements: + _permission: 'access content' + suggestion_provided: path: '/theme-test/suggestion-provided' defaults: diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test-general-suggestions--module-override.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test-general-suggestions--module-override.html.twig new file mode 100644 index 000000000000..5969332bcb04 --- /dev/null +++ b/core/modules/system/tests/themes/test_theme/templates/theme-test-general-suggestions--module-override.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_alter(). diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test-general-suggestions--theme-override.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test-general-suggestions--theme-override.html.twig new file mode 100644 index 000000000000..29322daeabce --- /dev/null +++ b/core/modules/system/tests/themes/test_theme/templates/theme-test-general-suggestions--theme-override.html.twig @@ -0,0 +1,2 @@ +{# Output for Theme API test #} +Template overridden based on new theme suggestion provided by the test_theme theme via hook_theme_suggestions_alter(). diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--module-override.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--module-override.html.twig index 26ce57bdae28..2a005cbc6c66 100644 --- a/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--module-override.html.twig +++ b/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--module-override.html.twig @@ -1,2 +1,2 @@ {# Output for Theme API test #} -Template overridden based on new theme suggestion provided by a module. +Template overridden based on new theme suggestion provided by a module via hook_theme_suggestions_HOOK_alter(). diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--theme-override.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--theme-override.html.twig index dee829f9750e..45162387fb03 100644 --- a/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--theme-override.html.twig +++ b/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--theme-override.html.twig @@ -1,2 +1,2 @@ {# Output for Theme API test #} -Template overridden based on new theme suggestion provided by the test_theme theme. +Template overridden based on new theme suggestion provided by the test_theme theme via hook_theme_suggestions_HOOK_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 9b10b2b03d23..13cb2b9bc908 100644 --- a/core/modules/system/tests/themes/test_theme/test_theme.theme +++ b/core/modules/system/tests/themes/test_theme/test_theme.theme @@ -30,10 +30,25 @@ function test_theme_theme_test_alter_alter(&$data) { $data = 'test_theme_theme_test_alter_alter was invoked'; } +/** + * Implements hook_theme_suggestions_alter(). + */ +function test_theme_theme_suggestions_alter(array &$suggestions, array $variables, $hook) { + drupal_set_message(__FUNCTION__ . '() executed.'); + // Theme alter hooks run after module alter hooks, so add this theme + // suggestion to the beginning of the array so that the suggestion added by + // the theme_suggestions_test module can be picked up when that module is + // enabled. + if ($hook == 'theme_test_general_suggestions') { + array_unshift($suggestions, 'theme_test_general_suggestions__' . 'theme_override'); + } +} + /** * Implements hook_theme_suggestions_HOOK_alter(). */ function test_theme_theme_suggestions_theme_test_suggestions_alter(array &$suggestions, array $variables) { + drupal_set_message(__FUNCTION__ . '() executed.'); // Theme alter hooks run after module alter hooks, so add this theme // suggestion to the beginning of the array so that the suggestion added by // the theme_suggestions_test module can be picked up when that module is diff --git a/core/modules/system/theme.api.php b/core/modules/system/theme.api.php index 34758dafda12..db11a21c4048 100644 --- a/core/modules/system/theme.api.php +++ b/core/modules/system/theme.api.php @@ -189,6 +189,55 @@ function hook_theme_suggestions_HOOK(array $variables) { return $suggestions; } +/** + * Alters named suggestions for all theme hooks. + * + * This hook is invoked for all theme hooks, if you are targeting a specific + * theme hook it's best to use hook_theme_suggestions_HOOK_alter(). + * + * The call order is as follows: all existing suggestion alter functions are + * called for module A, then all for module B, etc., followed by all for any + * base theme(s), and finally for the active theme. The order is + * determined by system weight, then by extension (module or theme) name. + * + * Within each module or theme, suggestion alter hooks are called in the + * following order: first, hook_theme_suggestions_alter(); second, + * hook_theme_suggestions_HOOK_alter(). So, for each module or theme, the more + * general hooks are called first followed by the more specific. + * + * In the following example, we provide an alternative template suggestion to + * node and taxonomy term templates based on the user being logged in. + * @code + * function MYMODULE_theme_suggestions_alter(array &$suggestions, array $variables, $hook) { + * if (\Drupal::currentUser()->isAuthenticated() && in_array($hook, array('node', 'taxonomy_term'))) { + * $suggestions[] = $hook . '__' . 'logged_in'; + * } + * } + * + * @endcode + * + * @param array $suggestions + * An array of alternate, more specific names for template files or theme + * functions. + * @param array $variables + * An array of variables passed to the theme hook. Note that this hook is + * invoked before any variable preprocessing. + * @param string $hook + * The base hook name. For example, if '#theme' => 'node__article' is called, + * then $hook will be 'node', not 'node__article'. The specific hook called + * (in this case 'node__article') is available in + * $variables['theme_hook_original']. + * + * @return array + * An array of theme suggestions. + * + * @see hook_theme_suggestions_HOOK_alter() + */ +function hook_theme_suggestions_alter(array &$suggestions, array $variables, $hook) { + // Add an interface-language specific suggestion to all theme hooks. + $suggestions[] = $hook . '__' . \Drupal::languageManager()->getLanguage()->id; +} + /** * Alters named suggestions for a specific theme hook. * @@ -210,6 +259,7 @@ function hook_theme_suggestions_HOOK(array $variables) { * An array of variables passed to the theme hook. Note that this hook is * invoked before any preprocessing. * + * @see hook_theme_suggestions_alter() * @see hook_theme_suggestions_HOOK() */ function hook_theme_suggestions_HOOK_alter(array &$suggestions, array $variables) { -- GitLab