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