Unverified Commit 10eb6921 authored by larowlan's avatar larowlan
Browse files

Issue #2937483 by tim.plunkett, larowlan, EclipseGc, samuel.mortenson:...

Issue #2937483 by tim.plunkett, larowlan, EclipseGc, samuel.mortenson: Defining a new type of section storage relies on magic strings and hidden assumptions
parent 66dda072
......@@ -73,9 +73,8 @@ function layout_builder_form_entity_form_display_edit_form_alter(&$form, FormSta
*/
function layout_builder_field_config_insert(FieldConfigInterface $field_config) {
// Clear the sample entity for this entity type and bundle.
/** @var \Drupal\Core\TempStore\SharedTempStore $tempstore */
$tempstore = \Drupal::service('tempstore.shared')->get('layout_builder.sample_entity');
$tempstore->delete($field_config->getTargetEntityTypeId() . '.' . $field_config->getTargetBundle());
$sample_entity_generator = \Drupal::service('layout_builder.sample_entity_generator');
$sample_entity_generator->delete($field_config->getTargetEntityTypeId(), $field_config->getTargetBundle());
\Drupal::service('plugin.manager.block')->clearCachedDefinitions();
}
......@@ -84,8 +83,7 @@ function layout_builder_field_config_insert(FieldConfigInterface $field_config)
*/
function layout_builder_field_config_delete(FieldConfigInterface $field_config) {
// Clear the sample entity for this entity type and bundle.
/** @var \Drupal\Core\TempStore\SharedTempStore $tempstore */
$tempstore = \Drupal::service('tempstore.shared')->get('layout_builder.sample_entity');
$tempstore->delete($field_config->getTargetEntityTypeId() . '.' . $field_config->getTargetBundle());
$sample_entity_generator = \Drupal::service('layout_builder.sample_entity_generator');
$sample_entity_generator->delete($field_config->getTargetEntityTypeId(), $field_config->getTargetBundle());
\Drupal::service('plugin.manager.block')->clearCachedDefinitions();
}
......@@ -115,6 +115,3 @@ layout_builder.move_block:
parameters:
section_storage:
layout_builder_tempstore: TRUE
route_callbacks:
- 'layout_builder.routes:getRoutes'
......@@ -6,9 +6,12 @@ services:
class: Drupal\layout_builder\Access\LayoutSectionAccessCheck
tags:
- { name: access_check, applies_to: _has_layout_section }
plugin.manager.layout_builder.section_storage:
class: Drupal\layout_builder\SectionStorage\SectionStorageManager
parent: default_plugin_manager
layout_builder.routes:
class: Drupal\layout_builder\Routing\LayoutBuilderRoutes
arguments: ['@entity_type.manager', '@entity_field.manager']
arguments: ['@plugin.manager.layout_builder.section_storage']
tags:
- { name: event_subscriber }
layout_builder.route_enhancer:
......@@ -17,17 +20,14 @@ services:
- { name: route_enhancer }
layout_builder.param_converter:
class: Drupal\layout_builder\Routing\LayoutTempstoreParamConverter
arguments: ['@layout_builder.tempstore_repository', '@class_resolver']
arguments: ['@layout_builder.tempstore_repository', '@plugin.manager.layout_builder.section_storage']
tags:
- { name: paramconverter, priority: 10 }
layout_builder.section_storage_param_converter.defaults:
class: Drupal\layout_builder\Routing\SectionStorageDefaultsParamConverter
arguments: ['@entity.manager']
layout_builder.section_storage_param_converter.overrides:
class: Drupal\layout_builder\Routing\SectionStorageOverridesParamConverter
arguments: ['@entity.manager']
cache_context.layout_builder_is_active:
class: Drupal\layout_builder\Cache\LayoutBuilderIsActiveCacheContext
arguments: ['@current_route_match']
tags:
- { name: cache.context}
layout_builder.sample_entity_generator:
class: Drupal\layout_builder\Entity\LayoutBuilderSampleEntityGenerator
arguments: ['@tempstore.shared', '@entity_type.manager']
<?php
namespace Drupal\layout_builder\Annotation;
use Drupal\Component\Annotation\Plugin;
use Drupal\layout_builder\SectionStorage\SectionStorageDefinition;
/**
* Defines a Section Storage type annotation object.
*
* @see \Drupal\layout_builder\SectionStorage\SectionStorageManager
* @see plugin_api
*
* @Annotation
*/
class SectionStorage extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* {@inheritdoc}
*/
public function get() {
return new SectionStorageDefinition($this->definition);
}
}
......@@ -305,7 +305,7 @@ public function saveLayout(SectionStorageInterface $section_storage) {
$this->messenger->addMessage($this->t('The layout has been saved.'));
}
return new RedirectResponse($section_storage->getCanonicalUrl()->setAbsolute()->toString());
return new RedirectResponse($section_storage->getRedirectUrl()->setAbsolute()->toString());
}
/**
......@@ -322,7 +322,7 @@ public function cancelLayout(SectionStorageInterface $section_storage) {
$this->messenger->addMessage($this->t('The changes to the layout have been discarded.'));
return new RedirectResponse($section_storage->getCanonicalUrl()->setAbsolute()->toString());
return new RedirectResponse($section_storage->getRedirectUrl()->setAbsolute()->toString());
}
}
<?php
namespace Drupal\layout_builder;
/**
* Defines an interface for an object that stores layout sections for defaults.
*
* @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.
*/
interface DefaultsSectionStorageInterface extends SectionStorageInterface {
/**
* Determines if the defaults allow custom overrides.
*
* @return bool
* TRUE if custom overrides are allowed, FALSE otherwise.
*/
public function isOverridable();
/**
* Sets the defaults to allow or disallow overrides.
*
* @param bool $overridable
* TRUE if the display should allow overrides, FALSE otherwise.
*
* @return $this
*/
public function setOverridable($overridable = TRUE);
}
......@@ -5,18 +5,17 @@
use Drupal\Component\Plugin\DependentPluginInterface;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\ContentEntityStorageInterface;
use Drupal\Core\Entity\Entity\EntityViewDisplay as BaseEntityViewDisplay;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\layout_builder\Section;
use Drupal\layout_builder\SectionComponent;
use Drupal\layout_builder\SectionStorage\SectionStorageTrait;
/**
* Provides an entity view display entity that has a layout.
......@@ -28,6 +27,8 @@
*/
class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements LayoutEntityDisplayInterface {
use SectionStorageTrait;
/**
* {@inheritdoc}
*/
......@@ -51,104 +52,13 @@ public function getSections() {
}
/**
* Store the information for all sections.
*
* @param \Drupal\layout_builder\Section[] $sections
* The sections information.
*
* @return $this
* {@inheritdoc}
*/
protected function setSections(array $sections) {
$this->setThirdPartySetting('layout_builder', 'sections', array_values($sections));
return $this;
}
/**
* {@inheritdoc}
*/
public function count() {
return count($this->getSections());
}
/**
* {@inheritdoc}
*/
public function getSection($delta) {
if (!$this->hasSection($delta)) {
throw new \OutOfBoundsException(sprintf('Invalid delta "%s" for the "%s" entity', $delta, $this->id()));
}
return $this->getSections()[$delta];
}
/**
* Sets the section for the given delta on the display.
*
* @param int $delta
* The delta of the section.
* @param \Drupal\layout_builder\Section $section
* The layout section.
*
* @return $this
*/
protected function setSection($delta, Section $section) {
$sections = $this->getSections();
$sections[$delta] = $section;
$this->setSections($sections);
return $this;
}
/**
* {@inheritdoc}
*/
public function appendSection(Section $section) {
$delta = $this->count();
$this->setSection($delta, $section);
return $this;
}
/**
* {@inheritdoc}
*/
public function insertSection($delta, Section $section) {
if ($this->hasSection($delta)) {
$sections = $this->getSections();
// @todo Use https://www.drupal.org/node/66183 once resolved.
$start = array_slice($sections, 0, $delta);
$end = array_slice($sections, $delta);
$this->setSections(array_merge($start, [$section], $end));
}
else {
$this->appendSection($section);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function removeSection($delta) {
$sections = $this->getSections();
unset($sections[$delta]);
$this->setSections($sections);
return $this;
}
/**
* Indicates if there is a section at the specified delta.
*
* @param int $delta
* The delta of the section.
*
* @return bool
* TRUE if there is a section for this delta, FALSE otherwise.
*/
protected function hasSection($delta) {
$sections = $this->getSections();
return isset($sections[$delta]);
}
/**
* {@inheritdoc}
*/
......@@ -255,50 +165,6 @@ public function buildMultiple(array $entities) {
return $build_list;
}
/**
* {@inheritdoc}
*/
public function getContexts() {
$entity = $this->getSampleEntity($this->getTargetEntityTypeId(), $this->getTargetBundle());
$context_label = new TranslatableMarkup('@entity being viewed', ['@entity' => $entity->getEntityType()->getLabel()]);
// @todo Use EntityContextDefinition after resolving
// https://www.drupal.org/node/2932462.
$contexts = [];
$contexts['layout_builder.entity'] = new Context(new ContextDefinition("entity:{$entity->getEntityTypeId()}", $context_label), $entity);
return $contexts;
}
/**
* Returns a sample entity.
*
* @param string $entity_type_id
* The entity type ID.
* @param string $bundle_id
* The bundle ID.
*
* @return \Drupal\Core\Entity\EntityInterface
* An entity.
*/
protected function getSampleEntity($entity_type_id, $bundle_id) {
/** @var \Drupal\Core\TempStore\SharedTempStore $tempstore */
$tempstore = \Drupal::service('tempstore.shared')->get('layout_builder.sample_entity');
if ($entity = $tempstore->get("$entity_type_id.$bundle_id")) {
return $entity;
}
$entity_storage = $this->entityTypeManager()->getStorage($entity_type_id);
if (!$entity_storage instanceof ContentEntityStorageInterface) {
throw new \InvalidArgumentException(sprintf('The "%s" entity storage is not supported', $entity_type_id));
}
$entity = $entity_storage->createWithSampleValues($bundle_id);
// Mark the sample entity as being a preview.
$entity->in_preview = TRUE;
$tempstore->set("$entity_type_id.$bundle_id", $entity);
return $entity;
}
/**
* Gets the runtime sections for a given entity.
*
......@@ -328,51 +194,6 @@ public function label() {
return new TranslatableMarkup('@bundle @label', ['@bundle' => $bundle_label, '@label' => $target_entity_type->getPluralLabel()]);
}
/**
* {@inheritdoc}
*/
public static function getStorageType() {
return 'defaults';
}
/**
* {@inheritdoc}
*/
public function getStorageId() {
return $this->id();
}
/**
* {@inheritdoc}
*/
public function getCanonicalUrl() {
return Url::fromRoute("entity.entity_view_display.{$this->getTargetEntityTypeId()}.view_mode", $this->getRouteParameters());
}
/**
* {@inheritdoc}
*/
public function getLayoutBuilderUrl() {
return Url::fromRoute("entity.entity_view_display.{$this->getTargetEntityTypeId()}.layout_builder", $this->getRouteParameters());
}
/**
* Returns the route parameters needed to build routes for this entity.
*
* @return string[]
* An array of route parameters.
*/
protected function getRouteParameters() {
$route_parameters = [];
$entity_type = $this->entityTypeManager()->getDefinition($this->getTargetEntityTypeId());
$bundle_parameter_key = $entity_type->getBundleEntityType() ?: 'bundle';
$route_parameters[$bundle_parameter_key] = $this->getTargetBundle();
$route_parameters['view_mode_name'] = $this->getMode();
return $route_parameters;
}
/**
* {@inheritdoc}
*/
......
<?php
namespace Drupal\layout_builder\Entity;
use Drupal\Core\Entity\ContentEntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\TempStore\SharedTempStoreFactory;
/**
* Generates a sample entity for use by the Layout Builder.
*
* @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.
*/
class LayoutBuilderSampleEntityGenerator {
/**
* The shared tempstore factory.
*
* @var \Drupal\Core\TempStore\SharedTempStoreFactory
*/
protected $tempStoreFactory;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* LayoutBuilderSampleEntityGenerator constructor.
*
* @param \Drupal\Core\TempStore\SharedTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(SharedTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager) {
$this->tempStoreFactory = $temp_store_factory;
$this->entityTypeManager = $entity_type_manager;
}
/**
* Gets a sample entity for a given entity type and bundle.
*
* @param string $entity_type_id
* The entity type ID.
* @param string $bundle_id
* The bundle ID.
*
* @return \Drupal\Core\Entity\EntityInterface
* An entity.
*/
public function get($entity_type_id, $bundle_id) {
$tempstore = $this->tempStoreFactory->get('layout_builder.sample_entity');
if ($entity = $tempstore->get("$entity_type_id.$bundle_id")) {
return $entity;
}
$entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
if (!$entity_storage instanceof ContentEntityStorageInterface) {
throw new \InvalidArgumentException(sprintf('The "%s" entity storage is not supported', $entity_type_id));
}
$entity = $entity_storage->createWithSampleValues($bundle_id);
// Mark the sample entity as being a preview.
$entity->in_preview = TRUE;
$tempstore->set("$entity_type_id.$bundle_id", $entity);
return $entity;
}
/**
* Deletes a sample entity for a given entity type and bundle.
*
* @param string $entity_type_id
* The entity type ID.
* @param string $bundle_id
* The bundle ID.
*
* @return $this
*/
public function delete($entity_type_id, $bundle_id) {
$tempstore = $this->tempStoreFactory->get('layout_builder.sample_entity');
$tempstore->delete("$entity_type_id.$bundle_id");
return $this;
}
}
......@@ -3,7 +3,7 @@
namespace Drupal\layout_builder\Entity;
use Drupal\Core\Entity\Display\EntityDisplayInterface;
use Drupal\layout_builder\SectionStorageInterface;
use Drupal\layout_builder\SectionListInterface;
/**
* Provides an interface for entity displays that have layout.
......@@ -13,7 +13,7 @@
* experimental modules and development releases of contributed modules.
* See https://www.drupal.org/core/experimental for more information.
*/
interface LayoutEntityDisplayInterface extends EntityDisplayInterface, SectionStorageInterface {
interface LayoutEntityDisplayInterface extends EntityDisplayInterface, SectionListInterface {
/**
* Determines if the display allows custom overrides.
......
......@@ -3,13 +3,8 @@
namespace Drupal\layout_builder\Field;
use Drupal\Core\Field\FieldItemList;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
use Drupal\layout_builder\OverridesSectionStorageInterface;
use Drupal\layout_builder\Section;
use Drupal\layout_builder\SectionStorageInterface;
use Drupal\layout_builder\SectionListInterface;
use Drupal\layout_builder\SectionStorage\SectionStorageTrait;
/**
* Defines a item list class for layout section fields.
......@@ -18,35 +13,9 @@
*
* @see \Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem
*/
class LayoutSectionItemList extends FieldItemList implements SectionStorageInterface, OverridesSectionStorageInterface {
class LayoutSectionItemList extends FieldItemList implements SectionListInterface {
/**
* {@inheritdoc}
*/
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;
// @todo Use https://www.drupal.org/node/66183 once resolved.
$start = array_slice($this->list, 0, $delta);
$end = array_slice($this->list, $delta);
$this->list = array_merge($start, [$item], $end);
}
else {
$this->appendSection($section);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function appendSection(Section $section) {
$this->appendItem()->section = $section;
return $this;
}
use SectionStorageTrait;
/**
* {@inheritdoc}
......@@ -63,84 +32,18 @@ public function getSections() {
/**
* {@inheritdoc}
*/
public function getSection($delta) {
protected function setSections(array $sections) {
$this->list = [];
$sections = array_values($sections);
/** @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()));
foreach ($sections as $section) {
$item = $this->appendItem();
$item->section = $section;
}
return $item->section;
}
/**
* {@inheritdoc}
*/
public function removeSection($delta) {
$this->removeItem($delta);
return $this;
}
/**
* {@inheritdoc}
*/
public function getContexts() {
$entity = $this->getEntity();
// @todo Use EntityContextDefinition after resolving
// https://www.drupal.org/node/2932462.
$contexts['layout_builder.entity'] = new Context(new ContextDefinition("entity:{$entity->getEntityTypeId()}", new TranslatableMarkup('@entity being viewed', ['@entity' => $entity->getEntityType()->getLabel()])), $entity);
return $contexts;