Issue #3004536 by tim.plunkett, tedbow, Sam152, phenaproxima, mark_fullmer: Move the Layout Builder UI into an entity form for better integration with other content authoring modules and core features

8.7.x
Lee Rowlands 2019-02-13 14:09:13 +10:00
parent 1cc42e056b
commit a9a53828a3
No known key found for this signature in database
GPG Key ID: 2B829A3DF9204DC4
22 changed files with 554 additions and 264 deletions

View File

@ -0,0 +1,32 @@
<?php
/**
* @file
* Hooks provided by the Layout Builder module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Allows customization of the Layout Builder UI for per-entity overrides.
*
* The Layout Builder widget will be added with a weight of -10 after this hook
* is invoked.
*
* @see hook_entity_form_display_alter()
* @see \Drupal\layout_builder\Form\OverridesEntityForm::init()
*/
function hook_layout_builder_overrides_entity_form_display_alter(\Drupal\Core\Entity\Display\EntityFormDisplayInterface $display) {
$display->setComponent('moderation_state', [
'type' => 'moderation_state_default',
'weight' => 2,
'settings' => [],
]);
}
/**
* @} End of "addtogroup hooks".
*/

View File

@ -6,6 +6,7 @@
*/
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
@ -13,7 +14,9 @@ use Drupal\Core\Url;
use Drupal\field\FieldConfigInterface;
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplayStorage;
use Drupal\layout_builder\Form\DefaultsEntityForm;
use Drupal\layout_builder\Form\LayoutBuilderEntityViewDisplayForm;
use Drupal\layout_builder\Form\OverridesEntityForm;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\layout_builder\Plugin\Block\ExtraFieldBlock;
use Drupal\layout_builder\InlineBlockEntityOperations;
@ -54,7 +57,15 @@ function layout_builder_entity_type_alter(array &$entity_types) {
$entity_types['entity_view_display']
->setClass(LayoutBuilderEntityViewDisplay::class)
->setStorageClass(LayoutBuilderEntityViewDisplayStorage::class)
->setFormClass('layout_builder', DefaultsEntityForm::class)
->setFormClass('edit', LayoutBuilderEntityViewDisplayForm::class);
// Ensure every fieldable entity type has a layout form.
foreach ($entity_types as $entity_type) {
if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) {
$entity_type->setFormClass('layout_builder', OverridesEntityForm::class);
}
}
}
/**

View File

@ -86,3 +86,10 @@ function layout_builder_post_update_cancel_link_to_discard_changes_form() {
function layout_builder_post_update_remove_layout_is_rebuilding() {
// Empty post-update hook.
}
/**
* Clear caches due to routing changes to move the Layout Builder UI to forms.
*/
function layout_builder_post_update_routing_entity_form() {
// Empty post-update hook.
}

View File

@ -2,64 +2,17 @@
namespace Drupal\layout_builder\Controller;
use Drupal\Core\Ajax\AjaxHelperTrait;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
use Drupal\layout_builder\OverridesSectionStorageInterface;
use Drupal\layout_builder\SectionStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Defines a controller to provide the Layout Builder admin UI.
*
* @internal
*/
class LayoutBuilderController implements ContainerInjectionInterface {
class LayoutBuilderController {
use LayoutBuilderContextTrait;
use StringTranslationTrait;
use AjaxHelperTrait;
/**
* The layout tempstore repository.
*
* @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
*/
protected $layoutTempstoreRepository;
/**
* The messenger service.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* LayoutBuilderController constructor.
*
* @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
* The layout tempstore repository.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger service.
*/
public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository, MessengerInterface $messenger) {
$this->layoutTempstoreRepository = $layout_tempstore_repository;
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('layout_builder.tempstore_repository'),
$container->get('messenger')
);
}
/**
* Provides a title callback.
@ -90,27 +43,4 @@ class LayoutBuilderController implements ContainerInjectionInterface {
];
}
/**
* Saves the layout.
*
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
* The section storage.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect response.
*/
public function saveLayout(SectionStorageInterface $section_storage) {
$section_storage->save();
$this->layoutTempstoreRepository->delete($section_storage);
if ($section_storage instanceof OverridesSectionStorageInterface) {
$this->messenger->addMessage($this->t('The layout override has been saved.'));
}
else {
$this->messenger->addMessage($this->t('The layout has been saved.'));
}
return new RedirectResponse($section_storage->getRedirectUrl()->setAbsolute()->toString());
}
}

View File

@ -0,0 +1,130 @@
<?php
namespace Drupal\layout_builder\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
use Drupal\layout_builder\SectionStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a form containing the Layout Builder UI for defaults.
*
* @internal
*/
class DefaultsEntityForm extends EntityForm {
/**
* Layout tempstore repository.
*
* @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
*/
protected $layoutTempstoreRepository;
/**
* The section storage.
*
* @var \Drupal\layout_builder\SectionStorageInterface
*/
protected $sectionStorage;
/**
* Constructs a new DefaultsEntityForm.
*
* @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
* The layout tempstore repository.
*/
public function __construct(LayoutTempstoreRepositoryInterface $layout_tempstore_repository) {
$this->layoutTempstoreRepository = $layout_tempstore_repository;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('layout_builder.tempstore_repository')
);
}
/**
* {@inheritdoc}
*/
public function getBaseFormId() {
return $this->getEntity()->getEntityTypeId() . '_layout_builder_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL) {
$form['layout_builder'] = [
'#type' => 'layout_builder',
'#section_storage' => $section_storage,
];
$this->sectionStorage = $section_storage;
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function buildEntity(array $form, FormStateInterface $form_state) {
// \Drupal\Core\Entity\EntityForm::buildEntity() clones the entity object.
// Keep it in sync with the one used by the section storage.
$this->setEntity($this->sectionStorage->getContextValue('display'));
$entity = parent::buildEntity($form, $form_state);
$this->sectionStorage->setContextValue('display', $entity);
return $entity;
}
/**
* {@inheritdoc}
*/
public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {
$route_parameters = $route_match->getParameters()->all();
return $this->entityTypeManager->getStorage('entity_view_display')->load($route_parameters['entity_type_id'] . '.' . $route_parameters['bundle'] . '.' . $route_parameters['view_mode_name']);
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$actions = parent::actions($form, $form_state);
$actions['submit']['#value'] = $this->t('Save layout');
$actions['#weight'] = -1000;
$actions['discard_changes'] = [
'#type' => 'link',
'#title' => $this->t('Discard changes'),
'#attributes' => ['class' => ['button']],
'#url' => $this->sectionStorage->getLayoutBuilderUrl('discard_changes'),
];
return $actions;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$return = $this->sectionStorage->save();
$this->layoutTempstoreRepository->delete($this->sectionStorage);
$this->messenger()->addMessage($this->t('The layout has been saved.'));
$form_state->setRedirectUrl($this->sectionStorage->getRedirectUrl());
return $return;
}
/**
* Retrieves the section storage object.
*
* @return \Drupal\layout_builder\SectionStorageInterface
* The section storage for the current form.
*/
public function getSectionStorage() {
return $this->sectionStorage;
}
}

View File

@ -0,0 +1,173 @@
<?php
namespace Drupal\layout_builder\Form;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
use Drupal\layout_builder\SectionStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a form containing the Layout Builder UI for overrides.
*
* @internal
*/
class OverridesEntityForm extends ContentEntityForm {
/**
* Layout tempstore repository.
*
* @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
*/
protected $layoutTempstoreRepository;
/**
* The section storage.
*
* @var \Drupal\layout_builder\SectionStorageInterface
*/
protected $sectionStorage;
/**
* Constructs a new OverridesEntityForm.
*
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository service.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle service.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Drupal\layout_builder\LayoutTempstoreRepositoryInterface $layout_tempstore_repository
* The layout tempstore repository.
*/
public function __construct(EntityRepositoryInterface $entity_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info, TimeInterface $time, LayoutTempstoreRepositoryInterface $layout_tempstore_repository) {
parent::__construct($entity_repository, $entity_type_bundle_info, $time);
$this->layoutTempstoreRepository = $layout_tempstore_repository;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.repository'),
$container->get('entity_type.bundle.info'),
$container->get('datetime.time'),
$container->get('layout_builder.tempstore_repository')
);
}
/**
* {@inheritdoc}
*/
public function getBaseFormId() {
return $this->getEntity()->getEntityTypeId() . '_layout_builder_form';
}
/**
* {@inheritdoc}
*/
protected function init(FormStateInterface $form_state) {
parent::init($form_state);
// Create a transient display that is not persisted, but used only for
// building the components required for the layout form.
$display = EntityFormDisplay::create([
'targetEntityType' => $this->getEntity()->getEntityTypeId(),
'bundle' => $this->getEntity()->bundle(),
]);
// Allow modules to choose if they are relevant to the layout form.
$this->moduleHandler->alter('layout_builder_overrides_entity_form_display', $display);
// Add the widget for Layout Builder after the alter.
$display->setComponent(OverridesSectionStorage::FIELD_NAME, [
'type' => 'layout_builder_widget',
'weight' => -10,
'settings' => [],
]);
$this->setFormDisplay($display, $form_state);
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL) {
$this->sectionStorage = $section_storage;
$form = parent::buildForm($form, $form_state);
// @todo \Drupal\layout_builder\Field\LayoutSectionItemList::defaultAccess()
// restricts all access to the field, explicitly allow access here until
// https://www.drupal.org/node/2942975 is resolved.
$form[OverridesSectionStorage::FIELD_NAME]['#access'] = TRUE;
return $form;
}
/**
* {@inheritdoc}
*/
public function buildEntity(array $form, FormStateInterface $form_state) {
// \Drupal\Core\Entity\EntityForm::buildEntity() clones the entity object.
// Keep it in sync with the one used by the section storage.
$this->setEntity($this->sectionStorage->getContextValue('entity'));
$entity = parent::buildEntity($form, $form_state);
$this->sectionStorage->setContextValue('entity', $entity);
return $entity;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$return = parent::save($form, $form_state);
$this->layoutTempstoreRepository->delete($this->sectionStorage);
$this->messenger()->addStatus($this->t('The layout override has been saved.'));
$form_state->setRedirectUrl($this->sectionStorage->getRedirectUrl());
return $return;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$actions = parent::actions($form, $form_state);
$actions['submit']['#value'] = $this->t('Save layout');
$actions['delete']['#access'] = FALSE;
$actions['#weight'] = -1000;
$actions['discard_changes'] = [
'#type' => 'link',
'#title' => $this->t('Discard changes'),
'#attributes' => ['class' => ['button']],
'#url' => $this->sectionStorage->getLayoutBuilderUrl('discard_changes'),
];
// @todo This link should be conditionally displayed, see
// https://www.drupal.org/node/2917777.
$actions['revert'] = [
'#type' => 'link',
'#title' => $this->t('Revert to defaults'),
'#attributes' => ['class' => ['button']],
'#url' => $this->sectionStorage->getLayoutBuilderUrl('revert'),
];
return $actions;
}
/**
* Retrieves the section storage object.
*
* @return \Drupal\layout_builder\SectionStorageInterface
* The section storage for the current form.
*/
public function getSectionStorage() {
return $this->sectionStorage;
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace Drupal\layout_builder\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
/**
* A widget to display the layout form.
*
* @FieldWidget(
* id = "layout_builder_widget",
* label = @Translation("Layout Builder Widget"),
* description = @Translation("A field widget for Layout Builder."),
* field_types = {
* "layout_section",
* },
* multiple_values = TRUE,
* )
*
* @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 LayoutBuilderWidget extends WidgetBase {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element += [
'#type' => 'layout_builder',
'#section_storage' => $this->getSectionStorage($form_state),
];
return $element;
}
/**
* {@inheritdoc}
*/
public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
// @todo This isn't resilient to being set twice, during validation and
// save https://www.drupal.org/project/drupal/issues/2833682.
if (!$form_state->isValidationComplete()) {
return;
}
$items->setValue($this->getSectionStorage($form_state)->getSections());
}
/**
* Gets the section storage.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return \Drupal\layout_builder\SectionStorageInterface
* The section storage loaded from the tempstore.
*/
private function getSectionStorage(FormStateInterface $form_state) {
return $form_state->getFormObject()->getSectionStorage();
}
}

View File

@ -41,7 +41,7 @@ use Symfony\Component\Routing\RouteCollection;
* experimental modules and development releases of contributed modules.
* See https://www.drupal.org/core/experimental for more information.
*/
class DefaultsSectionStorage extends SectionStorageBase implements ContainerFactoryPluginInterface, DefaultsSectionStorageInterface, SectionStorageLocalTaskProviderInterface {
class DefaultsSectionStorage extends SectionStorageBase implements ContainerFactoryPluginInterface, DefaultsSectionStorageInterface {
/**
* The entity type manager.
@ -172,7 +172,14 @@ class DefaultsSectionStorage extends SectionStorageBase implements ContainerFact
$options = $entity_route->getOptions();
$options['_admin_route'] = FALSE;
$this->buildLayoutRoutes($collection, $this->getPluginDefinition(), $path, $defaults, $requirements, $options, $entity_type_id);
$this->buildLayoutRoutes($collection, $this->getPluginDefinition(), $path, $defaults, $requirements, $options, $entity_type_id, 'entity_view_display');
// Set field_ui.route_enhancer to run on the manage layout form.
if (isset($defaults['bundle_key'])) {
$collection->get("layout_builder.defaults.$entity_type_id.view")
->setOption('_field_ui', TRUE)
->setDefault('bundle', '');
}
$route_names = [
"entity.entity_view_display.{$entity_type_id}.default",
@ -194,32 +201,6 @@ class DefaultsSectionStorage extends SectionStorageBase implements ContainerFact
}
}
/**
* {@inheritdoc}
*/
public function buildLocalTasks($base_plugin_definition) {
$local_tasks = [];
foreach ($this->getEntityTypes() as $entity_type_id => $entity_type) {
$local_tasks["layout_builder.defaults.$entity_type_id.view"] = $base_plugin_definition + [
'route_name' => "layout_builder.defaults.$entity_type_id.view",
'title' => $this->t('Manage layout'),
'base_route' => "layout_builder.defaults.$entity_type_id.view",
];
$local_tasks["layout_builder.defaults.$entity_type_id.save"] = $base_plugin_definition + [
'route_name' => "layout_builder.defaults.$entity_type_id.save",
'title' => $this->t('Save Layout'),
'parent_id' => "layout_builder_ui:layout_builder.defaults.$entity_type_id.view",
];
$local_tasks["layout_builder.defaults.$entity_type_id.discard_changes"] = $base_plugin_definition + [
'route_name' => "layout_builder.defaults.$entity_type_id.discard_changes",
'title' => $this->t('Discard changes'),
'weight' => 5,
'parent_id' => "layout_builder_ui:layout_builder.defaults.$entity_type_id.view",
];
}
return $local_tasks;
}
/**
* Returns an array of relevant entity types.
*
@ -228,7 +209,7 @@ class DefaultsSectionStorage extends SectionStorageBase implements ContainerFact
*/
protected function getEntityTypes() {
return array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) {
return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasViewBuilderClass() && $entity_type->get('field_ui_base_route');
return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasHandlerClass('form', 'layout_builder') && $entity_type->hasViewBuilderClass() && $entity_type->get('field_ui_base_route');
});
}

View File

@ -220,7 +220,7 @@ class OverridesSectionStorage extends SectionStorageBase implements ContainerFac
$options['parameters'][$entity_type_id]['type'] = 'entity:' . $entity_type_id;
$template = $entity_type->getLinkTemplate('canonical') . '/layout';
$this->buildLayoutRoutes($collection, $this->getPluginDefinition(), $template, $defaults, $requirements, $options, $entity_type_id);
$this->buildLayoutRoutes($collection, $this->getPluginDefinition(), $template, $defaults, $requirements, $options, $entity_type_id, $entity_type_id);
}
}
@ -237,28 +237,6 @@ class OverridesSectionStorage extends SectionStorageBase implements ContainerFac
'base_route' => "entity.$entity_type_id.canonical",
'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
];
$local_tasks["layout_builder.overrides.$entity_type_id.save"] = $base_plugin_definition + [
'route_name' => "layout_builder.overrides.$entity_type_id.save",
'title' => $this->t('Save Layout'),
'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
];
$local_tasks["layout_builder.overrides.$entity_type_id.discard_changes"] = $base_plugin_definition + [
'route_name' => "layout_builder.overrides.$entity_type_id.discard_changes",
'title' => $this->t('Discard changes'),
'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
'weight' => 5,
'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
];
// @todo This link should be conditionally displayed, see
// https://www.drupal.org/node/2917777.
$local_tasks["layout_builder.overrides.$entity_type_id.revert"] = $base_plugin_definition + [
'route_name' => "layout_builder.overrides.$entity_type_id.revert",
'title' => $this->t('Revert to defaults'),
'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view",
'weight' => 10,
'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id],
];
}
return $local_tasks;
}
@ -285,7 +263,7 @@ class OverridesSectionStorage extends SectionStorageBase implements ContainerFac
*/
protected function getEntityTypes() {
return array_filter($this->entityTypeManager->getDefinitions(), function (EntityTypeInterface $entity_type) {
return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('canonical');
return $entity_type->entityClassImplements(FieldableEntityInterface::class) && $entity_type->hasHandlerClass('form', 'layout_builder') && $entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('canonical');
});
}

View File

@ -36,8 +36,10 @@ trait LayoutBuilderRoutesTrait {
* (optional) An array of options.
* @param string $route_name_prefix
* (optional) The prefix to use for the route name.
* @param string $entity_type_id
* (optional) The entity type ID, if available.
*/
protected function buildLayoutRoutes(RouteCollection $collection, SectionStorageDefinition $definition, $path, array $defaults = [], array $requirements = [], array $options = [], $route_name_prefix = '') {
protected function buildLayoutRoutes(RouteCollection $collection, SectionStorageDefinition $definition, $path, array $defaults = [], array $requirements = [], array $options = [], $route_name_prefix = '', $entity_type_id = '') {
$type = $definition->id();
$defaults['section_storage_type'] = $type;
// Provide an empty value to allow the section storage to be upcast.
@ -60,22 +62,20 @@ trait LayoutBuilderRoutesTrait {
}
$main_defaults = $defaults;
$main_defaults['_controller'] = '\Drupal\layout_builder\Controller\LayoutBuilderController::layout';
$main_options = $options;
if ($entity_type_id) {
$main_defaults['_entity_form'] = "$entity_type_id.layout_builder";
}
else {
$main_defaults['_controller'] = '\Drupal\layout_builder\Controller\LayoutBuilderController::layout';
}
$main_defaults['_title_callback'] = '\Drupal\layout_builder\Controller\LayoutBuilderController::title';
$route = (new Route($path))
->setDefaults($main_defaults)
->setRequirements($requirements)
->setOptions($options);
->setOptions($main_options);
$collection->add("$route_name_prefix.view", $route);
$save_defaults = $defaults;
$save_defaults['_controller'] = '\Drupal\layout_builder\Controller\LayoutBuilderController::saveLayout';
$route = (new Route("$path/save"))
->setDefaults($save_defaults)
->setRequirements($requirements)
->setOptions($options);
$collection->add("$route_name_prefix.save", $route);
$discard_changes_defaults = $defaults;
$discard_changes_defaults['_form'] = '\Drupal\layout_builder\Form\DiscardLayoutChangesForm';
$route = (new Route("$path/discard-changes"))

View File

@ -5,6 +5,7 @@
* Provides hook implementations for Layout Builder tests.
*/
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
@ -80,3 +81,15 @@ function layout_builder_test_form_layout_builder_configure_block_alter(&$form, F
'#title' => 'Layout Builder Component: ' . $form_object->getCurrentComponent()->getPluginId(),
];
}
/**
* Implements hook_layout_builder_overrides_entity_form_display_alter().
*/
function layout_builder_test_layout_builder_overrides_entity_form_display_alter(EntityFormDisplayInterface $display) {
$display->setComponent('status', [
'type' => 'boolean_checkbox',
'settings' => [
'display_label' => TRUE,
],
]);
}

View File

@ -155,9 +155,9 @@ class SimpleConfigSectionStorage extends ContextAwarePluginBase implements Secti
'title' => $this->t('Layout'),
'base_route' => "layout_builder.$type.view",
];
$local_tasks["layout_builder.$type.save"] = $base_plugin_definition + [
'route_name' => "layout_builder.$type.save",
'title' => $this->t('Save Layout'),
$local_tasks["layout_builder.$type.view__child"] = $base_plugin_definition + [
'route_name' => "layout_builder.$type.view",
'title' => $this->t('Layout'),
'parent_id' => "layout_builder_ui:layout_builder.$type.view",
];
$local_tasks["layout_builder.$type.discard_changes"] = $base_plugin_definition + [

View File

@ -71,7 +71,7 @@ class LayoutBuilderSectionStorageTest extends BrowserTestBase {
$page->fillField('settings[label]', 'Defaults block title');
$page->checkField('settings[label_display]');
$page->pressButton('Add Block');
$page->clickLink('Save Layout');
$page->pressButton('Save layout');
$this->drupalGet('node/1');
$assert_session->pageTextContains('Defaults block title');

View File

@ -87,7 +87,7 @@ class LayoutBuilderTest extends BrowserTestBase {
$page->fillField('settings[label]', 'This is an override');
$page->checkField('settings[label_display]');
$page->pressButton('Add Block');
$page->clickLink('Save Layout');
$page->pressButton('Save layout');
$assert_session->pageTextContains('This is an override');
// Get the UUID of the component.
@ -99,7 +99,7 @@ class LayoutBuilderTest extends BrowserTestBase {
$page->uncheckField('settings[label_display]');
$page->pressButton('Update');
$assert_session->pageTextNotContains('This is an override');
$page->clickLink('Save Layout');
$page->pressButton('Save layout');
$assert_session->pageTextNotContains('This is an override');
}
@ -137,8 +137,7 @@ class LayoutBuilderTest extends BrowserTestBase {
// The extra field is only present once.
$assert_session->pageTextContainsOnce('Placeholder for the "Extra label" field');
// Save the defaults.
$assert_session->linkExists('Save Layout');
$this->clickLink('Save Layout');
$page->pressButton('Save layout');
$assert_session->addressEquals("$field_ui_prefix/display/default");
// Load the default layouts again after saving to confirm fields are only
@ -165,8 +164,7 @@ class LayoutBuilderTest extends BrowserTestBase {
$assert_session->addressEquals("$field_ui_prefix/display-layout/default");
// Save the defaults.
$assert_session->linkExists('Save Layout');
$this->clickLink('Save Layout');
$page->pressButton('Save layout');
$assert_session->pageTextContains('The layout has been saved.');
$assert_session->addressEquals("$field_ui_prefix/display/default");
@ -197,8 +195,7 @@ class LayoutBuilderTest extends BrowserTestBase {
$this->clickLink('Two column');
$assert_session->buttonExists('Add section');
$page->pressButton('Add section');
$assert_session->linkExists('Save Layout');
$this->clickLink('Save Layout');
$page->pressButton('Save');
$assert_session->pageTextNotContains('The first node body');
$assert_session->pageTextNotContains('Powered by Drupal');
$assert_session->pageTextNotContains('Extra, Extra read all about it.');
@ -218,7 +215,7 @@ class LayoutBuilderTest extends BrowserTestBase {
$page->pressButton('Add Block');
// The title field is present.
$assert_session->elementExists('css', '.field--name-title');
$this->clickLink('Save Layout');
$page->pressButton('Save layout');
// View the other node, which is still using the defaults.
$this->drupalGet('node/2');
@ -380,8 +377,7 @@ class LayoutBuilderTest extends BrowserTestBase {
$this->clickLink('Layout plugin (with dependencies)');
$assert_session->elementExists('css', '.layout--layout-test-dependencies-plugin');
$assert_session->elementExists('css', '.field--name-body');
$assert_session->linkExists('Save Layout');
$this->clickLink('Save Layout');
$page->pressButton('Save layout');
$this->drupalPostForm('admin/structure/menu/manage/myothermenu/delete', [], 'Delete');
$this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display-layout/default');
$assert_session->elementNotExists('css', '.layout--layout-test-dependencies-plugin');
@ -405,8 +401,7 @@ class LayoutBuilderTest extends BrowserTestBase {
$assert_session->pageTextContains('Powered by Drupal');
$assert_session->pageTextContains('My Menu');
$assert_session->elementExists('css', '.block.menu--mymenu');
$assert_session->linkExists('Save Layout');
$this->clickLink('Save Layout');
$page->pressButton('Save layout');
// Delete the menu.
$this->drupalPostForm('admin/structure/menu/manage/mymenu/delete', [], 'Delete');
@ -446,7 +441,7 @@ class LayoutBuilderTest extends BrowserTestBase {
$page->checkField('settings[label_display]');
$page->pressButton('Add Block');
$assert_session->pageTextContains('This is the default view mode');
$this->clickLink('Save Layout');
$page->pressButton('Save layout');
// The default view mode is used for both the node display and layout UI.
$this->drupalGet('node/1');
@ -466,7 +461,7 @@ class LayoutBuilderTest extends BrowserTestBase {
$page->checkField('settings[label_display]');
$page->pressButton('Add Block');
$assert_session->pageTextContains('This is the full view mode');
$this->clickLink('Save Layout');
$page->pressButton('Save layout');
// The full view mode is now used for both the node display and layout UI.
$this->drupalGet('node/1');
@ -579,7 +574,7 @@ class LayoutBuilderTest extends BrowserTestBase {
$assert_session->pageTextContains('Test Block View');
$assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1');
$this->clickLink('Save Layout');
$page->pressButton('Save');
$assert_session->pageTextContains('Test Block View');
$assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1');
@ -679,6 +674,39 @@ class LayoutBuilderTest extends BrowserTestBase {
$assert_session->pageTextContains($block_content);
}
/**
* Tests a custom alter of the overrides form.
*/
public function testOverridesFormAlter() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$this->drupalLogin($this->drupalCreateUser([
'configure any layout',
'administer node display',
'administer nodes',
]));
$field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
// Enable overrides.
$this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
$this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
$this->drupalGet('node/1');
// The status checkbox should be checked by default.
$page->clickLink('Layout');
$assert_session->checkboxChecked('status[value]');
$page->pressButton('Save layout');
$assert_session->pageTextContains('The layout override has been saved.');
// Unchecking the status checkbox will unpublish the entity.
$page->clickLink('Layout');
$page->uncheckField('status[value]');
$page->pressButton('Save layout');
$assert_session->statusCodeEquals(403);
$assert_session->pageTextContains('The layout override has been saved.');
}
/**
* Tests the Block UI when Layout Builder is installed.
*/

View File

@ -63,8 +63,7 @@ class LayoutDisplayTest extends BrowserTestBase {
$assert_session->linkExists('Powered by Drupal');
$this->clickLink('Powered by Drupal');
$page->pressButton('Add Block');
$assert_session->linkExists('Save Layout');
$this->clickLink('Save Layout');
$page->pressButton('Save');
$assert_session->pageTextContains('Powered by Drupal');
// Add a new view mode.

View File

@ -84,7 +84,7 @@ abstract class LayoutRestTestBase extends ResourceTestBase {
$page->fillField('settings[label]', 'This is an override');
$page->checkField('settings[label_display]');
$page->pressButton('Add Block');
$page->clickLink('Save Layout');
$page->pressButton('Save layout');
$assert_session->pageTextContains('This is an override');
$this->nodeStorage = $this->container->get('entity_type.manager')->getStorage('node');

View File

@ -79,13 +79,11 @@ abstract class InlineBlockTestBase extends WebDriverTestBase {
*/
protected function assertSaveLayout() {
$assert_session = $this->assertSession();
$assert_session->linkExists('Save Layout');
// Go to the Save Layout page. Currently there are random test failures if
// 'clickLink()' is used.
// @todo Convert tests that extend this class to NightWatch tests in
// https://www.drupal.org/node/2984161
$link = $this->getSession()->getPage()->findLink('Save Layout');
$this->drupalGet($link->getAttribute('href'));
$page = $this->getSession()->getPage();
// Reload the page to prevent random failures.
$this->drupalGet($this->getUrl());
$page->pressButton('Save layout');
$this->assertNotEmpty($assert_session->waitForElement('css', '.messages--status'));
if (stristr($this->getUrl(), 'admin/structure') === FALSE) {

View File

@ -116,8 +116,7 @@ class LayoutBuilderOptInTest extends WebDriverTestBase {
$page->selectFieldOption('settings[formatter][type]', 'text_trimmed');
$assert_session->assertWaitOnAjaxRequest();
$page->pressButton('Update');
$assert_session->linkExists('Save Layout');
$this->clickLink('Save Layout');
$page->pressButton('Save layout');
$this->drupalGet($layout_builder_ui);
$assert_session->fieldValueEquals('settings[formatter][type]', 'text_trimmed');

View File

@ -140,8 +140,7 @@ class LayoutBuilderTest extends WebDriverTestBase {
$assert_session->pageTextContains('Powered by Drupal');
// Save the layout, and the new block is visible.
$assert_session->linkExists('Save Layout');
$this->clickLink('Save Layout');
$page->pressButton('Save layout');
$assert_session->addressEquals($node_url);
$assert_session->pageTextContains('Powered by Drupal');
$assert_session->pageTextContains('This is the label');
@ -177,8 +176,7 @@ class LayoutBuilderTest extends WebDriverTestBase {
$assert_session->elementTextContains('css', '.layout__region--second', 'Powered by Drupal');
// Ensure the dragged block is still in the correct position after save.
$assert_session->linkExists('Save Layout');
$this->clickLink('Save Layout');
$page->pressButton('Save layout');
$assert_session->elementExists('css', '.layout__region--second .block-system-powered-by-block');
$assert_session->elementTextContains('css', '.layout__region--second', 'Powered by Drupal');
@ -212,8 +210,7 @@ class LayoutBuilderTest extends WebDriverTestBase {
$assert_session->addressEquals($layout_url);
$this->assertPageNotReloaded();
$assert_session->linkExists('Save Layout');
$this->clickLink('Save Layout');
$page->pressButton('Save layout');
$assert_session->elementExists('css', '.layout');
// Test deriver-based blocks.
@ -242,8 +239,7 @@ class LayoutBuilderTest extends WebDriverTestBase {
$assert_session->linkNotExists('Add Block');
$this->assertPageNotReloaded();
$assert_session->linkExists('Save Layout');
$this->clickLink('Save Layout');
$page->pressButton('Save layout');
// Removing all sections results in the default layout display being used.
$assert_session->addressEquals($node_url);

View File

@ -89,7 +89,7 @@ class LayoutBuilderUiTest extends WebDriverTestBase {
// Make and then save changes.
$this->assertModifiedLayout(static::FIELD_UI_PREFIX . '/display-layout/default');
$page->clickLink('Save Layout');
$page->pressButton('Save layout');
$assert_session->pageTextNotContains('You have unsaved changes.');
}

View File

@ -330,6 +330,7 @@ class DefaultsSectionStorageTest extends UnitTestCase {
/**
* @covers ::buildRoutes
* @covers ::getEntityTypes
* @covers \Drupal\layout_builder\Routing\LayoutBuilderRoutesTrait::buildLayoutRoutes
*/
public function testBuildRoutes() {
$entity_types = [];
@ -338,25 +339,34 @@ class DefaultsSectionStorageTest extends UnitTestCase {
$not_fieldable->entityClassImplements(FieldableEntityInterface::class)->willReturn(FALSE);
$entity_types['not_fieldable'] = $not_fieldable->reveal();
$no_layout_builder_form = $this->prophesize(EntityTypeInterface::class);
$no_layout_builder_form->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
$no_layout_builder_form->hasHandlerClass('form', 'layout_builder')->willReturn(FALSE);
$entity_types['no_layout_builder_form'] = $no_layout_builder_form->reveal();
$no_view_builder = $this->prophesize(EntityTypeInterface::class);
$no_view_builder->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
$no_view_builder->hasHandlerClass('form', 'layout_builder')->willReturn(TRUE);
$no_view_builder->hasViewBuilderClass()->willReturn(FALSE);
$entity_types['no_view_builder'] = $no_view_builder->reveal();
$no_field_ui_route = $this->prophesize(EntityTypeInterface::class);
$no_field_ui_route->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
$no_field_ui_route->hasHandlerClass('form', 'layout_builder')->willReturn(TRUE);
$no_field_ui_route->hasViewBuilderClass()->willReturn(TRUE);
$no_field_ui_route->get('field_ui_base_route')->willReturn(NULL);
$entity_types['no_field_ui_route'] = $no_field_ui_route->reveal();
$unknown_field_ui_route = $this->prophesize(EntityTypeInterface::class);
$unknown_field_ui_route->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
$unknown_field_ui_route->hasHandlerClass('form', 'layout_builder')->willReturn(TRUE);
$unknown_field_ui_route->hasViewBuilderClass()->willReturn(TRUE);
$unknown_field_ui_route->get('field_ui_base_route')->willReturn('unknown');
$entity_types['unknown_field_ui_route'] = $unknown_field_ui_route->reveal();
$with_bundle_key = $this->prophesize(EntityTypeInterface::class);
$with_bundle_key->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
$with_bundle_key->hasHandlerClass('form', 'layout_builder')->willReturn(TRUE);
$with_bundle_key->hasViewBuilderClass()->willReturn(TRUE);
$with_bundle_key->get('field_ui_base_route')->willReturn('known');
$with_bundle_key->hasKey('bundle')->willReturn(TRUE);
@ -365,6 +375,7 @@ class DefaultsSectionStorageTest extends UnitTestCase {
$with_bundle_parameter = $this->prophesize(EntityTypeInterface::class);
$with_bundle_parameter->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
$with_bundle_parameter->hasHandlerClass('form', 'layout_builder')->willReturn(TRUE);
$with_bundle_parameter->hasViewBuilderClass()->willReturn(TRUE);
$with_bundle_parameter->get('field_ui_base_route')->willReturn('with_bundle');
$entity_types['with_bundle_parameter'] = $with_bundle_parameter->reveal();
@ -378,9 +389,10 @@ class DefaultsSectionStorageTest extends UnitTestCase {
[
'entity_type_id' => 'with_bundle_key',
'bundle_key' => 'my_bundle_type',
'bundle' => '',
'section_storage_type' => 'defaults',
'section_storage' => '',
'_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::layout',
'_entity_form' => 'entity_view_display.layout_builder',
'_title_callback' => '\Drupal\layout_builder\Controller\LayoutBuilderController::title',
],
[
@ -394,28 +406,7 @@ class DefaultsSectionStorageTest extends UnitTestCase {
],
'_layout_builder' => TRUE,
'_admin_route' => FALSE,
]
),
'layout_builder.defaults.with_bundle_key.save' => new Route(
'/admin/entity/whatever/display-layout/{view_mode_name}/save',
[
'entity_type_id' => 'with_bundle_key',
'bundle_key' => 'my_bundle_type',
'section_storage_type' => 'defaults',
'section_storage' => '',
'_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::saveLayout',
],
[
'_field_ui_view_mode_access' => 'administer with_bundle_key display',
'_has_layout_section' => 'true',
'_layout_builder_access' => 'view',
],
[
'parameters' => [
'section_storage' => ['layout_builder_tempstore' => TRUE],
],
'_layout_builder' => TRUE,
'_admin_route' => FALSE,
'_field_ui' => TRUE,
]
),
'layout_builder.defaults.with_bundle_key.discard_changes' => new Route(
@ -466,7 +457,7 @@ class DefaultsSectionStorageTest extends UnitTestCase {
'entity_type_id' => 'with_bundle_parameter',
'section_storage_type' => 'defaults',
'section_storage' => '',
'_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::layout',
'_entity_form' => 'entity_view_display.layout_builder',
'_title_callback' => '\Drupal\layout_builder\Controller\LayoutBuilderController::title',
],
[
@ -482,27 +473,6 @@ class DefaultsSectionStorageTest extends UnitTestCase {
'_admin_route' => FALSE,
]
),
'layout_builder.defaults.with_bundle_parameter.save' => new Route(
'/admin/entity/{bundle}/display-layout/{view_mode_name}/save',
[
'entity_type_id' => 'with_bundle_parameter',
'section_storage_type' => 'defaults',
'section_storage' => '',
'_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::saveLayout',
],
[
'_field_ui_view_mode_access' => 'administer with_bundle_parameter display',
'_has_layout_section' => 'true',
'_layout_builder_access' => 'view',
],
[
'parameters' => [
'section_storage' => ['layout_builder_tempstore' => TRUE],
],
'_layout_builder' => TRUE,
'_admin_route' => FALSE,
]
),
'layout_builder.defaults.with_bundle_parameter.discard_changes' => new Route(
'/admin/entity/{bundle}/display-layout/{view_mode_name}/discard-changes',
[

View File

@ -4,6 +4,7 @@ namespace Drupal\Tests\layout_builder\Unit;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityType;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
@ -132,9 +133,13 @@ class OverridesSectionStorageTest extends UnitTestCase {
$entity_storage->load('entity_with_layout')->willReturn($entity_with_layout->reveal());
$this->entityTypeManager->getStorage($expected_entity_type_id)->willReturn($entity_storage->reveal());
$entity_type = new EntityType(['id' => $expected_entity_type_id]);
$this->entityTypeManager->getDefinition($expected_entity_type_id)->willReturn($entity_type);
}
else {
$this->entityTypeManager->getStorage(Argument::any())->shouldNotBeCalled();
$this->entityTypeManager->getDefinition(Argument::any())->shouldNotBeCalled();
}
if (!$success) {
@ -196,6 +201,10 @@ class OverridesSectionStorageTest extends UnitTestCase {
$entity_with_layout->hasField(OverridesSectionStorage::FIELD_NAME)->willReturn(TRUE);
$entity_storage->load('entity_with_layout')->willReturn($entity_with_layout->reveal());
$this->entityTypeManager->getStorage($expected_entity_type_id)->willReturn($entity_storage->reveal());
$entity_type = new EntityType([
'id' => $expected_entity_type_id,
]);
$this->entityTypeManager->getDefinition($expected_entity_type_id)->willReturn($entity_type);
}
else {
$this->entityTypeManager->getStorage(Argument::any())->shouldNotBeCalled();
@ -256,6 +265,7 @@ class OverridesSectionStorageTest extends UnitTestCase {
* @covers ::buildRoutes
* @covers ::hasIntegerId
* @covers ::getEntityTypes
* @covers \Drupal\layout_builder\Routing\LayoutBuilderRoutesTrait::buildLayoutRoutes
*/
public function testBuildRoutes() {
$entity_types = [];
@ -264,15 +274,23 @@ class OverridesSectionStorageTest extends UnitTestCase {
$not_fieldable->entityClassImplements(FieldableEntityInterface::class)->willReturn(FALSE);
$entity_types['not_fieldable'] = $not_fieldable->reveal();
$no_layout_builder_form = $this->prophesize(EntityTypeInterface::class);
$no_layout_builder_form->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
$no_layout_builder_form->hasHandlerClass('form', 'layout_builder')->willReturn(FALSE);
$entity_types['no_layout_builder_form'] = $no_layout_builder_form->reveal();
$this->entityFieldManager->getFieldStorageDefinitions('no_layout_builder_form')->shouldNotBeCalled();
$no_view_builder = $this->prophesize(EntityTypeInterface::class);
$no_view_builder->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
$no_view_builder->hasViewBuilderClass()->willReturn(FALSE);
$no_view_builder->hasHandlerClass('form', 'layout_builder')->willReturn(TRUE);
$entity_types['no_view_builder'] = $no_view_builder->reveal();
$no_canonical_link = $this->prophesize(EntityTypeInterface::class);
$no_canonical_link->entityClassImplements(FieldableEntityInterface::class)->willReturn(TRUE);
$no_canonical_link->hasViewBuilderClass()->willReturn(TRUE);
$no_canonical_link->hasLinkTemplate('canonical')->willReturn(FALSE);
$no_canonical_link->hasHandlerClass('form', 'layout_builder')->willReturn(TRUE);
$entity_types['no_canonical_link'] = $no_canonical_link->reveal();
$this->entityFieldManager->getFieldStorageDefinitions('no_canonical_link')->shouldNotBeCalled();
@ -281,6 +299,7 @@ class OverridesSectionStorageTest extends UnitTestCase {
$with_string_id->hasViewBuilderClass()->willReturn(TRUE);
$with_string_id->hasLinkTemplate('canonical')->willReturn(TRUE);
$with_string_id->getLinkTemplate('canonical')->willReturn('/entity/{entity}');
$with_string_id->hasHandlerClass('form', 'layout_builder')->willReturn(TRUE);
$with_string_id->id()->willReturn('with_string_id');
$with_string_id->getKey('id')->willReturn('id');
$entity_types['with_string_id'] = $with_string_id->reveal();
@ -293,6 +312,7 @@ class OverridesSectionStorageTest extends UnitTestCase {
$with_integer_id->hasViewBuilderClass()->willReturn(TRUE);
$with_integer_id->hasLinkTemplate('canonical')->willReturn(TRUE);
$with_integer_id->getLinkTemplate('canonical')->willReturn('/entity/{entity}');
$with_integer_id->hasHandlerClass('form', 'layout_builder')->willReturn(TRUE);
$with_integer_id->id()->willReturn('with_integer_id');
$with_integer_id->getKey('id')->willReturn('id');
$entity_types['with_integer_id'] = $with_integer_id->reveal();
@ -309,28 +329,8 @@ class OverridesSectionStorageTest extends UnitTestCase {
'entity_type_id' => 'with_string_id',
'section_storage_type' => 'overrides',
'section_storage' => '',
'_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::layout',
'_title_callback' => '\Drupal\layout_builder\Controller\LayoutBuilderController::title',
],
[
'_has_layout_section' => 'true',
'_layout_builder_access' => 'view',
],
[
'parameters' => [
'section_storage' => ['layout_builder_tempstore' => TRUE],
'with_string_id' => ['type' => 'entity:with_string_id'],
],
'_layout_builder' => TRUE,
]
),
'layout_builder.overrides.with_string_id.save' => new Route(
'/entity/{entity}/layout/save',
[
'entity_type_id' => 'with_string_id',
'section_storage_type' => 'overrides',
'section_storage' => '',
'_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::saveLayout',
'_entity_form' => 'with_string_id.layout_builder',
],
[
'_has_layout_section' => 'true',
@ -390,29 +390,8 @@ class OverridesSectionStorageTest extends UnitTestCase {
'entity_type_id' => 'with_integer_id',
'section_storage_type' => 'overrides',
'section_storage' => '',
'_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::layout',
'_title_callback' => '\Drupal\layout_builder\Controller\LayoutBuilderController::title',
],
[
'_has_layout_section' => 'true',
'_layout_builder_access' => 'view',
'with_integer_id' => '\d+',
],
[
'parameters' => [
'section_storage' => ['layout_builder_tempstore' => TRUE],
'with_integer_id' => ['type' => 'entity:with_integer_id'],
],
'_layout_builder' => TRUE,
]
),
'layout_builder.overrides.with_integer_id.save' => new Route(
'/entity/{entity}/layout/save',
[
'entity_type_id' => 'with_integer_id',
'section_storage_type' => 'overrides',
'section_storage' => '',
'_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::saveLayout',
'_entity_form' => 'with_integer_id.layout_builder',
],
[
'_has_layout_section' => 'true',