diff --git a/core/modules/content_moderation/content_moderation.module b/core/modules/content_moderation/content_moderation.module index 8fc2c4ceb6..435c7feff0 100644 --- a/core/modules/content_moderation/content_moderation.module +++ b/core/modules/content_moderation/content_moderation.module @@ -11,6 +11,7 @@ use Drupal\content_moderation\Plugin\Action\ModerationOptOutPublish; use Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublish; use Drupal\Core\Access\AccessResult; +use Drupal\Core\Entity\Display\EntityFormDisplayInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityPublishedInterface; @@ -191,6 +192,17 @@ function content_moderation_entity_view(array &$build, EntityInterface $entity, ->entityView($build, $entity, $display, $view_mode); } +/** + * Implements hook_layout_builder_entity_form_display_alter(). + */ +function content_moderation_layout_builder_entity_form_display_alter(EntityFormDisplayInterface $display) { + $display->setComponent('moderation_state', [ + 'type' => 'moderation_state_default', + 'weight' => 2, + 'settings' => [], + ]); +} + /** * Implements hook_entity_access(). * diff --git a/core/modules/layout_builder/layout_builder.module b/core/modules/layout_builder/layout_builder.module index 71265e2d46..e9e16e83ad 100644 --- a/core/modules/layout_builder/layout_builder.module +++ b/core/modules/layout_builder/layout_builder.module @@ -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,8 +14,10 @@ use Drupal\field\FieldConfigInterface; use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay; use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplayStorage; +use Drupal\layout_builder\Form\LayoutBuilderEntityForm; use Drupal\layout_builder\Form\LayoutBuilderEntityViewDisplayForm; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; +use Drupal\layout_builder\Form\ManageLayoutForm; use Drupal\layout_builder\Plugin\Block\ExtraFieldBlock; use Drupal\layout_builder\InlineBlockEntityOperations; use Drupal\Core\Session\AccountInterface; @@ -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('edit', LayoutBuilderEntityViewDisplayForm::class); + ->setFormClass('edit', LayoutBuilderEntityViewDisplayForm::class) + ->setFormClass('layout_builder__form', ManageLayoutForm::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__form', LayoutBuilderEntityForm::class); + } + } } /** diff --git a/core/modules/layout_builder/layout_builder.services.yml b/core/modules/layout_builder/layout_builder.services.yml index f0c517d995..65fb66a895 100644 --- a/core/modules/layout_builder/layout_builder.services.yml +++ b/core/modules/layout_builder/layout_builder.services.yml @@ -19,10 +19,6 @@ services: arguments: ['@plugin.manager.layout_builder.section_storage'] tags: - { name: event_subscriber } - layout_builder.route_enhancer: - class: Drupal\layout_builder\Routing\LayoutBuilderRouteEnhancer - tags: - - { name: route_enhancer } layout_builder.param_converter: class: Drupal\layout_builder\Routing\LayoutTempstoreParamConverter arguments: ['@layout_builder.tempstore_repository', '@plugin.manager.layout_builder.section_storage'] diff --git a/core/modules/layout_builder/src/Controller/LayoutBuilderController.php b/core/modules/layout_builder/src/Controller/LayoutBuilderController.php index 48ab26bd34..3c1920a375 100644 --- a/core/modules/layout_builder/src/Controller/LayoutBuilderController.php +++ b/core/modules/layout_builder/src/Controller/LayoutBuilderController.php @@ -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. @@ -79,41 +32,15 @@ public function title(SectionStorageInterface $section_storage) { * * @param \Drupal\layout_builder\SectionStorageInterface $section_storage * The section storage. - * @param bool $is_rebuilding - * (optional) Indicates if the layout is rebuilding, defaults to FALSE. * * @return array * A render array. */ - public function layout(SectionStorageInterface $section_storage, $is_rebuilding = FALSE) { + public function layout(SectionStorageInterface $section_storage) { return [ '#type' => 'layout_builder', '#section_storage' => $section_storage, - '#is_rebuilding' => $is_rebuilding, ]; } - /** - * 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()); - } - } diff --git a/core/modules/layout_builder/src/Controller/LayoutRebuildTrait.php b/core/modules/layout_builder/src/Controller/LayoutRebuildTrait.php index 4ec8a43a27..2cda6ee31c 100644 --- a/core/modules/layout_builder/src/Controller/LayoutRebuildTrait.php +++ b/core/modules/layout_builder/src/Controller/LayoutRebuildTrait.php @@ -45,7 +45,6 @@ protected function rebuildLayout(SectionStorageInterface $section_storage) { $layout = [ '#type' => 'layout_builder', '#section_storage' => $section_storage, - '#is_rebuilding' => TRUE, ]; $response->addCommand(new ReplaceCommand('#layout-builder', $layout)); return $response; diff --git a/core/modules/layout_builder/src/Element/LayoutBuilder.php b/core/modules/layout_builder/src/Element/LayoutBuilder.php index 0933eecdd8..3728ce97cc 100644 --- a/core/modules/layout_builder/src/Element/LayoutBuilder.php +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php @@ -78,7 +78,6 @@ public static function create(ContainerInterface $container, array $configuratio public function getInfo() { return [ '#section_storage' => NULL, - '#is_rebuilding' => FALSE, '#pre_render' => [ [$this, 'preRender'], ], @@ -90,7 +89,7 @@ public function getInfo() { */ public function preRender($element) { if ($element['#section_storage'] instanceof SectionStorageInterface) { - $element['layout_builder'] = $this->layout($element['#section_storage'], $element['#is_rebuilding']); + $element['layout_builder'] = $this->layout($element['#section_storage']); } return $element; } @@ -100,14 +99,12 @@ public function preRender($element) { * * @param \Drupal\layout_builder\SectionStorageInterface $section_storage * The section storage. - * @param bool $is_rebuilding - * Indicates if the layout is rebuilding. * * @return array * A render array. */ - protected function layout(SectionStorageInterface $section_storage, $is_rebuilding) { - $this->prepareLayout($section_storage, $is_rebuilding); + protected function layout(SectionStorageInterface $section_storage) { + $this->prepareLayout($section_storage); $output = []; if ($this->isAjax()) { @@ -135,17 +132,14 @@ protected function layout(SectionStorageInterface $section_storage, $is_rebuildi * * @param \Drupal\layout_builder\SectionStorageInterface $section_storage * The section storage. - * @param bool $is_rebuilding - * Indicates if the layout is rebuilding. */ - protected function prepareLayout(SectionStorageInterface $section_storage, $is_rebuilding) { + protected function prepareLayout(SectionStorageInterface $section_storage) { // If the layout has pending changes, add a warning. if ($this->layoutTempstoreRepository->has($section_storage)) { $this->messenger->addWarning($this->t('You have unsaved changes.')); } - // Only add sections if the layout is new and empty. - if (!$is_rebuilding && $section_storage->count() === 0) { + elseif ($section_storage->count() === 0) { $sections = []; // If this is an empty override, copy the sections from the corresponding // default. diff --git a/core/modules/layout_builder/src/Form/LayoutBuilderEntityForm.php b/core/modules/layout_builder/src/Form/LayoutBuilderEntityForm.php new file mode 100644 index 0000000000..24f1fd788d --- /dev/null +++ b/core/modules/layout_builder/src/Form/LayoutBuilderEntityForm.php @@ -0,0 +1,75 @@ + $this->entity->getEntityTypeId(), + 'bundle' => $this->entity->bundle(), + ]); + $display->setComponent(OverridesSectionStorage::FIELD_NAME, [ + 'type' => 'layout_builder_widget', + 'weight' => 1, + 'settings' => [], + ]); + if ($log_message_field = $this->entity->getEntityType()->getRevisionMetadataKey('revision_log_message')) { + $display->setComponent($log_message_field, [ + 'weight' => 2, + 'type' => 'string_textarea', + 'settings' => [ + 'rows' => 2, + ], + ]); + } + + // Allow modules to choose if they are relevant to the layout builder entity + // form. + $this->moduleHandler->alter('layout_builder_entity_form_display', $display); + return $display; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = parent::buildForm($form, $form_state); + // Add a bit of spacing. + $form['spacing'] = ['#markup' => '
', '#weight' => -1000]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + parent::submitForm($form, $form_state); + $this->messenger()->addStatus($this->t('The layout override has been saved.')); + $form_state->setRedirectUrl($this->entity->toUrl($this->entity->isDefaultRevision() ? 'canonical' : 'latest-version')); + } + + /** + * {@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; + return $actions; + } + +} diff --git a/core/modules/layout_builder/src/Form/LayoutRebuildConfirmFormBase.php b/core/modules/layout_builder/src/Form/LayoutRebuildConfirmFormBase.php index eed867ed8c..d6c05f87bf 100644 --- a/core/modules/layout_builder/src/Form/LayoutRebuildConfirmFormBase.php +++ b/core/modules/layout_builder/src/Form/LayoutRebuildConfirmFormBase.php @@ -64,7 +64,7 @@ public static function create(ContainerInterface $container) { * {@inheritdoc} */ public function getCancelUrl() { - return $this->sectionStorage->getLayoutBuilderUrl()->setOption('query', ['layout_is_rebuilding' => TRUE]); + return $this->sectionStorage->getLayoutBuilderUrl(); } /** diff --git a/core/modules/layout_builder/src/Form/ManageLayoutForm.php b/core/modules/layout_builder/src/Form/ManageLayoutForm.php new file mode 100644 index 0000000000..0a16cf199e --- /dev/null +++ b/core/modules/layout_builder/src/Form/ManageLayoutForm.php @@ -0,0 +1,92 @@ +layoutTempstoreRepository = $layout_tempstore_repository; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('layout_builder.tempstore_repository') + ); + } + + /** + * {@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 getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) { + $route_parameters = $route_match->getParameters()->all(); + + return EntityViewDisplay::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'); + 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; + } + +} diff --git a/core/modules/layout_builder/src/Plugin/Field/FieldWidget/LayoutBuilderWidget.php b/core/modules/layout_builder/src/Plugin/Field/FieldWidget/LayoutBuilderWidget.php new file mode 100644 index 0000000000..650d9e2598 --- /dev/null +++ b/core/modules/layout_builder/src/Plugin/Field/FieldWidget/LayoutBuilderWidget.php @@ -0,0 +1,116 @@ +sectionStorageManager = $section_storage_manager; + $this->layoutTempstoreRepository = $layout_tempstore_repository; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['third_party_settings'], + $container->get('plugin.manager.layout_builder.section_storage'), + $container->get('layout_builder.tempstore_repository') + ); + } + + /** + * {@inheritdoc} + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + $element += [ + '#type' => 'layout_builder', + '#section_storage' => $this->getSectionStorage($items), + ]; + 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; + } + + $section_storage = $this->getSectionStorage($items); + $items->setValue($section_storage->getSections()); + $this->layoutTempstoreRepository->delete($section_storage); + } + + /** + * Gets the section storage for a field item list. + * + * @param \Drupal\Core\Field\FieldItemListInterface $items + * The field item list. + * + * @return \Drupal\layout_builder\SectionStorageInterface + * The section storage loaded from the tempstore. + */ + private function getSectionStorage(FieldItemListInterface $items) { + $section_storage = $this->sectionStorageManager->load('overrides', [ + 'entity' => EntityContext::fromEntity($items->getEntity()), + // @todo Expand to work for all view modes in + 'view_mode' => new Context(new ContextDefinition('string'), 'full'), + ]); + return $this->layoutTempstoreRepository->get($section_storage); + } + +} diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php index 3c3c95777f..5d69f64440 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php @@ -172,7 +172,14 @@ public function buildRoutes(RouteCollection $collection) { $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", @@ -205,9 +212,9 @@ public function buildLocalTasks($base_plugin_definition) { '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'), + $local_tasks["layout_builder.defaults.$entity_type_id.view__child"] = $base_plugin_definition + [ + 'route_name' => "layout_builder.defaults.$entity_type_id.view", + 'title' => $this->t('Manage 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 + [ diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php index fc9e9a3cf8..36f58371c6 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php @@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Entity\RevisionableStorageInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; @@ -185,7 +186,16 @@ private function extractEntityFromRoute($value, array $defaults) { return NULL; } - $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($entity_id); + $storage = $this->entityTypeManager->getStorage($entity_type_id); + $entity_type = $this->entityTypeManager->getDefinition($entity_type_id); + + // @todo Workaround https://www.drupal.org/project/drupal/issues/2942907. + if ($entity_type->isRevisionable() && $storage instanceof RevisionableStorageInterface) { + $entity = $storage->loadRevision($storage->getLatestRevisionId($entity_id)); + } + else { + $entity = $storage->load($entity_id); + } if ($entity instanceof FieldableEntityInterface && $entity->hasField(static::FIELD_NAME)) { return $entity; } @@ -210,7 +220,7 @@ public function buildRoutes(RouteCollection $collection) { $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); } } @@ -227,9 +237,9 @@ public function buildLocalTasks($base_plugin_definition) { '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'), + $local_tasks["layout_builder.overrides.$entity_type_id.view__child"] = $base_plugin_definition + [ + 'route_name' => "layout_builder.overrides.$entity_type_id.view", + 'title' => $this->t('Layout'), 'parent_id' => "layout_builder_ui:layout_builder.overrides.$entity_type_id.view", 'cache_contexts' => ['layout_builder_is_active:' . $entity_type_id], ]; diff --git a/core/modules/layout_builder/src/Routing/LayoutBuilderRouteEnhancer.php b/core/modules/layout_builder/src/Routing/LayoutBuilderRouteEnhancer.php deleted file mode 100644 index 65d25423c9..0000000000 --- a/core/modules/layout_builder/src/Routing/LayoutBuilderRouteEnhancer.php +++ /dev/null @@ -1,27 +0,0 @@ -hasOption('_layout_builder')) { - $defaults['is_rebuilding'] = (bool) $request->query->get('layout_is_rebuilding', FALSE); - } - return $defaults; - } - -} diff --git a/core/modules/layout_builder/src/Routing/LayoutBuilderRoutesTrait.php b/core/modules/layout_builder/src/Routing/LayoutBuilderRoutesTrait.php index 87fc0a2392..567f9b4a9e 100644 --- a/core/modules/layout_builder/src/Routing/LayoutBuilderRoutesTrait.php +++ b/core/modules/layout_builder/src/Routing/LayoutBuilderRoutesTrait.php @@ -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,23 +62,20 @@ protected function buildLayoutRoutes(RouteCollection $collection, SectionStorage } $main_defaults = $defaults; - $main_defaults['is_rebuilding'] = FALSE; - $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__form"; + } + 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")) diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/SimpleConfigSectionStorage.php b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/SimpleConfigSectionStorage.php index 638d1fec6f..a4149f5fde 100644 --- a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/SimpleConfigSectionStorage.php +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/SimpleConfigSectionStorage.php @@ -155,9 +155,9 @@ public function buildLocalTasks($base_plugin_definition) { '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 + [ diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderContentModerationIntegrationTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderContentModerationIntegrationTest.php new file mode 100644 index 0000000000..e2e0897351 --- /dev/null +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderContentModerationIntegrationTest.php @@ -0,0 +1,120 @@ +drupalPlaceBlock('local_tasks_block'); + + // Add a new bundle and add an editorial workflow. + $this->createContentType(['type' => 'bundle_with_section_field']); + $workflow = $this->createEditorialWorkflow(); + $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'bundle_with_section_field'); + $workflow->save(); + + // Enable layout overrides. + LayoutBuilderEntityViewDisplay::load('node.bundle_with_section_field.default') + ->enableLayoutBuilder() + ->setOverridable() + ->save(); + + $this->drupalLogin($this->drupalCreateUser([ + 'configure any layout', + 'edit any bundle_with_section_field content', + 'view bundle_with_section_field revisions', + 'revert bundle_with_section_field revisions', + 'view own unpublished content', + 'view latest version', + 'use editorial transition create_new_draft', + 'use editorial transition publish', + ])); + } + + /** + * Tests that Layout changes are respected by Content Moderation. + */ + public function testLayoutModeration() { + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + + // Create an unpublished node. Revision count: 1. + $node = $this->createNode([ + 'type' => 'bundle_with_section_field', + 'title' => 'The first node title', + 'body' => [ + [ + 'value' => 'The first node body', + ], + ], + ]); + + $this->drupalGet($node->toUrl()); + // Publish the node. Revision count: 2. + $page->fillField('new_state', 'published'); + $page->pressButton('Apply'); + + // Modify the layout. + $page->clickLink('Layout'); + $page->clickLink('Add Block'); + $page->clickLink('Powered by Drupal'); + $page->pressButton('Add Block'); + // Save the node as a draft. Revision count: 3. + $page->fillField('moderation_state[0][state]', 'draft'); + $page->pressButton('Save'); + + // Block is visible on the revision page. + $assert_session->pageTextContains('Powered by Drupal'); + + // Block is not visible on the live node page. + $page->clickLink('View'); + $assert_session->pageTextNotContains('Powered by Drupal'); + + // Publish the node. Revision count: 4. + $page->clickLink('Latest version'); + $page->fillField('new_state', 'published'); + $page->pressButton('Apply'); + + // Block is visible on the live node page. + $assert_session->pageTextContains('Powered by Drupal'); + + // Revert to the previous revision. + $page->clickLink('Revisions'); + // Assert that there are 4 total revisions and 3 revert links. + $assert_session->elementsCount('named', ['link', 'Revert'], 3); + // Revert to the 2nd revision before modifying the layout. + $this->clickLink('Revert', 1); + $page->pressButton('Revert'); + + $page->clickLink('View'); + $assert_session->pageTextNotContains('Powered by Drupal'); + } + +} diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php index 5817ae5dc0..f70e62499e 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php @@ -71,7 +71,7 @@ public function testRenderByContextAwarePluginDelegate() { $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'); diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php index cfa154aa7b..ba2985ee04 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php @@ -87,7 +87,7 @@ public function testOverrides() { $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 @@ public function testOverrides() { $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 @@ public function testLayoutBuilderUi() { // 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 @@ public function testLayoutBuilderUi() { $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 @@ public function testLayoutBuilderUi() { $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 @@ public function testLayoutBuilderUi() { $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 @@ public function testPluginDependencies() { $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 @@ public function testPluginDependencies() { $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 @@ public function testLayoutBuilderUiFullViewMode() { $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 @@ public function testLayoutBuilderUiFullViewMode() { $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 @@ public function testDeletedView() { $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'); diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutCommentFieldTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutCommentFieldTest.php new file mode 100644 index 0000000000..b91f6fa77a --- /dev/null +++ b/core/modules/layout_builder/tests/src/Functional/LayoutCommentFieldTest.php @@ -0,0 +1,84 @@ +drupalPlaceBlock('local_tasks_block'); + + // Create two nodes. + $this->createContentType(['type' => 'bundle_with_section_field']); + $this->addDefaultCommentField('node', 'bundle_with_section_field'); + $this->createNode([ + 'type' => 'bundle_with_section_field', + 'title' => 'The first node title', + 'body' => [ + [ + 'value' => 'The first node body', + ], + ], + ]); + $this->createNode([ + 'type' => 'bundle_with_section_field', + 'title' => 'The second node title', + 'body' => [ + [ + 'value' => 'The second node body', + ], + ], + ]); + } + + /** + * Tests that the comment field is displayed. + */ + public function testCommentField() { + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + $this->drupalLogin($this->drupalCreateUser([ + 'configure any layout', + 'administer node display', + 'administer node fields', + 'post comments', + ])); + + $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field'; + // From the manage display page, go to manage the layout. + // Allow overrides for the layout. + $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/layout'); + $assert_session->pageTextContains('Add new comment'); + } + +} diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutDisplayTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutDisplayTest.php index 4b26c628bc..5f31393a5d 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutDisplayTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutDisplayTest.php @@ -38,6 +38,16 @@ protected function setUp() { ], 'foobar')); } + /** + * Tests viewing a user. + */ + public function testViewUser() { + $assert_session = $this->assertSession(); + + $this->drupalGet('user/' . $this->loggedInUser->id()); + $assert_session->pageTextContains($this->loggedInUser->label()); + } + /** * Tests the interaction between multiple view modes. */ @@ -63,8 +73,7 @@ public function testMultipleViewModes() { $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. diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTestBase.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTestBase.php index 441ec96e52..068d559396 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTestBase.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTestBase.php @@ -79,13 +79,9 @@ protected function setUp() { */ 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(); + + $page->pressButton('Save layout'); $this->assertNotEmpty($assert_session->waitForElement('css', '.messages--status')); if (stristr($this->getUrl(), 'admin/structure') === FALSE) { diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php index d75636564f..a49667f229 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php @@ -116,8 +116,7 @@ public function testDefaultValues() { $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'); diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderUiTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderUiTest.php index 9dc7dd3e7e..a3d87ca5b0 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderUiTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderUiTest.php @@ -65,7 +65,7 @@ public function testUnsavedChangesMessage() { // 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.'); } diff --git a/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php b/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php index 9c5e96d430..f29d4ff110 100644 --- a/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php @@ -378,10 +378,10 @@ public function testBuildRoutes() { [ 'entity_type_id' => 'with_bundle_key', 'bundle_key' => 'my_bundle_type', + 'bundle' => '', 'section_storage_type' => 'defaults', 'section_storage' => '', - 'is_rebuilding' => FALSE, - '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::layout', + '_entity_form' => 'entity_view_display.layout_builder__form', '_title_callback' => '\Drupal\layout_builder\Controller\LayoutBuilderController::title', ], [ @@ -395,28 +395,7 @@ public function testBuildRoutes() { ], '_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( @@ -467,8 +446,7 @@ public function testBuildRoutes() { 'entity_type_id' => 'with_bundle_parameter', 'section_storage_type' => 'defaults', 'section_storage' => '', - 'is_rebuilding' => FALSE, - '_controller' => '\Drupal\layout_builder\Controller\LayoutBuilderController::layout', + '_entity_form' => 'entity_view_display.layout_builder__form', '_title_callback' => '\Drupal\layout_builder\Controller\LayoutBuilderController::title', ], [ @@ -484,27 +462,6 @@ public function testBuildRoutes() { '_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', [ diff --git a/core/modules/layout_builder/tests/src/Unit/LayoutBuilderRouteEnhancerTest.php b/core/modules/layout_builder/tests/src/Unit/LayoutBuilderRouteEnhancerTest.php deleted file mode 100644 index 62c4924d5b..0000000000 --- a/core/modules/layout_builder/tests/src/Unit/LayoutBuilderRouteEnhancerTest.php +++ /dev/null @@ -1,40 +0,0 @@ - TRUE]); - $route_enhancer = new LayoutBuilderRouteEnhancer(); - $defaults = [ - RouteObjectInterface::ROUTE_OBJECT => $route, - ]; - // Ensure that the 'section_storage' key now contains the value stored for a - // given entity type. - $expected = [ - RouteObjectInterface::ROUTE_OBJECT => $route, - 'is_rebuilding' => TRUE, - ]; - $result = $route_enhancer->enhance($defaults, new Request(['layout_is_rebuilding' => TRUE])); - $this->assertEquals($expected, $result); - - $expected['is_rebuilding'] = FALSE; - $result = $route_enhancer->enhance($defaults, new Request()); - $this->assertEquals($expected, $result); - } - -} diff --git a/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php index 567648c0d1..5b6254ad5c 100644 --- a/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php @@ -4,9 +4,11 @@ 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; +use Drupal\Core\Entity\RevisionableStorageInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage; use Drupal\layout_builder\SectionStorage\SectionStorageDefinition; @@ -130,9 +132,13 @@ public function testGetSectionListFromId($success, $expected_entity_type_id, $id $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) { @@ -194,6 +200,10 @@ public function testExtractEntityFromRoute($success, $expected_entity_type_id, $ $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(); @@ -250,6 +260,35 @@ public function providerTestExtractEntityFromRoute() { return $data; } + /** + * @covers ::extractEntityFromRoute + */ + public function testExtractEntityFromRouteRevisionable() { + $expected_id = 'entity_with_layout'; + $expected_entity_type_id = 'my_entity_type'; + + $entity_type = new EntityType([ + 'id' => $expected_entity_type_id, + 'entity_keys' => [ + 'revision' => 'revision', + ], + ]); + $this->entityTypeManager->getDefinition($expected_entity_type_id)->willReturn($entity_type); + + $entity_with_layout = $this->prophesize(FieldableEntityInterface::class); + $entity_with_layout->hasField(OverridesSectionStorage::FIELD_NAME)->willReturn(TRUE); + + $entity_storage = $this->prophesize(RevisionableStorageInterface::class); + $entity_storage->getLatestRevisionId($expected_id)->willReturn(7); + $entity_storage->loadRevision(7)->willReturn($entity_with_layout); + $this->entityTypeManager->getStorage($expected_entity_type_id)->willReturn($entity_storage->reveal()); + + $method = new \ReflectionMethod($this->plugin, 'extractEntityFromRoute'); + $method->setAccessible(TRUE); + $result = $method->invoke($this->plugin, "$expected_entity_type_id.$expected_id", []); + $this->assertSame($entity_with_layout->reveal(), $result); + } + /** * @covers ::buildRoutes * @covers ::hasIntegerId @@ -307,29 +346,8 @@ public function testBuildRoutes() { 'entity_type_id' => 'with_string_id', 'section_storage_type' => 'overrides', 'section_storage' => '', - 'is_rebuilding' => FALSE, - '_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__form', ], [ '_has_layout_section' => 'true', @@ -389,30 +407,8 @@ public function testBuildRoutes() { 'entity_type_id' => 'with_integer_id', 'section_storage_type' => 'overrides', 'section_storage' => '', - 'is_rebuilding' => FALSE, - '_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__form', ], [ '_has_layout_section' => 'true',