BlockViewBuilder.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. <?php
  2. namespace Drupal\block;
  3. use Drupal\Core\Block\MainContentBlockPluginInterface;
  4. use Drupal\Core\Block\TitleBlockPluginInterface;
  5. use Drupal\Core\Cache\Cache;
  6. use Drupal\Core\Cache\CacheableMetadata;
  7. use Drupal\Core\Entity\EntityManagerInterface;
  8. use Drupal\Core\Entity\EntityTypeInterface;
  9. use Drupal\Core\Entity\EntityViewBuilder;
  10. use Drupal\Core\Entity\EntityInterface;
  11. use Drupal\Core\Extension\ModuleHandlerInterface;
  12. use Drupal\Core\Language\LanguageManagerInterface;
  13. use Drupal\Core\Plugin\ContextAwarePluginInterface;
  14. use Drupal\Core\Render\Element;
  15. use Drupal\block\Entity\Block;
  16. use Symfony\Component\DependencyInjection\ContainerInterface;
  17. /**
  18. * Provides a Block view builder.
  19. */
  20. class BlockViewBuilder extends EntityViewBuilder {
  21. /**
  22. * The module handler.
  23. *
  24. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  25. */
  26. protected $moduleHandler;
  27. /**
  28. * Constructs a new BlockViewBuilder.
  29. *
  30. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  31. * The entity type definition.
  32. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
  33. * The entity manager service.
  34. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
  35. * The language manager.
  36. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  37. * The module handler.
  38. */
  39. public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, ModuleHandlerInterface $module_handler) {
  40. parent::__construct($entity_type, $entity_manager, $language_manager);
  41. $this->moduleHandler = $module_handler;
  42. }
  43. /**
  44. * {@inheritdoc}
  45. */
  46. public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
  47. return new static(
  48. $entity_type,
  49. $container->get('entity.manager'),
  50. $container->get('language_manager'),
  51. $container->get('module_handler')
  52. );
  53. }
  54. /**
  55. * {@inheritdoc}
  56. */
  57. public function buildComponents(array &$build, array $entities, array $displays, $view_mode) {
  58. }
  59. /**
  60. * {@inheritdoc}
  61. */
  62. public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
  63. $build = $this->viewMultiple([$entity], $view_mode, $langcode);
  64. return reset($build);
  65. }
  66. /**
  67. * {@inheritdoc}
  68. */
  69. public function viewMultiple(array $entities = [], $view_mode = 'full', $langcode = NULL) {
  70. /** @var \Drupal\block\BlockInterface[] $entities */
  71. $build = [];
  72. foreach ($entities as $entity) {
  73. $entity_id = $entity->id();
  74. $plugin = $entity->getPlugin();
  75. $cache_tags = Cache::mergeTags($this->getCacheTags(), $entity->getCacheTags());
  76. $cache_tags = Cache::mergeTags($cache_tags, $plugin->getCacheTags());
  77. // Create the render array for the block as a whole.
  78. // @see template_preprocess_block().
  79. $build[$entity_id] = [
  80. '#cache' => [
  81. 'keys' => ['entity_view', 'block', $entity->id()],
  82. 'contexts' => Cache::mergeContexts(
  83. $entity->getCacheContexts(),
  84. $plugin->getCacheContexts()
  85. ),
  86. 'tags' => $cache_tags,
  87. 'max-age' => $plugin->getCacheMaxAge(),
  88. ],
  89. '#weight' => $entity->getWeight(),
  90. ];
  91. // Allow altering of cacheability metadata or setting #create_placeholder.
  92. $this->moduleHandler->alter(['block_build', "block_build_" . $plugin->getBaseId()], $build[$entity_id], $plugin);
  93. if ($plugin instanceof MainContentBlockPluginInterface || $plugin instanceof TitleBlockPluginInterface) {
  94. // Immediately build a #pre_render-able block, since this block cannot
  95. // be built lazily.
  96. $build[$entity_id] += static::buildPreRenderableBlock($entity, $this->moduleHandler());
  97. }
  98. else {
  99. // Assign a #lazy_builder callback, which will generate a #pre_render-
  100. // able block lazily (when necessary).
  101. $build[$entity_id] += [
  102. '#lazy_builder' => [static::class . '::lazyBuilder', [$entity_id, $view_mode, $langcode]],
  103. ];
  104. }
  105. }
  106. return $build;
  107. }
  108. /**
  109. * Builds a #pre_render-able block render array.
  110. *
  111. * @param \Drupal\block\BlockInterface $entity
  112. * A block config entity.
  113. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  114. * The module handler service.
  115. *
  116. * @return array
  117. * A render array with a #pre_render callback to render the block.
  118. */
  119. protected static function buildPreRenderableBlock($entity, ModuleHandlerInterface $module_handler) {
  120. $plugin = $entity->getPlugin();
  121. $plugin_id = $plugin->getPluginId();
  122. $base_id = $plugin->getBaseId();
  123. $derivative_id = $plugin->getDerivativeId();
  124. $configuration = $plugin->getConfiguration();
  125. // Inject runtime contexts.
  126. if ($plugin instanceof ContextAwarePluginInterface) {
  127. $contexts = \Drupal::service('context.repository')->getRuntimeContexts($plugin->getContextMapping());
  128. \Drupal::service('context.handler')->applyContextMapping($plugin, $contexts);
  129. }
  130. // Create the render array for the block as a whole.
  131. // @see template_preprocess_block().
  132. $build = [
  133. '#theme' => 'block',
  134. '#attributes' => [],
  135. // All blocks get a "Configure block" contextual link.
  136. '#contextual_links' => [
  137. 'block' => [
  138. 'route_parameters' => ['block' => $entity->id()],
  139. ],
  140. ],
  141. '#weight' => $entity->getWeight(),
  142. '#configuration' => $configuration,
  143. '#plugin_id' => $plugin_id,
  144. '#base_plugin_id' => $base_id,
  145. '#derivative_plugin_id' => $derivative_id,
  146. '#id' => $entity->id(),
  147. '#pre_render' => [
  148. static::class . '::preRender',
  149. ],
  150. // Add the entity so that it can be used in the #pre_render method.
  151. '#block' => $entity,
  152. ];
  153. // If an alter hook wants to modify the block contents, it can append
  154. // another #pre_render hook.
  155. $module_handler->alter(['block_view', "block_view_$base_id"], $build, $plugin);
  156. return $build;
  157. }
  158. /**
  159. * #lazy_builder callback; builds a #pre_render-able block.
  160. *
  161. * @param $entity_id
  162. * A block config entity ID.
  163. * @param $view_mode
  164. * The view mode the block is being viewed in.
  165. *
  166. * @return array
  167. * A render array with a #pre_render callback to render the block.
  168. */
  169. public static function lazyBuilder($entity_id, $view_mode) {
  170. return static::buildPreRenderableBlock(Block::load($entity_id), \Drupal::service('module_handler'));
  171. }
  172. /**
  173. * #pre_render callback for building a block.
  174. *
  175. * Renders the content using the provided block plugin, and then:
  176. * - if there is no content, aborts rendering, and makes sure the block won't
  177. * be rendered.
  178. * - if there is content, moves the contextual links from the block content to
  179. * the block itself.
  180. */
  181. public static function preRender($build) {
  182. $content = $build['#block']->getPlugin()->build();
  183. // Remove the block entity from the render array, to ensure that blocks
  184. // can be rendered without the block config entity.
  185. unset($build['#block']);
  186. if ($content !== NULL && !Element::isEmpty($content)) {
  187. // Place the $content returned by the block plugin into a 'content' child
  188. // element, as a way to allow the plugin to have complete control of its
  189. // properties and rendering (for instance, its own #theme) without
  190. // conflicting with the properties used above, or alternate ones used by
  191. // alternate block rendering approaches in contrib (for instance, Panels).
  192. // However, the use of a child element is an implementation detail of this
  193. // particular block rendering approach. Semantically, the content returned
  194. // by the plugin "is the" block, and in particular, #attributes and
  195. // #contextual_links is information about the *entire* block. Therefore,
  196. // we must move these properties from $content and merge them into the
  197. // top-level element.
  198. foreach (['#attributes', '#contextual_links'] as $property) {
  199. if (isset($content[$property])) {
  200. $build[$property] += $content[$property];
  201. unset($content[$property]);
  202. }
  203. }
  204. $build['content'] = $content;
  205. }
  206. // Either the block's content is completely empty, or it consists only of
  207. // cacheability metadata.
  208. else {
  209. // Abort rendering: render as the empty string and ensure this block is
  210. // render cached, so we can avoid the work of having to repeatedly
  211. // determine whether the block is empty. For instance, modifying or adding
  212. // entities could cause the block to no longer be empty.
  213. $build = [
  214. '#markup' => '',
  215. '#cache' => $build['#cache'],
  216. ];
  217. // If $content is not empty, then it contains cacheability metadata, and
  218. // we must merge it with the existing cacheability metadata. This allows
  219. // blocks to be empty, yet still bubble cacheability metadata, to indicate
  220. // why they are empty.
  221. if (!empty($content)) {
  222. CacheableMetadata::createFromRenderArray($build)
  223. ->merge(CacheableMetadata::createFromRenderArray($content))
  224. ->applyTo($build);
  225. }
  226. }
  227. return $build;
  228. }
  229. }