annotate core/modules/block/src/BlockViewBuilder.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
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 }