Commit 7e163dbb authored by catch's avatar catch

Issue #1751194 by Cottser, mikl, effulgentsia, benjifisher: Introduce...

Issue #1751194 by Cottser, mikl, effulgentsia, benjifisher: Introduce hook_theme_suggestions_HOOK() and hook_theme_suggestions_HOOK_alter().
parent 4bdcb12b
This diff is collapsed.
...@@ -24,6 +24,11 @@ public function getImplementations($hook) { ...@@ -24,6 +24,11 @@ public function getImplementations($hook) {
if (substr($hook, -6) === '_alter') { if (substr($hook, -6) === '_alter') {
return array(); return array();
} }
// theme() is called during updates and fires hooks, so whitelist the
// system module.
if (substr($hook, 0, 6) == 'theme_') {
return array('system');
}
switch ($hook) { switch ($hook) {
// hook_requirements is necessary for updates to work. // hook_requirements is necessary for updates to work.
case 'requirements': case 'requirements':
......
...@@ -484,6 +484,39 @@ function block_rebuild() { ...@@ -484,6 +484,39 @@ function block_rebuild() {
} }
} }
/**
* Implements hook_theme_suggestions_HOOK().
*/
function block_theme_suggestions_block(array $variables) {
$suggestions = array();
$suggestions[] = 'block__' . $variables['elements']['#configuration']['module'];
// Hyphens (-) and underscores (_) play a special role in theme suggestions.
// Theme suggestions should only contain underscores, because within
// drupal_find_theme_templates(), underscores are converted to hyphens to
// match template file names, and then converted back to underscores to match
// pre-processing and other function names. So if your theme suggestion
// contains a hyphen, it will end up as an underscore after this conversion,
// and your function names won't be recognized. So, we need to convert
// hyphens to underscores in block deltas for the theme suggestions.
// We can safely explode on : because we know the Block plugin type manager
// enforces that delimiter for all derivatives.
$parts = explode(':', $variables['elements']['#plugin_id']);
$suggestion = 'block';
while ($part = array_shift($parts)) {
$suggestions[] = $suggestion .= '__' . strtr($part, '-', '_');
}
if ($id = $variables['elements']['#block']->id()) {
$config_id = explode('.', $id);
$machine_name = array_pop($config_id);
$suggestions[] = 'block__' . $machine_name;
}
return $suggestions;
}
/** /**
* Prepares variables for block templates. * Prepares variables for block templates.
* *
...@@ -527,29 +560,11 @@ function template_preprocess_block(&$variables) { ...@@ -527,29 +560,11 @@ function template_preprocess_block(&$variables) {
// Add default class for block content. // Add default class for block content.
$variables['content_attributes']['class'][] = 'content'; $variables['content_attributes']['class'][] = 'content';
$variables['theme_hook_suggestions'][] = 'block__' . $variables['configuration']['module'];
// Hyphens (-) and underscores (_) play a special role in theme suggestions.
// Theme suggestions should only contain underscores, because within
// drupal_find_theme_templates(), underscores are converted to hyphens to
// match template file names, and then converted back to underscores to match
// pre-processing and other function names. So if your theme suggestion
// contains a hyphen, it will end up as an underscore after this conversion,
// and your function names won't be recognized. So, we need to convert
// hyphens to underscores in block deltas for the theme suggestions.
// We can safely explode on : because we know the Block plugin type manager
// enforces that delimiter for all derivatives.
$parts = explode(':', $variables['plugin_id']);
$suggestion = 'block';
while ($part = array_shift($parts)) {
$variables['theme_hook_suggestions'][] = $suggestion .= '__' . strtr($part, '-', '_');
}
// Create a valid HTML ID and make sure it is unique. // Create a valid HTML ID and make sure it is unique.
if ($id = $variables['elements']['#block']->id()) { if ($id = $variables['elements']['#block']->id()) {
$config_id = explode('.', $id); $config_id = explode('.', $id);
$machine_name = array_pop($config_id); $machine_name = array_pop($config_id);
$variables['attributes']['id'] = drupal_html_id('block-' . $machine_name); $variables['attributes']['id'] = drupal_html_id('block-' . $machine_name);
$variables['theme_hook_suggestions'][] = 'block__' . $machine_name;
} }
} }
......
<?php
/**
* @file
* Contains \Drupal\block\Tests\BlockPreprocessUnitTest.
*/
namespace Drupal\block\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Unit tests for template_preprocess_block().
*/
class BlockPreprocessUnitTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('block');
public static function getInfo() {
return array(
'name' => 'Block preprocess',
'description' => 'Test the template_preprocess_block() function.',
'group' => 'Block',
);
}
/**
* Tests block classes with template_preprocess_block().
*/
function testBlockClasses() {
// Define a block with a derivative to be preprocessed, which includes both
// an underscore (not transformed) and a hyphen (transformed to underscore),
// and generates possibilities for each level of derivative.
// @todo Clarify this comment.
$block = entity_create('block', array(
'plugin' => 'system_menu_block:admin',
'region' => 'footer',
'id' => \Drupal::config('system.theme')->get('default') . '.machinename',
));
$variables = array();
$variables['elements']['#block'] = $block;
$variables['elements']['#configuration'] = $block->getPlugin()->getConfiguration();
$variables['elements']['#plugin_id'] = $block->get('plugin');
$variables['elements']['content'] = array();
// Test adding a class to the block content.
$variables['content_attributes']['class'][] = 'test-class';
template_preprocess_block($variables);
$this->assertEqual($variables['content_attributes']['class'], array('test-class', 'content'), 'Default .content class added to block content_attributes');
}
}
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
use Drupal\simpletest\WebTestBase; use Drupal\simpletest\WebTestBase;
/** /**
* Unit tests for template_preprocess_block(). * Unit tests for block_theme_suggestions_block().
*/ */
class BlockTemplateSuggestionsUnitTest extends WebTestBase { class BlockTemplateSuggestionsUnitTest extends WebTestBase {
...@@ -24,13 +24,13 @@ class BlockTemplateSuggestionsUnitTest extends WebTestBase { ...@@ -24,13 +24,13 @@ class BlockTemplateSuggestionsUnitTest extends WebTestBase {
public static function getInfo() { public static function getInfo() {
return array( return array(
'name' => 'Block template suggestions', 'name' => 'Block template suggestions',
'description' => 'Test the template_preprocess_block() function.', 'description' => 'Test the block_theme_suggestions_block() function.',
'group' => 'Block', 'group' => 'Block',
); );
} }
/** /**
* Test if template_preprocess_block() handles the suggestions right. * Tests template suggestions from block_theme_suggestions_block().
*/ */
function testBlockThemeHookSuggestions() { function testBlockThemeHookSuggestions() {
// Define a block with a derivative to be preprocessed, which includes both // Define a block with a derivative to be preprocessed, which includes both
...@@ -48,11 +48,8 @@ function testBlockThemeHookSuggestions() { ...@@ -48,11 +48,8 @@ function testBlockThemeHookSuggestions() {
$variables['elements']['#configuration'] = $block->getPlugin()->getConfiguration(); $variables['elements']['#configuration'] = $block->getPlugin()->getConfiguration();
$variables['elements']['#plugin_id'] = $block->get('plugin'); $variables['elements']['#plugin_id'] = $block->get('plugin');
$variables['elements']['content'] = array(); $variables['elements']['content'] = array();
// Test adding a class to the block content. $suggestions = block_theme_suggestions_block($variables);
$variables['content_attributes']['class'][] = 'test-class'; $this->assertEqual($suggestions, array('block__system', 'block__system_menu_block', 'block__system_menu_block__admin', 'block__machinename'));
template_preprocess_block($variables);
$this->assertEqual($variables['theme_hook_suggestions'], array('block__system', 'block__system_menu_block', 'block__system_menu_block__admin', 'block__machinename'));
$this->assertEqual($variables['content_attributes']['class'], array('test-class', 'content'), 'Default .content class added to block content_attributes');
} }
} }
...@@ -659,6 +659,22 @@ function field_page_build(&$page) { ...@@ -659,6 +659,22 @@ function field_page_build(&$page) {
$page['#attached']['css'][$path . '/css/field.module.css'] = array('every_page' => TRUE); $page['#attached']['css'][$path . '/css/field.module.css'] = array('every_page' => TRUE);
} }
/**
* Implements hook_theme_suggestions_HOOK().
*/
function field_theme_suggestions_field(array $variables) {
$suggestions = array();
$element = $variables['element'];
$suggestions[] = 'field__' . $element['#field_type'];
$suggestions[] = 'field__' . $element['#field_name'];
$suggestions[] = 'field__' . $element['#entity_type'] . '__' . $element['#bundle'];
$suggestions[] = 'field__' . $element['#entity_type'] . '__' . $element['#field_name'];
$suggestions[] = 'field__' . $element['#entity_type'] . '__' . $element['#field_name'] . '__' . $element['#bundle'];
return $suggestions;
}
/** /**
* Prepares variables for field templates. * Prepares variables for field templates.
* *
...@@ -716,15 +732,6 @@ function template_preprocess_field(&$variables, $hook) { ...@@ -716,15 +732,6 @@ function template_preprocess_field(&$variables, $hook) {
$variables['attributes']['class'][] = 'clearfix'; $variables['attributes']['class'][] = 'clearfix';
} }
// Add specific suggestions that can override the default implementation.
$variables['theme_hook_suggestions'] = array(
'field__' . $element['#field_type'],
'field__' . $element['#field_name'],
'field__' . $element['#entity_type'] . '__' . $element['#bundle'],
'field__' . $element['#entity_type'] . '__' . $element['#field_name'],
'field__' . $element['#entity_type'] . '__' . $element['#field_name'] . '__' . $element['#bundle'],
);
static $default_attributes; static $default_attributes;
if (!isset($default_attributes)) { if (!isset($default_attributes)) {
$default_attributes = new Attribute; $default_attributes = new Attribute;
......
...@@ -588,6 +588,33 @@ function forum_preprocess_block(&$variables) { ...@@ -588,6 +588,33 @@ function forum_preprocess_block(&$variables) {
} }
} }
/**
* Implements hook_theme_suggestions_HOOK().
*/
function forum_theme_suggestions_forums(array $variables) {
$suggestions = array();
$tid = $variables['term']->id();
// Provide separate template suggestions based on what's being output. Topic
// ID is also accounted for. Check both variables to be safe then the inverse.
// Forums with topic IDs take precedence.
if ($variables['forums'] && !$variables['topics']) {
$suggestions[] = 'forums__containers';
$suggestions[] = 'forums__' . $tid;
$suggestions[] = 'forums__containers__' . $tid;
}
elseif (!$variables['forums'] && $variables['topics']) {
$suggestions[] = 'forums__topics';
$suggestions[] = 'forums__' . $tid;
$suggestions[] = 'forums__topics__' . $tid;
}
else {
$suggestions[] = 'forums__' . $tid;
}
return $suggestions;
}
/** /**
* Prepares variables for forums templates. * Prepares variables for forums templates.
* *
...@@ -635,23 +662,6 @@ function template_preprocess_forums(&$variables) { ...@@ -635,23 +662,6 @@ function template_preprocess_forums(&$variables) {
else { else {
$variables['topics'] = array(); $variables['topics'] = array();
} }
// Provide separate template suggestions based on what's being output. Topic id is also accounted for.
// Check both variables to be safe then the inverse. Forums with topic ID's take precedence.
if ($variables['forums'] && !$variables['topics']) {
$variables['theme_hook_suggestions'][] = 'forums__containers';
$variables['theme_hook_suggestions'][] = 'forums__' . $variables['tid'];
$variables['theme_hook_suggestions'][] = 'forums__containers__' . $variables['tid'];
}
elseif (!$variables['forums'] && $variables['topics']) {
$variables['theme_hook_suggestions'][] = 'forums__topics';
$variables['theme_hook_suggestions'][] = 'forums__' . $variables['tid'];
$variables['theme_hook_suggestions'][] = 'forums__topics__' . $variables['tid'];
}
else {
$variables['theme_hook_suggestions'][] = 'forums__' . $variables['tid'];
}
} }
else { else {
$variables['forums'] = array(); $variables['forums'] = array();
......
...@@ -641,6 +641,19 @@ function node_preprocess_block(&$variables) { ...@@ -641,6 +641,19 @@ function node_preprocess_block(&$variables) {
} }
} }
/**
* Implements hook_theme_suggestions_HOOK().
*/
function node_theme_suggestions_node(array $variables) {
$suggestions = array();
$node = $variables['elements']['#node'];
$suggestions[] = 'node__' . $node->bundle();
$suggestions[] = 'node__' . $node->id();
return $suggestions;
}
/** /**
* Prepares variables for node templates. * Prepares variables for node templates.
* *
...@@ -730,11 +743,6 @@ function template_preprocess_node(&$variables) { ...@@ -730,11 +743,6 @@ function template_preprocess_node(&$variables) {
if (isset($variables['preview'])) { if (isset($variables['preview'])) {
$variables['attributes']['class'][] = 'preview'; $variables['attributes']['class'][] = 'preview';
} }
// Clean up name so there are no underscores.
$variables['theme_hook_suggestions'][] = 'node__' . $node->bundle();
$variables['theme_hook_suggestions'][] = 'node__' . $node->id();
$variables['content_attributes']['class'][] = 'content'; $variables['content_attributes']['class'][] = 'content';
} }
......
...@@ -74,6 +74,13 @@ function search_view($plugin_id = NULL, $keys = '') { ...@@ -74,6 +74,13 @@ function search_view($plugin_id = NULL, $keys = '') {
return $build; return $build;
} }
/**
* Implements hook_theme_suggestions_HOOK().
*/
function search_theme_suggestions_search_results(array $variables) {
return array('search_results__' . $variables['plugin_id']);
}
/** /**
* Prepares variables for search results templates. * Prepares variables for search results templates.
* *
...@@ -100,7 +107,13 @@ function template_preprocess_search_results(&$variables) { ...@@ -100,7 +107,13 @@ function template_preprocess_search_results(&$variables) {
// @todo Revisit where this help text is added, see also // @todo Revisit where this help text is added, see also
// http://drupal.org/node/1918856. // http://drupal.org/node/1918856.
$variables['help'] = search_help('search#noresults', drupal_help_arg()); $variables['help'] = search_help('search#noresults', drupal_help_arg());
$variables['theme_hook_suggestions'][] = 'search_results__' . $variables['plugin_id']; }
/**
* Implements hook_theme_suggestions_HOOK().
*/
function search_theme_suggestions_search_result(array $variables) {
return array('search_result__' . $variables['plugin_id']);
} }
/** /**
...@@ -148,7 +161,6 @@ function template_preprocess_search_result(&$variables) { ...@@ -148,7 +161,6 @@ function template_preprocess_search_result(&$variables) {
// Provide separated and grouped meta information.. // Provide separated and grouped meta information..
$variables['info_split'] = $info; $variables['info_split'] = $info;
$variables['info'] = implode(' - ', $info); $variables['info'] = implode(' - ', $info);
$variables['theme_hook_suggestions'][] = 'search_result__' . $variables['plugin_id'];
} }
/** /**
......
<?php
/**
* @file
* Contains \Drupal\system\Tests\Theme\ThemeSuggestionsAlterTest.
*/
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\WebTestBase;
/**
* Tests theme suggestion alter hooks.
*/
class ThemeSuggestionsAlterTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('theme_test');
public static function getInfo() {
return array(
'name' => 'Theme suggestions alter',
'description' => 'Test theme suggestion alter hooks.',
'group' => 'Theme',
);
}
function setUp() {
parent::setUp();
theme_enable(array('test_theme'));
}
/**
* Tests that hooks to provide theme suggestions work.
*/
function testTemplateSuggestions() {
$this->drupalGet('theme-test/suggestion-provided');
$this->assertText('Template for testing suggestions provided by the module declaring the theme hook.');
// Enable test_theme, it contains a template suggested by theme_test.module
// in theme_test_theme_suggestions_theme_test_suggestion_provided().
config('system.theme')
->set('default', 'test_theme')
->save();
$this->drupalGet('theme-test/suggestion-provided');
$this->assertText('Template overridden based on suggestion provided by the module declaring the theme hook.');
}
/**
* Tests that theme suggestion alter hooks work for templates.
*/
function testTemplateSuggestionsAlter() {
$this->drupalGet('theme-test/suggestion-alter');
$this->assertText('Original template.');
// 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.');
// 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.');
}
/**
* Tests that theme suggestion alter hooks work for specific theme calls.
*/
function testSpecificSuggestionsAlter() {
// Test that the default template is rendered.
$this->drupalGet('theme-test/specific-suggestion-alter');
$this->assertText('Template for testing specific theme calls.');
config('system.theme')
->set('default', 'test_theme')
->save();
// Test a specific theme call similar to '#theme' => 'node__article'.
$this->drupalGet('theme-test/specific-suggestion-alter');
$this->assertText('Template matching the specific theme call.');
$this->assertText('theme_test_specific_suggestions__variant', 'Specific theme call is added to the suggestions array.');
// Ensure that the base hook is used to determine the suggestion alter hook.
\Drupal::moduleHandler()->install(array('theme_suggestions_test'));
$this->drupalGet('theme-test/specific-suggestion-alter');
$this->assertText('Template overridden based on suggestion alter hook determined by the base hook.');
$this->assertTrue(strpos($this->drupalGetContent(), 'theme_test_specific_suggestions__variant') < strpos($this->drupalGetContent(), 'theme_test_specific_suggestions__variant__foo'), 'Specific theme call is added to the suggestions array before the suggestions alter hook.');
}
/**
* Tests that theme suggestion alter hooks work for theme functions.
*/
function testThemeFunctionSuggestionsAlter() {
$this->drupalGet('theme-test/function-suggestion-alter');
$this->assertText('Original theme function.');
// Enable test_theme and test that themes can alter theme suggestions.
config('system.theme')
->set('default', 'test_theme')
->save();
$this->drupalGet('theme-test/function-suggestion-alter');
$this->assertText('Theme function overridden based on new theme suggestion provided by the test_theme theme.');
// Enable the theme_suggestions_test module to test modules implementing
// suggestions alter hooks.
\Drupal::moduleHandler()->install(array('theme_suggestions_test'));
$this->drupalGet('theme-test/function-suggestion-alter');
$this->assertText('Theme function overridden based on new theme suggestion provided by a module.');
}
}
...@@ -909,6 +909,54 @@ function system_menu() { ...@@ -909,6 +909,54 @@ function system_menu() {
return $items; return $items;
} }
/**
* Implements hook_theme_suggestions_HOOK().
*/
function system_theme_suggestions_html(array $variables) {
return theme_get_suggestions(arg(), 'html');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function system_theme_suggestions_page(array $variables) {
return theme_get_suggestions(arg(), 'page');
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function system_theme_suggestions_maintenance_page(array $variables) {
$suggestions = array();
// Dead databases will show error messages so supplying this template will
// allow themers to override the page and the content completely.
$offline = defined('MAINTENANCE_MODE');
try {
drupal_is_front_page();
}
catch (Exception $e) {
// The database is not yet available.
$offline = TRUE;
}
if ($offline) {
$suggestions[] = 'maintenance_page__offline';
}
return $suggestions;
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function system_theme_suggestions_region(array $variables) {
$suggestions = array();
if (!empty($variables['elements']['#region'])) {
$suggestions[] = 'region__' . $variables['elements']['#region'];
}
return $suggestions;
}
/** /**
* Theme callback for the default batch page. * Theme callback for the default batch page.
*/ */
......
name: 'Theme suggestions test'
type: module
description: 'Support module for testing theme suggestions.'
package: Testing
version: VERSION
core: 8.x
hidden: true
<?php
/**
* @file
* Support module for testing theme suggestions.
*/