Commit 2e16f2a3 authored by xjm's avatar xjm

Issue #2926914 by tim.plunkett, xjm, larowlan, tedbow, EclipseGc: Rewrite...

Issue #2926914 by tim.plunkett, xjm, larowlan, tedbow, EclipseGc: Rewrite \Drupal\layout_builder\Section to represent the entire section, not just the block info
parent 3cf0815a
......@@ -6,6 +6,7 @@
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
use Drupal\layout_builder\Section;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
......@@ -63,13 +64,9 @@ public static function create(ContainerInterface $container) {
* The controller response.
*/
public function build(EntityInterface $entity, $delta, $plugin_id) {
/** @var \Drupal\layout_builder\Field\LayoutSectionItemListInterface $field_list */
/** @var \Drupal\layout_builder\SectionStorageInterface $field_list */
$field_list = $entity->layout_builder__layout;
$field_list->addItem($delta, [
'layout' => $plugin_id,
'layout_settings' => [],
'section' => [],
]);
$field_list->insertSection($delta, new Section($plugin_id));
$this->layoutTempstoreRepository->set($entity);
......
......@@ -10,8 +10,8 @@
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\layout_builder\LayoutSectionBuilder;
use Drupal\layout_builder\Field\LayoutSectionItemInterface;
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
use Drupal\layout_builder\Section;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
......@@ -111,20 +111,20 @@ public function layout(EntityInterface $entity, $is_rebuilding = FALSE) {
$entity_id = $entity->id();
$entity_type_id = $entity->getEntityTypeId();
/** @var \Drupal\layout_builder\Field\LayoutSectionItemListInterface $field_list */
/** @var \Drupal\layout_builder\SectionStorageInterface $field_list */
$field_list = $entity->layout_builder__layout;
// For a new layout override, begin with a single section of one column.
if (!$is_rebuilding && $field_list->isEmpty()) {
$field_list->addItem(0, ['layout' => 'layout_onecol']);
if (!$is_rebuilding && $field_list->count() === 0) {
$field_list->appendSection(new Section('layout_onecol'));
$this->layoutTempstoreRepository->set($entity);
}
$output = [];
$count = 0;
foreach ($field_list as $item) {
foreach ($field_list->getSections() as $section) {
$output[] = $this->buildAddSectionLink($entity_type_id, $entity_id, $count);
$output[] = $this->buildAdministrativeSection($item, $entity, $count);
$output[] = $this->buildAdministrativeSection($section, $entity, $count);
$count++;
}
$output[] = $this->buildAddSectionLink($entity_type_id, $entity_id, $count);
......@@ -179,8 +179,8 @@ protected function buildAddSectionLink($entity_type_id, $entity_id, $delta) {
/**
* Builds the render array for the layout section while editing.
*
* @param \Drupal\layout_builder\Field\LayoutSectionItemInterface $item
* The layout section item.
* @param \Drupal\layout_builder\Section $section
* The layout section.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
* @param int $delta
......@@ -189,12 +189,12 @@ protected function buildAddSectionLink($entity_type_id, $entity_id, $delta) {
* @return array
* The render array for a given section.
*/
protected function buildAdministrativeSection(LayoutSectionItemInterface $item, EntityInterface $entity, $delta) {
protected function buildAdministrativeSection(Section $section, EntityInterface $entity, $delta) {
$entity_type_id = $entity->getEntityTypeId();
$entity_id = $entity->id();
$layout = $this->layoutManager->createInstance($item->layout, $item->layout_settings);
$build = $this->builder->buildSectionFromLayout($layout, $item->section);
$layout = $section->getLayout();
$build = $section->toRenderArray();
$layout_definition = $layout->getPluginDefinition();
foreach ($layout_definition->getRegions() as $region => $info) {
......
......@@ -69,32 +69,29 @@ public static function create(ContainerInterface $container) {
* An AJAX response.
*/
public function build(EntityInterface $entity, $delta_from, $delta_to, $region_from, $region_to, $block_uuid, $preceding_block_uuid = NULL) {
/** @var \Drupal\layout_builder\Field\LayoutSectionItemInterface $field */
$field = $entity->layout_builder__layout->get($delta_from);
$section = $field->getSection();
/** @var \Drupal\layout_builder\SectionStorageInterface $field_list */
$field_list = $entity->layout_builder__layout;
$section = $field_list->getSection($delta_from);
$block = $section->getBlock($region_from, $block_uuid);
$section->removeBlock($region_from, $block_uuid);
$component = $section->getComponent($block_uuid);
$section->removeComponent($block_uuid);
// If the block is moving from one section to another, update the original
// section and load the new one.
if ($delta_from !== $delta_to) {
$field->updateFromSection($section);
$field = $entity->layout_builder__layout->get($delta_to);
$section = $field->getSection();
$section = $field_list->getSection($delta_to);
}
// If a preceding block was specified, insert after that. Otherwise add the
// block to the front.
$component->setRegion($region_to);
if (isset($preceding_block_uuid)) {
$section->insertBlock($region_to, $block_uuid, $block, $preceding_block_uuid);
$section->insertAfterComponent($preceding_block_uuid, $component);
}
else {
$section->addBlock($region_to, $block_uuid, $block);
$section->appendComponent($component);
}
$field->updateFromSection($section);
$this->layoutTempstoreRepository->set($entity);
return $this->rebuildLayout($entity);
}
......
<?php
namespace Drupal\layout_builder\Field;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\layout_builder\Section;
/**
* Defines an interface for the layout section field item.
*
* @internal
* Layout Builder is currently experimental and should only be leveraged by
* experimental modules and development releases of contributed modules.
* See https://www.drupal.org/core/experimental for more information.
*
* @property string layout
* @property array[] layout_settings
* @property array[] section
*/
interface LayoutSectionItemInterface extends FieldItemInterface {
/**
* Gets a domain object for the layout section.
*
* @return \Drupal\layout_builder\Section
* The layout section.
*/
public function getSection();
/**
* Updates the stored value based on the domain object.
*
* @param \Drupal\layout_builder\Section $section
* The layout section.
*
* @return $this
*/
public function updateFromSection(Section $section);
}
......@@ -3,6 +3,8 @@
namespace Drupal\layout_builder\Field;
use Drupal\Core\Field\FieldItemList;
use Drupal\layout_builder\Section;
use Drupal\layout_builder\SectionStorageInterface;
/**
* Defines a item list class for layout section fields.
......@@ -11,22 +13,65 @@
*
* @see \Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem
*/
class LayoutSectionItemList extends FieldItemList implements LayoutSectionItemListInterface {
class LayoutSectionItemList extends FieldItemList implements SectionStorageInterface {
/**
* {@inheritdoc}
*/
public function addItem($index, $value) {
if ($this->get($index)) {
$start = array_slice($this->list, 0, $index);
$end = array_slice($this->list, $index);
$item = $this->createItem($index, $value);
public function insertSection($delta, Section $section) {
if ($this->get($delta)) {
/** @var \Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem $item */
$item = $this->createItem($delta);
$item->section = $section;
$start = array_slice($this->list, 0, $delta);
$end = array_slice($this->list, $delta);
$this->list = array_merge($start, [$item], $end);
}
else {
$item = $this->appendItem($value);
$this->appendSection($section);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function appendSection(Section $section) {
$this->appendItem()->section = $section;
return $this;
}
/**
* {@inheritdoc}
*/
public function getSections() {
$sections = [];
/** @var \Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem $item */
foreach ($this->list as $delta => $item) {
$sections[$delta] = $item->section;
}
return $item;
return $sections;
}
/**
* {@inheritdoc}
*/
public function getSection($delta) {
/** @var \Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem $item */
if (!$item = $this->get($delta)) {
throw new \OutOfBoundsException(sprintf('Invalid delta "%s" for the "%s" entity', $delta, $this->getEntity()->label()));
}
return $item->section;
}
/**
* {@inheritdoc}
*/
public function removeSection($delta) {
$this->removeItem($delta);
return $this;
}
}
<?php
namespace Drupal\layout_builder\Field;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Defines a item list class for layout section fields.
*
* @internal
* Layout Builder is currently experimental and should only be leveraged by
* experimental modules and development releases of contributed modules.
* See https://www.drupal.org/core/experimental for more information.
*
* @see \Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem
*/
interface LayoutSectionItemListInterface extends FieldItemListInterface {
/**
* {@inheritdoc}
*
* @return \Drupal\layout_builder\Field\LayoutSectionItemInterface|null
* The layout section item, if it exists.
*/
public function get($index);
/**
* Adds a new item to the list.
*
* If an item exists at the given index, the item at that position and others
* after it are shifted backward.
*
* @param int $index
* The position of the item in the list.
* @param mixed $value
* The value of the item to be stored at the specified position.
*
* @return \Drupal\Core\TypedData\TypedDataInterface
* The item that was appended.
*
* @todo Move to \Drupal\Core\TypedData\ListInterface directly in
* https://www.drupal.org/node/2907417.
*/
public function addItem($index, $value);
}
......@@ -3,6 +3,7 @@
namespace Drupal\layout_builder\Form;
use Drupal\layout_builder\Section;
use Drupal\layout_builder\SectionComponent;
/**
* Provides a form to add a block.
......@@ -29,7 +30,7 @@ protected function submitLabel() {
* {@inheritdoc}
*/
protected function submitBlock(Section $section, $region, $uuid, array $configuration) {
$section->addBlock($region, $uuid, $configuration);
$section->appendComponent(new SectionComponent($uuid, $region, $configuration));
}
}
......@@ -246,11 +246,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
$configuration = $this->block->getConfiguration();
/** @var \Drupal\layout_builder\Field\LayoutSectionItemInterface $field */
$field = $this->entity->layout_builder__layout->get($this->delta);
$section = $field->getSection();
$this->submitBlock($section, $this->region, $configuration['uuid'], ['block' => $configuration]);
$field->updateFromSection($section);
/** @var \Drupal\layout_builder\SectionStorageInterface $field_list */
$field_list = $this->entity->layout_builder__layout;
$section = $field_list->getSection($this->delta);
$this->submitBlock($section, $this->region, $configuration['uuid'], $configuration);
$this->layoutTempstoreRepository->set($this->entity);
$form_state->setRedirectUrl($this->entity->toUrl('layout-builder'));
......
......@@ -14,6 +14,7 @@
use Drupal\Core\Plugin\PluginWithFormsInterface;
use Drupal\layout_builder\Controller\LayoutRebuildTrait;
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
use Drupal\layout_builder\Section;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -121,14 +122,15 @@ public function buildForm(array $form, FormStateInterface $form_state, EntityInt
$this->delta = $delta;
$this->isUpdate = is_null($plugin_id);
$configuration = [];
if ($this->isUpdate) {
/** @var \Drupal\layout_builder\Field\LayoutSectionItemInterface $field */
$field = $this->entity->layout_builder__layout->get($this->delta);
$plugin_id = $field->layout;
$configuration = $field->layout_settings;
/** @var \Drupal\layout_builder\SectionStorageInterface $field_list */
$field_list = $this->entity->layout_builder__layout;
$section = $field_list->getSection($this->delta);
}
$this->layout = $this->layoutManager->createInstance($plugin_id, $configuration);
else {
$section = new Section($plugin_id);
}
$this->layout = $section->getLayout();
$form['#tree'] = TRUE;
$form['layout_settings'] = [];
......@@ -166,19 +168,13 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
$plugin_id = $this->layout->getPluginId();
$configuration = $this->layout->getConfiguration();
/** @var \Drupal\layout_builder\Field\LayoutSectionItemListInterface $field_list */
/** @var \Drupal\layout_builder\SectionStorageInterface $field_list */
$field_list = $this->entity->layout_builder__layout;
if ($this->isUpdate) {
$field = $field_list->get($this->delta);
$field->layout = $plugin_id;
$field->layout_settings = $configuration;
$field_list->getSection($this->delta)->setLayoutSettings($configuration);
}
else {
$field_list->addItem($this->delta, [
'layout' => $plugin_id,
'layout_settings' => $configuration,
'section' => [],
]);
$field_list->insertSection($this->delta, new Section($plugin_id, $configuration));
}
$this->layoutTempstoreRepository->set($this->entity);
......
......@@ -60,11 +60,9 @@ public function buildForm(array $form, FormStateInterface $form_state, EntityInt
* {@inheritdoc}
*/
protected function handleEntity(EntityInterface $entity, FormStateInterface $form_state) {
/** @var \Drupal\layout_builder\Field\LayoutSectionItemInterface $field */
$field = $entity->layout_builder__layout->get($this->delta);
$section = $field->getSection();
$section->removeBlock($this->region, $this->uuid);
$field->updateFromSection($section);
/** @var \Drupal\layout_builder\SectionStorageInterface $field_list */
$field_list = $this->entity->layout_builder__layout;
$field_list->getSection($this->delta)->removeComponent($this->uuid);
}
}
......@@ -40,14 +40,11 @@ public function getFormId() {
* The form array.
*/
public function buildForm(array $form, FormStateInterface $form_state, EntityInterface $entity = NULL, $delta = NULL, $region = NULL, $uuid = NULL) {
/** @var \Drupal\layout_builder\Field\LayoutSectionItemInterface $field */
$field = $entity->layout_builder__layout->get($delta);
$block = $field->getSection()->getBlock($region, $uuid);
if (empty($block['block']['id'])) {
throw new \InvalidArgumentException('Invalid UUID specified');
}
/** @var \Drupal\layout_builder\SectionStorageInterface $field_list */
$field_list = $entity->layout_builder__layout;
$plugin = $field_list->getSection($delta)->getComponent($uuid)->getPlugin();
return parent::buildForm($form, $form_state, $entity, $delta, $region, $block['block']['id'], $block['block']);
return parent::buildForm($form, $form_state, $entity, $delta, $region, $plugin->getPluginId(), $plugin->getConfiguration());
}
/**
......@@ -61,7 +58,7 @@ protected function submitLabel() {
* {@inheritdoc}
*/
protected function submitBlock(Section $section, $region, $uuid, array $configuration) {
$section->updateBlock($region, $uuid, $configuration);
$section->getComponent($uuid)->setConfiguration($configuration);
}
}
......@@ -2,14 +2,11 @@
namespace Drupal\layout_builder;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Layout\LayoutInterface;
use Drupal\Core\Layout\LayoutPluginManagerInterface;
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
......@@ -17,6 +14,8 @@
* Builds the UI for layout sections.
*
* @internal
*
* @todo Remove in https://www.drupal.org/project/drupal/issues/2928450.
*/
class LayoutSectionBuilder {
......@@ -84,37 +83,21 @@ public function __construct(AccountInterface $account, LayoutPluginManagerInterf
*
* @param \Drupal\Core\Layout\LayoutInterface $layout
* The ID of the layout.
* @param array $section
* An array of configuration, keyed first by region and then by block UUID.
* @param \Drupal\layout_builder\SectionComponent[] $components
* An array of components.
*
* @return array
* The render array for a given section.
*/
public function buildSectionFromLayout(LayoutInterface $layout, array $section) {
$cacheability = CacheableMetadata::createFromRenderArray([]);
public function buildSectionFromLayout(LayoutInterface $layout, array $components) {
$regions = [];
$weight = 0;
foreach ($section as $region => $blocks) {
if (!is_array($blocks)) {
throw new \InvalidArgumentException(sprintf('The "%s" region in the "%s" layout has invalid configuration', $region, $layout->getPluginId()));
}
foreach ($blocks as $uuid => $configuration) {
if (!is_array($configuration) || !isset($configuration['block'])) {
throw new \InvalidArgumentException(sprintf('The block with UUID of "%s" has invalid configuration', $uuid));
}
if ($block_output = $this->buildBlock($uuid, $configuration['block'], $cacheability)) {
$block_output['#weight'] = $weight++;
$regions[$region][$uuid] = $block_output;
}
foreach ($components as $component) {
if ($output = $component->toRenderArray()) {
$regions[$component->getRegion()][$component->getUuid()] = $output;
}
}
$result = $layout->build($regions);
$cacheability->applyTo($result);
return $result;
return $layout->build($regions);
}
/**
......@@ -124,78 +107,15 @@ public function buildSectionFromLayout(LayoutInterface $layout, array $section)
* The ID of the layout.
* @param array $layout_settings
* The configuration for the layout.
* @param array $section
* An array of configuration, keyed first by region and then by block UUID.
* @param \Drupal\layout_builder\SectionComponent[] $components
* An array of components.
*
* @return array
* The render array for a given section.
*/
public function buildSection($layout_id, array $layout_settings, array $section) {
public function buildSection($layout_id, array $layout_settings, array $components) {
$layout = $this->layoutPluginManager->createInstance($layout_id, $layout_settings);
return $this->buildSectionFromLayout($layout, $section);
}
/**
* Builds the render array for a given block.
*
* @param string $uuid
* The UUID of this block instance.
* @param array $configuration
* An array of configuration relevant to the block instance. Must contain
* the plugin ID with the key 'id'.
* @param \Drupal\Core\Cache\CacheableMetadata $cacheability
* The cacheability metadata.
*
* @return array|null
* The render array representing this block, if accessible. NULL otherwise.
*/
protected function buildBlock($uuid, array $configuration, CacheableMetadata $cacheability) {
$block = $this->getBlock($uuid, $configuration);
$access = $block->access($this->account, TRUE);
$cacheability->addCacheableDependency($access);
$block_output = NULL;
if ($access->isAllowed()) {
$block_output = [
'#theme' => 'block',
'#configuration' => $block->getConfiguration(),
'#plugin_id' => $block->getPluginId(),
'#base_plugin_id' => $block->getBaseId(),
'#derivative_plugin_id' => $block->getDerivativeId(),
'content' => $block->build(),
];
$cacheability->addCacheableDependency($block);
}
return $block_output;
}
/**
* Gets a block instance.
*
* @param string $uuid
* The UUID of this block instance.
* @param array $configuration
* An array of configuration relevant to the block instance. Must contain
* the plugin ID with the key 'id'.
*
* @return \Drupal\Core\Block\BlockPluginInterface
* The block instance.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* Thrown when the configuration parameter does not contain 'id'.
*/
protected function getBlock($uuid, array $configuration) {
if (!isset($configuration['id'])) {
throw new PluginException(sprintf('No plugin ID specified for block with "%s" UUID', $uuid));
}
$block = $this->blockManager->createInstance($configuration['id'], $configuration);
if ($block instanceof ContextAwarePluginInterface) {
$contexts = $this->contextRepository->getRuntimeContexts(array_values($block->getContextMapping()));
$this->contextHandler->applyContextMapping($block, $contexts);
}
return $block;
return $this->buildSectionFromLayout($layout, $components);
}
}
<?php
namespace Drupal\layout_builder\Plugin\DataType;
use Drupal\Core\TypedData\TypedData;
use Drupal\layout_builder\Section;
/**
* Provides a data type wrapping \Drupal\layout_builder\Section.
*
* @DataType(
* id = "layout_section",
* label = @Translation("Layout Section"),
* description = @Translation("A layout section"),
* )
*/
class SectionData extends TypedData {
/**
* The section object.
*
* @var \Drupal\layout_builder\Section
*/
protected $value;
/**
* {@inheritdoc}
*/
public function setValue($value, $notify = TRUE) {
if ($value && !$value instanceof Section) {
throw new \InvalidArgumentException(sprintf('Value assigned to "%s" is not a valid section', $this->getName()));
}
parent::setValue($value, $notify);
}
}
......@@ -78,9 +78,9 @@ public static function create(ContainerInterface $container, array $configuratio
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
/** @var \Drupal\layout_builder\Field\LayoutSectionItemInterface[] $items */
foreach ($items as $delta => $item) {
$elements[$delta] = $this->builder->buildSection($item->layout, $item->layout_settings, $item->section);
/** @var \Drupal\layout_builder\SectionStorageInterface $items */
foreach ($items->getSections() as $delta => $section) {
$elements[$delta] = $section->toRenderArray();
}
return $elements;
......
......@@ -7,8 +7,6 @@
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\MapDataDefinition;
use Drupal\layout_builder\Field\LayoutSectionItemInterface;
use Drupal\layout_builder\Section;
/**
......@@ -25,22 +23,16 @@
* no_ui = TRUE,
* cardinality = \Drupal\Core\Field\FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
* )