3 namespace Drupal\block;
5 use Drupal\Core\Block\MainContentBlockPluginInterface;
6 use Drupal\Core\Block\TitleBlockPluginInterface;
7 use Drupal\Core\Cache\Cache;
8 use Drupal\Core\Cache\CacheableMetadata;
9 use Drupal\Core\Entity\EntityManagerInterface;
10 use Drupal\Core\Entity\EntityTypeInterface;
11 use Drupal\Core\Entity\EntityViewBuilder;
12 use Drupal\Core\Entity\EntityInterface;
13 use Drupal\Core\Extension\ModuleHandlerInterface;
14 use Drupal\Core\Language\LanguageManagerInterface;
15 use Drupal\Core\Plugin\ContextAwarePluginInterface;
16 use Drupal\Core\Render\Element;
17 use Drupal\block\Entity\Block;
18 use Symfony\Component\DependencyInjection\ContainerInterface;
21 * Provides a Block view builder.
23 class BlockViewBuilder extends EntityViewBuilder {
28 * @var \Drupal\Core\Extension\ModuleHandlerInterface
30 protected $moduleHandler;
33 * Constructs a new BlockViewBuilder.
35 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
36 * The entity type definition.
37 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
38 * The entity manager service.
39 * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
40 * The language manager.
41 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
44 public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, ModuleHandlerInterface $module_handler) {
45 parent::__construct($entity_type, $entity_manager, $language_manager);
46 $this->moduleHandler = $module_handler;
52 public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
55 $container->get('entity.manager'),
56 $container->get('language_manager'),
57 $container->get('module_handler')
64 public function buildComponents(array &$build, array $entities, array $displays, $view_mode) {
70 public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
71 $build = $this->viewMultiple([$entity], $view_mode, $langcode);
78 public function viewMultiple(array $entities = [], $view_mode = 'full', $langcode = NULL) {
79 /** @var \Drupal\block\BlockInterface[] $entities */
81 foreach ($entities as $entity) {
82 $entity_id = $entity->id();
83 $plugin = $entity->getPlugin();
85 $cache_tags = Cache::mergeTags($this->getCacheTags(), $entity->getCacheTags());
86 $cache_tags = Cache::mergeTags($cache_tags, $plugin->getCacheTags());
88 // Create the render array for the block as a whole.
89 // @see template_preprocess_block().
90 $build[$entity_id] = [
92 'keys' => ['entity_view', 'block', $entity->id()],
93 'contexts' => Cache::mergeContexts(
94 $entity->getCacheContexts(),
95 $plugin->getCacheContexts()
97 'tags' => $cache_tags,
98 'max-age' => $plugin->getCacheMaxAge(),
100 '#weight' => $entity->getWeight(),
103 // Allow altering of cacheability metadata or setting #create_placeholder.
104 $this->moduleHandler->alter(['block_build', "block_build_" . $plugin->getBaseId()], $build[$entity_id], $plugin);
106 if ($plugin instanceof MainContentBlockPluginInterface || $plugin instanceof TitleBlockPluginInterface) {
107 // Immediately build a #pre_render-able block, since this block cannot
109 $build[$entity_id] += static::buildPreRenderableBlock($entity, $this->moduleHandler());
112 // Assign a #lazy_builder callback, which will generate a #pre_render-
113 // able block lazily (when necessary).
114 $build[$entity_id] += [
115 '#lazy_builder' => [static::class . '::lazyBuilder', [$entity_id, $view_mode, $langcode]],
124 * Builds a #pre_render-able block render array.
126 * @param \Drupal\block\BlockInterface $entity
127 * A block config entity.
128 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
129 * The module handler service.
132 * A render array with a #pre_render callback to render the block.
134 protected static function buildPreRenderableBlock($entity, ModuleHandlerInterface $module_handler) {
135 $plugin = $entity->getPlugin();
136 $plugin_id = $plugin->getPluginId();
137 $base_id = $plugin->getBaseId();
138 $derivative_id = $plugin->getDerivativeId();
139 $configuration = $plugin->getConfiguration();
141 // Inject runtime contexts.
142 if ($plugin instanceof ContextAwarePluginInterface) {
143 $contexts = \Drupal::service('context.repository')->getRuntimeContexts($plugin->getContextMapping());
144 \Drupal::service('context.handler')->applyContextMapping($plugin, $contexts);
147 // Create the render array for the block as a whole.
148 // @see template_preprocess_block().
152 // All blocks get a "Configure block" contextual link.
153 '#contextual_links' => [
155 'route_parameters' => ['block' => $entity->id()],
158 '#weight' => $entity->getWeight(),
159 '#configuration' => $configuration,
160 '#plugin_id' => $plugin_id,
161 '#base_plugin_id' => $base_id,
162 '#derivative_plugin_id' => $derivative_id,
163 '#id' => $entity->id(),
165 static::class . '::preRender',
167 // Add the entity so that it can be used in the #pre_render method.
171 // If an alter hook wants to modify the block contents, it can append
172 // another #pre_render hook.
173 $module_handler->alter(['block_view', "block_view_$base_id"], $build, $plugin);
179 * #lazy_builder callback; builds a #pre_render-able block.
182 * A block config entity ID.
184 * The view mode the block is being viewed in.
187 * A render array with a #pre_render callback to render the block.
189 public static function lazyBuilder($entity_id, $view_mode) {
190 return static::buildPreRenderableBlock(Block::load($entity_id), \Drupal::service('module_handler'));
194 * #pre_render callback for building a block.
196 * Renders the content using the provided block plugin, and then:
197 * - if there is no content, aborts rendering, and makes sure the block won't
199 * - if there is content, moves the contextual links from the block content to
202 public static function preRender($build) {
203 $content = $build['#block']->getPlugin()->build();
204 // Remove the block entity from the render array, to ensure that blocks
205 // can be rendered without the block config entity.
206 unset($build['#block']);
207 if ($content !== NULL && !Element::isEmpty($content)) {
208 // Place the $content returned by the block plugin into a 'content' child
209 // element, as a way to allow the plugin to have complete control of its
210 // properties and rendering (for instance, its own #theme) without
211 // conflicting with the properties used above, or alternate ones used by
212 // alternate block rendering approaches in contrib (for instance, Panels).
213 // However, the use of a child element is an implementation detail of this
214 // particular block rendering approach. Semantically, the content returned
215 // by the plugin "is the" block, and in particular, #attributes and
216 // #contextual_links is information about the *entire* block. Therefore,
217 // we must move these properties from $content and merge them into the
218 // top-level element.
219 foreach (['#attributes', '#contextual_links'] as $property) {
220 if (isset($content[$property])) {
221 $build[$property] += $content[$property];
222 unset($content[$property]);
225 $build['content'] = $content;
227 // Either the block's content is completely empty, or it consists only of
228 // cacheability metadata.
230 // Abort rendering: render as the empty string and ensure this block is
231 // render cached, so we can avoid the work of having to repeatedly
232 // determine whether the block is empty. For instance, modifying or adding
233 // entities could cause the block to no longer be empty.
236 '#cache' => $build['#cache'],
238 // If $content is not empty, then it contains cacheability metadata, and
239 // we must merge it with the existing cacheability metadata. This allows
240 // blocks to be empty, yet still bubble cacheability metadata, to indicate
241 // why they are empty.
242 if (!empty($content)) {
243 CacheableMetadata::createFromRenderArray($build)
244 ->merge(CacheableMetadata::createFromRenderArray($content))