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