diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php index 6d2caf7..1b7555c 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php @@ -172,6 +172,7 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco 'contexts' => $entity->getCacheContexts(), 'max-age' => $entity->getCacheMaxAge(), ), + '#entity_type' => $this->entityTypeId, ); // Cache the rendered output if permitted by the view mode and global entity @@ -327,6 +328,30 @@ public function buildComponents(array &$build, array $entities, array $displays, } /** + * {@inheritdoc} + */ + public function getTemplateSuggestions(EntityInterface $entity, $view_mode = 'default') { + $entity_type_id = $entity->getEntityTypeId(); + $bundle_name = $entity->bundle(); + $entity_id = $entity->id(); + $sanitized_view_mode = strtr($view_mode, '.', '_'); + + $suggestions = array(); + $suggestions[] = $entity_type_id . '__' . $sanitized_view_mode; + + // Add suggestions with bundle_name only if bundles exists on entity type. + if ($entity->getEntityType()->getKey('bundle')) { + $suggestions[] = $entity_type_id . '__' . $bundle_name; + $suggestions[] = $entity_type_id . '__' . $bundle_name . '__' . $sanitized_view_mode; + } + + $suggestions[] = $entity_type_id . '__' . $entity_id; + $suggestions[] = $entity_type_id . '__' . $entity_id . '__' . $sanitized_view_mode; + + return $suggestions; + } + + /** * Specific per-entity building. * * @param array $build diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php b/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php index c3e167a..0c9b55f 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php @@ -36,6 +36,19 @@ public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL); /** + * Returns a list of template suggestions from an entity and the view mode. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity to get the template suggestions. + * @param string $view_mode + * The view mode to render the entity. + * + * @return array + * The list of template suggestions. + */ + public function getTemplateSuggestions(EntityInterface $entity, $view_mode = 'default'); + + /** * Returns the render array for the provided entity. * * @param \Drupal\Core\Entity\EntityInterface $entity diff --git a/core/modules/block/block.module b/core/modules/block/block.module index 9d5f406..42549bd 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -188,8 +188,9 @@ function block_rebuild() { */ function block_theme_suggestions_block(array $variables) { $suggestions = array(); + $provider = $variables['elements']['#configuration']['provider']; - $suggestions[] = 'block__' . $variables['elements']['#configuration']['provider']; + $suggestions[] = 'block__' . $provider; // 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 @@ -199,16 +200,25 @@ function block_theme_suggestions_block(array $variables) { // 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 ($provider == 'block_content') { + $entity = $variables['elements']['content']['#block_content']; + $view_mode = $variables['elements']['#configuration']['block_content']['view_mode']; + $entity_suggestions = \Drupal::entityManager()->getViewBuilder('block_content')->getTemplateSuggestions($entity, $view_mode); + + $suggestions = array_merge($suggestions, $entity_suggestions); } + else { + // 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 (!empty($variables['elements']['#id'])) { - $suggestions[] = 'block__' . $variables['elements']['#id']; + if (!empty($variables['elements']['#id'])) { + $suggestions[] = 'block__' . $variables['elements']['#id']; + } } return $suggestions; diff --git a/core/modules/block/src/BlockViewBuilder.php b/core/modules/block/src/BlockViewBuilder.php index 216dfa8..b15b39a 100644 --- a/core/modules/block/src/BlockViewBuilder.php +++ b/core/modules/block/src/BlockViewBuilder.php @@ -27,6 +27,12 @@ public function buildComponents(array &$build, array $entities, array $displays, /** * {@inheritdoc} */ + public function getTemplateSuggestions(EntityInterface $entity, $view_mode = 'default') { + } + + /** + * {@inheritdoc} + */ public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) { $build = $this->viewMultiple(array($entity), $view_mode, $langcode); return reset($build); diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 4550a6d..ffa69c7 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -538,23 +538,6 @@ function node_preprocess_block(&$variables) { } /** - * Implements hook_theme_suggestions_HOOK(). - */ -function node_theme_suggestions_node(array $variables) { - $suggestions = array(); - $node = $variables['elements']['#node']; - $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_'); - - $suggestions[] = 'node__' . $sanitized_view_mode; - $suggestions[] = 'node__' . $node->bundle(); - $suggestions[] = 'node__' . $node->bundle() . '__' . $sanitized_view_mode; - $suggestions[] = 'node__' . $node->id(); - $suggestions[] = 'node__' . $node->id() . '__' . $sanitized_view_mode; - - return $suggestions; -} - -/** * Prepares variables for node templates. * * Default template: node.html.twig. diff --git a/core/modules/node/src/Tests/NodeTemplateSuggestionsTest.php b/core/modules/node/src/Tests/NodeTemplateSuggestionsTest.php index fef6aa0..9af0624 100644 --- a/core/modules/node/src/Tests/NodeTemplateSuggestionsTest.php +++ b/core/modules/node/src/Tests/NodeTemplateSuggestionsTest.php @@ -26,16 +26,19 @@ function testNodeThemeHookSuggestions() { $build = \Drupal::entityManager()->getViewBuilder('node')->view($node, $view_mode); $variables['elements'] = $build; - $suggestions = \Drupal::moduleHandler()->invokeAll('theme_suggestions_node', array($variables)); + $suggestions = array(); + $hook = 'node'; + \Drupal::moduleHandler()->alter('theme_suggestions', $suggestions, $variables, $hook); $this->assertEqual($suggestions, array('node__full', 'node__page', 'node__page__full', 'node__' . $node->id(), 'node__' . $node->id() . '__full'), 'Found expected node suggestions.'); // Change the view mode. $view_mode = 'node.my_custom_view_mode'; $build = \Drupal::entityManager()->getViewBuilder('node')->view($node, $view_mode); - $variables['elements'] = $build; - $suggestions = \Drupal::moduleHandler()->invokeAll('theme_suggestions_node', array($variables)); + + $suggestions = array(); + \Drupal::moduleHandler()->alter('theme_suggestions', $suggestions, $variables, $hook); $this->assertEqual($suggestions, array('node__node_my_custom_view_mode', 'node__page', 'node__page__node_my_custom_view_mode', 'node__' . $node->id(), 'node__' . $node->id() . '__node_my_custom_view_mode'), 'Found expected node suggestions.'); } diff --git a/core/modules/system/src/Tests/Entity/EntityTemplateSuggestionsTest.php b/core/modules/system/src/Tests/Entity/EntityTemplateSuggestionsTest.php new file mode 100644 index 0000000..d11ec25 --- /dev/null +++ b/core/modules/system/src/Tests/Entity/EntityTemplateSuggestionsTest.php @@ -0,0 +1,98 @@ +entity_name = $this->randomMachineName(); + $this->entity_user = $this->createUser(); + + // Pass in the value of the name field when creating. With the user + // field we test setting a field after creation. + $entity = entity_create($entity_type); + $entity->user_id->target_id = $this->entity_user->id(); + $entity->name->value = $this->entity_name; + + return $entity; + } + + /** + * Tests generation of template suggestions based on entity properties. + */ + public function testReadWrite() { + + // All entity variations have to have the same results. + foreach (entity_test_entity_types() as $entity_type) { + $this->assertTemplateSuggestions($entity_type); + } + } + + /** + * Executes the template suggestions test set for a defined entity type. + * + * @param string $entity_type + * The entity type to run the tests with. + */ + protected function assertTemplateSuggestions($entity_type) { + $entity = $this->createTestEntity($entity_type); + + $entity_definition = \Drupal::entityManager()->getDefinition($entity_type, FALSE); + + if ($entity_definition->hasViewBuilderClass()) { + $view_modes = \Drupal::entityManager()->getFormModes($entity_type); + + foreach ($view_modes as $id => $view_mode) { + + if ($view_builder = \Drupal::entityManager()->getViewBuilder($entity_type)) { + $entity_id = rand(1, 20); + $sanitized_view_mode = strtr($id, '.', '_'); + $bundle_name = $entity->bundle(); + $entity->{$entity_definition->getKey('id')} = $entity_id; + + $suggestions = $view_builder->getTemplateSuggestions($entity, $id); + + $expected = array( + $entity_type . '__' . $sanitized_view_mode, + $entity_type . '__' . $bundle_name, + $entity_type . '__' . $bundle_name . '__' . $sanitized_view_mode, + $entity_type . '__' . $entity_id, + $entity_type . '__' . $entity_id . '__' . $sanitized_view_mode, + ); + + $this->assertEqual($expected, $suggestions, json_encode($suggestions)); + } + } + } + } + +} diff --git a/core/modules/system/system.module b/core/modules/system/system.module index f2dcc7c..f839c94 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -316,6 +316,18 @@ function system_theme_suggestions_field(array $variables) { } /** + * Implements hook_theme_suggestions_alter(). + */ +function system_theme_suggestions_alter(array &$suggestions, array $variables, $hook) { + // If this is an entity being rendered, call the preprocess() method on the + // view builder. + if (isset($variables['elements']['#entity_type']) && isset($variables['elements']['#view_mode']) && $view_builder = \Drupal::entityManager()->getViewBuilder($variables['elements']['#entity_type'])) { + $entity_suggestions = $view_builder->getTemplateSuggestions($variables['elements']['#' . $variables['elements']['#entity_type']], $variables['elements']['#view_mode']); + $suggestions = array_merge($suggestions, $entity_suggestions); + } +} + +/** * @defgroup authorize Authorized operations * @{ * Functions to run operations with elevated privileges via authorize.php. diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module index 42c543a..01a9686 100644 --- a/core/modules/taxonomy/taxonomy.module +++ b/core/modules/taxonomy/taxonomy.module @@ -210,21 +210,6 @@ function taxonomy_term_view_multiple(array $terms, $view_mode = 'full', $langcod } /** - * Implements hook_theme_suggestions_HOOK(). - */ -function taxonomy_theme_suggestions_taxonomy_term(array $variables) { - $suggestions = array(); - - /** @var \Drupal\taxonomy\TermInterface $term */ - $term = $variables['elements']['#taxonomy_term']; - - $suggestions[] = 'taxonomy_term__' . $term->bundle(); - $suggestions[] = 'taxonomy_term__' . $term->id(); - - return $suggestions; -} - -/** * Prepares variables for taxonomy term templates. * * Default template: taxonomy-term.html.twig.