Commit 75b5ee12 authored by webchick's avatar webchick

Issue #1817044 by sdboyer, effulgentsia, tim.plunkett, Gábor Hojtsy, yched:...

Issue #1817044 by sdboyer, effulgentsia, tim.plunkett, Gábor Hojtsy, yched: Implement Display, a type of config for use by layouts, et. all.
parent b28e80dd
......@@ -2,4 +2,6 @@ title: One column
category: Columns: 1
template: one-col
regions:
content: 'Content'
content:
label: Middle column
type: content
\ No newline at end of file
......@@ -4,5 +4,9 @@ template: two-col
stylesheets:
- two-col.css
regions:
first: 'First column'
second: 'Second column'
first:
label: Left side
type: content
second:
label: Right side
type: aside
<?php
/**
* @file
* Definition of Drupal\layout\Config\BoundDisplayInterface
*/
namespace Drupal\layout\Config;
use Drupal\layout\Plugin\LayoutInterface;
/**
* Interface for a Display object that is coupled to a specific layout.
*
* Bound displays contains references both to block instances and a specific
* layout, and the blocks are assigned to specific regions in that layout. Bound
* displays are used to serve real pages at request time.
*
* @see \Drupal\layout\Config\DisplayInterface
*/
interface BoundDisplayInterface extends DisplayInterface {
/**
* Sets the layout to be used by this display.
*
* @param string $layout_id
* The id of the desired layout.
*/
public function setLayout($layout_id);
/**
* Returns the blocks in the requested region, ordered by weight.
*
* @param string $region
* The region from which to return the set of blocks.
*
* @return array
* The list of blocks, ordered by their weight within this display. Each
* value in the list is the configuration object name of the block.
*/
public function getSortedBlocksByRegion($region);
/**
* Returns this display's blocks, organized by region and ordered by weight.
*
* @return array
* An array keyed by region name. For each region, the value is the same as
* what is returned by getSortedBlocksByRegion().
*
* @see getSortedBlocksByRegion()
*/
public function getAllSortedBlocks();
/**
* Returns the instantiated layout object to be used by this display.
*
* @return \Drupal\layout\Plugin\LayoutInterface
*/
public function getLayoutInstance();
/**
* Adjusts this display's block placement to work with the provided layout.
*
* Essentially a shortcut that calls DisplayInterface::mapBlocksToLayout(),
* saves the result in the appropriate object property, and finally calls
* BoundDisplayInterface::setLayout().
*
* @param \Drupal\layout\Plugin\LayoutInterface $layout
* The new layout to which blocks should be remapped.
*
* @see \Drupal\layout\Config\DisplayInterface::mapBlocksToLayout()
*/
public function remapToLayout(LayoutInterface $layout);
/**
* Returns an entity with the non-layout-specific configuration of this one.
*
* @param string $id
* The entity id to assign to the newly created entity.
*
* @param string $entity_type
* The type of entity to create. The PHP class for this entity type must
* implement \Drupal\layout\Config\UnboundDisplayInterface.
*
* @return \Drupal\layout\Config\UnboundDisplayInterface
* The newly-created unbound display.
*/
public function generateUnboundDisplay($id, $entity_type = 'unbound_display');
}
<?php
/**
* @file
* Definition of Drupal\layout\Config\DisplayBase.
*/
namespace Drupal\layout\Config;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\layout\Plugin\LayoutInterface;
/**
* Base class for 'display' and 'unbound_display' configuration entities.
*
* @see \Drupal\layout\Config\DisplayInterface
*/
abstract class DisplayBase extends ConfigEntityBase implements DisplayInterface {
/**
* The ID (config name) identifying a specific display object.
*
* @var string
*/
public $id;
/**
* The UUID identifying a specific display object.
*
* @var string
*/
public $uuid;
/**
* Contains all block configuration.
*
* There are two levels to the configuration contained herein: display-level
* block configuration, and then block instance configuration.
*
* Block instance configuration is stored in a separate config object. This
* array is keyed by the config name that uniquely identifies each block
* instance. At runtime, various object methods will retrieve this additional
* config and return it to calling code.
*
* Display-level block configuration is data that determines the behavior of
* a block *in this display*. The most important examples of this are the
* region to which the block is assigned, and its weighting in that region.
*
* @code
* array(
* 'block1-configkey' => array(
* 'region' => 'content',
* // store the region type name here so that we can do type conversion w/out
* // needing to have access to the original layout plugin
* 'region-type' => 'content',
* // increment by 100 so there is ALWAYS plenty of space for manual insertion
* 'weight' => -100,
* ),
* 'block2-configkey' => array(
* 'region' => 'sidebar_first',
* 'region-type' => 'aside',
* 'weight' => -100,
* ),
* 'block2-configkey' => array(
* 'region' => 'sidebar_first',
* 'region-type' => 'aside',
* 'weight' => 0,
* ),
* 'maincontent' => array(
* 'region' => 'content',
* 'region-type' => 'content',
* 'weight' => -200,
* ),
* );
* @endcode
*
* @var array
*/
protected $blockInfo = array();
/**
* Implements DisplayInterface::getAllBlockInfo().
*/
public function getAllBlockInfo() {
return $this->blockInfo;
}
/**
* Implements DisplayInterface::mapBlocksToLayout().
*
* @todo Decouple this implementation from this class, so that it could be
* more easily customized.
*/
public function mapBlocksToLayout(LayoutInterface $layout) {
$types = array();
$layout_regions = $layout->getRegions();
$layout_regions_indexed = array_keys($layout_regions);
foreach ($layout_regions as $name => $info) {
$types[$info['type']][] = $name;
}
$remapped_config = array();
foreach ($this->blockInfo as $name => $info) {
// First, if there's a direct region name match, use that.
if (!empty($info['region']) && isset($layout_regions[$info['region']])) {
// No need to do anything.
}
// Then, try to remap using region types.
else if (!empty($types[$info['region-type']])) {
$info['region'] = reset($types[$info['region-type']]);
}
// Finally, fall back to dumping everything in the layout's first region.
else {
if (!isset($first_region)) {
reset($layout_regions);
$first_region = key($layout_regions);
}
$info['region'] = $first_region;
}
$remapped_config[$name] = $info;
}
return $remapped_config;
}
/**
* Implements DisplayInterface::getAllRegionTypes().
*/
public function getAllRegionTypes() {
$types = array();
foreach ($this->blockInfo as $info) {
$types[] = $info['region-type'];
}
return array_unique($types);
}
}
<?php
/**
* @file
* Definition of Drupal\layout\Config\DisplayInterface
*/
namespace Drupal\layout\Config;
use Drupal\layout\Plugin\LayoutInterface;
/**
* Interface describing a Display configuration object.
*
* Displays are configuration that describe the placement of block instances
* in regions. Drupal includes two types of Display objects:
* - Bound displays include a reference to a specific layout, and each block is
* specified to display in a specific region of that layout. Bound displays
* are used to serve real pages at request time.
* - Unbound displays do not include a reference to any layout, and each block
* is assigned a region type, but not a specific region. Developers including
* default displays with their modules or distributions are encouraged to use
* unbound displays in order to minimize dependencies on specific layouts and
* allow site-specific configuration to dictate the layout.
*
* This interface defines what is common to all displays, whether bound or
* unbound.
*
* @see \Drupal\layout\Config\BoundDisplayInterface
* @see \Drupal\layout\Config\UnboundDisplayInterface
*/
interface DisplayInterface {
/**
* Returns the display-specific configuration of all blocks in this display.
*
* For each block that exists in Drupal (e.g., the "Who's Online" block),
* multiple "configured instances" can be created (e.g., a "Who's been online
* in the last 5 minutes" instance and a "Who's been online in the last 60
* minutes" instance). Each configured instance can be referenced by multiple
* displays (e.g., by a "regular" page, by an administrative page, and within
* one or more dashboards). This function returns the block instances that
* have been added to this display. Each key of the returned array is the
* block instance's configuration object name, and config() may be called on
* it in order to retrieve the full configuration that is shared across all
* displays. For each key, the value is an array of display-specific
* configuration, primarily the 'region' and 'weight', and anything else that
* affects the placement of the block within the layout rather than only the
* contents of the block.
*
* @return array
* An array keyed on each block's configuration object name. Each value is
* an array of information that determines the placement of the block within
* a layout, including:
* - region: The region in which to display the block (for bound displays
* only).
* - region-type: The type of region that is most appropriate for the block.
* Usually one of 'header', 'footer', 'nav', 'content', 'aside', or
* 'system', though custom region types are also allowed. This is
* primarily specified by unbound displays, where specifying a specific
* region name is impossible, because different layouts come with
* different regions.
* - weight: Within a region, blocks are rendered from low to high weight.
*/
public function getAllBlockInfo();
/**
* Maps the contained block info to the provided layout.
*
* @param \Drupal\layout\Plugin\LayoutInterface $layout
*
* @return array
* An array containing block configuration info, identical to that which
* is returned by DisplayInterface::getAllBlockInfo().
*/
public function mapBlocksToLayout(LayoutInterface $layout);
/**
* Returns the names of all region types to which blocks are assigned.
*
* @return array
* An indexed array of unique region type names, or an empty array if no
* region types were assigned.
*/
public function getAllRegionTypes();
}
<?php
/**
* @file
* Definition of Drupal\layout\Config\UnboundDisplayInterface
*/
namespace Drupal\layout\Config;
use Drupal\layout\Plugin\LayoutInterface;
/**
* Interface for a Display that is not coupled with any layout.
*
* Unbound displays contain references to blocks, but not to any particular
* layout. Their primary use case is to express a set of relative block
* placements without necessitating any particular layout be present. This
* allows upstream (module and distribution) developers to express a visual
* composition of blocks without knowing anything about the layouts a
* particular site has available.
*
* @see \Drupal\layout\Config\DisplayInterface
*/
interface UnboundDisplayInterface extends DisplayInterface {
/**
* Returns a bound display entity by binding a layout to this unbound display.
*
* This will DisplayInterface::mapBlocksToLayout() using the provided layout,
* then create and return a new Display object with the output. This is just
* a factory - calling code is responsible for saving the returned object.
*
* @param \Drupal\layout\Plugin\LayoutInterface $layout
* The desired layout.
*
* @param string $id
* The entity id to assign to the newly created entity.
*
* @param string $entity_type
* The type of entity to create. The PHP class for this entity type must
* implement \Drupal\layout\Config\BoundDisplayInterface.
*
* @return \Drupal\layout\Config\BoundDisplayInterface
* The newly created entity.
*/
public function generateDisplay(LayoutInterface $layout, $id, $entity_type = 'display');
}
<?php
/**
* @file
* Definition of Drupal\layout\Plugin\Core\Entity\Display.
*/
namespace Drupal\layout\Plugin\Core\Entity;
use Drupal\layout\Config\DisplayBase;
use Drupal\layout\Config\BoundDisplayInterface;
use Drupal\layout\Config\UnboundDisplayInterface;
use Drupal\layout\Plugin\LayoutInterface;
use Drupal\Core\Annotation\Plugin;
use Drupal\Core\Annotation\Translation;
/**
* Defines the display entity.
*
* @Plugin(
* id = "display",
* label = @Translation("Display"),
* module = "layout",
* controller_class = "Drupal\Core\Config\Entity\ConfigStorageController",
* config_prefix = "display.bound",
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid"
* }
* )
*/
class Display extends DisplayBase implements BoundDisplayInterface {
/**
* A two-level array expressing block ordering within regions.
*
* The outer array is associative, keyed on region name. Each inner array is
* indexed, with the config address of a block as values and sorted according
* to order in which those blocks should appear in that region.
*
* This property is not stored statically in config, but is derived at runtime
* by DisplayBase::sortBlocks(). It is not stored statically because that
* would make using weights for ordering more difficult, and weights make
* external mass manipulation of displays much easier.
*
* @var array
*/
protected $blocksInRegions;
/**
* The layout instance being used to serve this page.
*
* @var \Drupal\layout\Plugin\LayoutInterface
*/
protected $layoutInstance;
/**
* The name of the layout plugin to use.
*
* @var string
*/
public $layout;
/**
* The settings with which to instantiate the layout plugin.
*
* @var array
*/
public $layoutSettings = array();
/**
* Implements BoundDisplayInterface::getSortedBlocksByRegion().
*
* @throws \Exception
*/
public function getSortedBlocksByRegion($region) {
if ($this->blocksInRegions === NULL) {
$this->sortBlocks();
}
if (!isset($this->blocksInRegions[$region])) {
throw new \Exception(sprintf("Region %region does not exist in layout %layout", array('%region' => $region, '%layout' => $this->getLayoutInstance()->name)), E_RECOVERABLE_ERROR);
}
return $this->blocksInRegions[$region];
}
/**
* Implements BoundDisplayInterface::getAllSortedBlocks().
*/
public function getAllSortedBlocks() {
if ($this->blocksInRegions === NULL) {
$this->sortBlocks();
}
return $this->blocksInRegions;
}
/**
* Transform the stored blockConfig into a sorted, region-oriented array.
*/
protected function sortBlocks() {
$layout_instance = $this->getLayoutInstance();
if ($this->layout !== $layout_instance->getPluginId()) {
$block_config = $this->mapBlocksToLayout($layout_instance);
}
else {
$block_config = $this->blockInfo;
}
$this->blocksInRegions = array();
$regions = array_fill_keys(array_keys($layout_instance->getRegions()), array());
foreach ($block_config as $config_name => $info) {
$regions[$info['region']][$config_name] = $info;
}
foreach ($regions as $region_name => &$blocks) {
uasort($blocks, 'drupal_sort_weight');
$this->blocksInRegions[$region_name] = array_keys($blocks);
}
}
/**
* Implements BoundDisplayInterface::remapToLayout().
*/
public function remapToLayout(LayoutInterface $layout) {
$this->blockInfo = $this->mapBlocksToLayout($layout);
$this->setLayout($layout->getPluginId());
}
/**
* Set the contained layout plugin.
*
* @param string $plugin_id
* The plugin id of the desired layout plugin.
*/
public function setLayout($plugin_id) {
// @todo verification?
$this->layout = $plugin_id;
$this->layoutInstance = NULL;
$this->blocksInRegions = NULL;
}
/**
* Implements BoundDisplayInterface::generateUnboundDisplay().
*
* @throws \Exception
*/
public function generateUnboundDisplay($id, $entity_type = 'unbound_display') {
$block_info = $this->getAllBlockInfo();
foreach ($block_info as &$info) {
unset($info['region']);
}
$values = array(
'blockInfo' => $block_info,
'id' => $id,
);
$entity = entity_create($entity_type, $values);
if (!$entity instanceof UnboundDisplayInterface) {
throw new \Exception(sprintf('Attempted to create an unbound display using an invalid entity type.'), E_RECOVERABLE_ERROR);
}
return $entity;
}
/**
* Returns the instantiated layout object.
*
* @throws \Exception
*/
public function getLayoutInstance() {
if ($this->layoutInstance === NULL) {
if (empty($this->layout)) {
throw new \Exception(sprintf('Display "%id" had no layout plugin attached.', array('%id' => $this->id())), E_RECOVERABLE_ERROR);
}
$this->layoutInstance = layout_manager()->createInstance($this->layout, $this->layoutSettings);
// @todo add handling for remapping if the layout could not be found
}
return $this->layoutInstance;
}
}
<?php
/**
* @file
* Definition of Drupal\layout\Plugin\Core\Entity\Display.
*/
namespace Drupal\layout\Plugin\Core\Entity;
use Drupal\layout\Config\DisplayBase;
use Drupal\layout\Config\BoundDisplayInterface;
use Drupal\layout\Config\UnboundDisplayInterface;
use Drupal\layout\Plugin\LayoutInterface;
use Drupal\Core\Annotation\Plugin;
use Drupal\Core\Annotation\Translation;
/**
* Defines the unbound_display entity.
*
* Unbound displays contain blocks that are not 'bound' to a specific layout,
* and their contained blocks are mapped only to region types, not regions.
*
* @Plugin(
* id = "unbound_display",
* label = @Translation("Unbound Display"),
* module = "layout",
* controller_class = "Drupal\Core\Config\Entity\ConfigStorageController",
* config_prefix = "display.unbound",
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid"