Chris@0: viewMultiple([$entity], $view_mode, $langcode); Chris@0: return reset($build); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function viewMultiple(array $entities = [], $view_mode = 'full', $langcode = NULL) { Chris@0: /** @var \Drupal\block\BlockInterface[] $entities */ Chris@0: $build = []; Chris@0: foreach ($entities as $entity) { Chris@0: $entity_id = $entity->id(); Chris@0: $plugin = $entity->getPlugin(); Chris@0: Chris@0: $cache_tags = Cache::mergeTags($this->getCacheTags(), $entity->getCacheTags()); Chris@0: $cache_tags = Cache::mergeTags($cache_tags, $plugin->getCacheTags()); Chris@0: Chris@0: // Create the render array for the block as a whole. Chris@0: // @see template_preprocess_block(). Chris@0: $build[$entity_id] = [ Chris@0: '#cache' => [ Chris@0: 'keys' => ['entity_view', 'block', $entity->id()], Chris@0: 'contexts' => Cache::mergeContexts( Chris@0: $entity->getCacheContexts(), Chris@0: $plugin->getCacheContexts() Chris@0: ), Chris@0: 'tags' => $cache_tags, Chris@0: 'max-age' => $plugin->getCacheMaxAge(), Chris@0: ], Chris@0: '#weight' => $entity->getWeight(), Chris@0: ]; Chris@0: Chris@0: // Allow altering of cacheability metadata or setting #create_placeholder. Chris@0: $this->moduleHandler->alter(['block_build', "block_build_" . $plugin->getBaseId()], $build[$entity_id], $plugin); Chris@0: Chris@0: if ($plugin instanceof MainContentBlockPluginInterface || $plugin instanceof TitleBlockPluginInterface) { Chris@0: // Immediately build a #pre_render-able block, since this block cannot Chris@0: // be built lazily. Chris@0: $build[$entity_id] += static::buildPreRenderableBlock($entity, $this->moduleHandler()); Chris@0: } Chris@0: else { Chris@0: // Assign a #lazy_builder callback, which will generate a #pre_render- Chris@0: // able block lazily (when necessary). Chris@0: $build[$entity_id] += [ Chris@0: '#lazy_builder' => [static::class . '::lazyBuilder', [$entity_id, $view_mode, $langcode]], Chris@0: ]; Chris@0: } Chris@0: } Chris@0: Chris@0: return $build; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Builds a #pre_render-able block render array. Chris@0: * Chris@0: * @param \Drupal\block\BlockInterface $entity Chris@0: * A block config entity. Chris@0: * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler Chris@0: * The module handler service. Chris@0: * Chris@0: * @return array Chris@0: * A render array with a #pre_render callback to render the block. Chris@0: */ Chris@0: protected static function buildPreRenderableBlock($entity, ModuleHandlerInterface $module_handler) { Chris@0: $plugin = $entity->getPlugin(); Chris@0: $plugin_id = $plugin->getPluginId(); Chris@0: $base_id = $plugin->getBaseId(); Chris@0: $derivative_id = $plugin->getDerivativeId(); Chris@0: $configuration = $plugin->getConfiguration(); Chris@0: Chris@0: // Inject runtime contexts. Chris@0: if ($plugin instanceof ContextAwarePluginInterface) { Chris@0: $contexts = \Drupal::service('context.repository')->getRuntimeContexts($plugin->getContextMapping()); Chris@0: \Drupal::service('context.handler')->applyContextMapping($plugin, $contexts); Chris@0: } Chris@0: Chris@0: // Create the render array for the block as a whole. Chris@0: // @see template_preprocess_block(). Chris@0: $build = [ Chris@0: '#theme' => 'block', Chris@0: '#attributes' => [], Chris@0: // All blocks get a "Configure block" contextual link. Chris@0: '#contextual_links' => [ Chris@0: 'block' => [ Chris@0: 'route_parameters' => ['block' => $entity->id()], Chris@0: ], Chris@0: ], Chris@0: '#weight' => $entity->getWeight(), Chris@0: '#configuration' => $configuration, Chris@0: '#plugin_id' => $plugin_id, Chris@0: '#base_plugin_id' => $base_id, Chris@0: '#derivative_plugin_id' => $derivative_id, Chris@0: '#id' => $entity->id(), Chris@0: '#pre_render' => [ Chris@0: static::class . '::preRender', Chris@0: ], Chris@0: // Add the entity so that it can be used in the #pre_render method. Chris@0: '#block' => $entity, Chris@0: ]; Chris@0: Chris@0: // If an alter hook wants to modify the block contents, it can append Chris@0: // another #pre_render hook. Chris@0: $module_handler->alter(['block_view', "block_view_$base_id"], $build, $plugin); Chris@0: Chris@0: return $build; Chris@0: } Chris@0: Chris@0: /** Chris@0: * #lazy_builder callback; builds a #pre_render-able block. Chris@0: * Chris@0: * @param $entity_id Chris@0: * A block config entity ID. Chris@0: * @param $view_mode Chris@0: * The view mode the block is being viewed in. Chris@0: * Chris@0: * @return array Chris@0: * A render array with a #pre_render callback to render the block. Chris@0: */ Chris@0: public static function lazyBuilder($entity_id, $view_mode) { Chris@0: return static::buildPreRenderableBlock(Block::load($entity_id), \Drupal::service('module_handler')); Chris@0: } Chris@0: Chris@0: /** Chris@0: * #pre_render callback for building a block. Chris@0: * Chris@0: * Renders the content using the provided block plugin, and then: Chris@0: * - if there is no content, aborts rendering, and makes sure the block won't Chris@0: * be rendered. Chris@0: * - if there is content, moves the contextual links from the block content to Chris@0: * the block itself. Chris@0: */ Chris@0: public static function preRender($build) { Chris@0: $content = $build['#block']->getPlugin()->build(); Chris@0: // Remove the block entity from the render array, to ensure that blocks Chris@0: // can be rendered without the block config entity. Chris@0: unset($build['#block']); Chris@0: if ($content !== NULL && !Element::isEmpty($content)) { Chris@0: // Place the $content returned by the block plugin into a 'content' child Chris@0: // element, as a way to allow the plugin to have complete control of its Chris@0: // properties and rendering (for instance, its own #theme) without Chris@0: // conflicting with the properties used above, or alternate ones used by Chris@0: // alternate block rendering approaches in contrib (for instance, Panels). Chris@0: // However, the use of a child element is an implementation detail of this Chris@0: // particular block rendering approach. Semantically, the content returned Chris@0: // by the plugin "is the" block, and in particular, #attributes and Chris@0: // #contextual_links is information about the *entire* block. Therefore, Chris@0: // we must move these properties from $content and merge them into the Chris@0: // top-level element. Chris@0: foreach (['#attributes', '#contextual_links'] as $property) { Chris@0: if (isset($content[$property])) { Chris@0: $build[$property] += $content[$property]; Chris@0: unset($content[$property]); Chris@0: } Chris@0: } Chris@0: $build['content'] = $content; Chris@0: } Chris@0: // Either the block's content is completely empty, or it consists only of Chris@0: // cacheability metadata. Chris@0: else { Chris@0: // Abort rendering: render as the empty string and ensure this block is Chris@0: // render cached, so we can avoid the work of having to repeatedly Chris@0: // determine whether the block is empty. For instance, modifying or adding Chris@0: // entities could cause the block to no longer be empty. Chris@0: $build = [ Chris@0: '#markup' => '', Chris@0: '#cache' => $build['#cache'], Chris@0: ]; Chris@0: // If $content is not empty, then it contains cacheability metadata, and Chris@0: // we must merge it with the existing cacheability metadata. This allows Chris@0: // blocks to be empty, yet still bubble cacheability metadata, to indicate Chris@0: // why they are empty. Chris@0: if (!empty($content)) { Chris@0: CacheableMetadata::createFromRenderArray($build) Chris@0: ->merge(CacheableMetadata::createFromRenderArray($content)) Chris@0: ->applyTo($build); Chris@0: } Chris@0: } Chris@0: return $build; Chris@0: } Chris@0: Chris@0: }