diff --git a/core/modules/layout_builder/config/schema/layout_builder.schema.yml b/core/modules/layout_builder/config/schema/layout_builder.schema.yml index 682caa78c4..b6b5fa6342 100644 --- a/core/modules/layout_builder/config/schema/layout_builder.schema.yml +++ b/core/modules/layout_builder/config/schema/layout_builder.schema.yml @@ -2,6 +2,9 @@ core.entity_view_display.*.*.*.third_party.layout_builder: type: mapping label: 'Per-view-mode Layout Builder settings' mapping: + enabled: + type: boolean + label: 'Whether the Layout Builder is enabled for this display' allow_custom: type: boolean label: 'Allow a customized layout' diff --git a/core/modules/layout_builder/layout_builder.install b/core/modules/layout_builder/layout_builder.install index acb1e4fdf3..1bb2a367ad 100644 --- a/core/modules/layout_builder/layout_builder.install +++ b/core/modules/layout_builder/layout_builder.install @@ -13,6 +13,8 @@ * Implements hook_install(). */ function layout_builder_install() { + $display_changed = FALSE; + $displays = LayoutBuilderEntityViewDisplay::loadMultiple(); /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface[] $displays */ foreach ($displays as $display) { @@ -20,21 +22,43 @@ function layout_builder_install() { $field_layout = $display->getThirdPartySettings('field_layout'); if (isset($field_layout['id'])) { $field_layout += ['settings' => []]; - $display->appendSection(new Section($field_layout['id'], $field_layout['settings'])); + $display + ->enableLayoutBuilder() + ->appendSection(new Section($field_layout['id'], $field_layout['settings'])) + ->save(); + $display_changed = TRUE; } - - // Sort the components by weight. - $components = $display->get('content'); - uasort($components, 'Drupal\Component\Utility\SortArray::sortByWeightElement'); - foreach ($components as $name => $component) { - $display->setComponent($name, $component); - } - $display->save(); } // Clear the rendered cache to ensure the new layout builder flow is used. // While in many cases the above change will not affect the rendered output, // the cacheability metadata will have changed and should be processed to // prepare for future changes. - Cache::invalidateTags(['rendered']); + if ($display_changed) { + Cache::invalidateTags(['rendered']); + } +} + +/** + * Enable Layout Builder for existing entity displays. + */ +function layout_builder_update_8601(&$sandbox) { + $config_factory = \Drupal::configFactory(); + + if (!isset($sandbox['count'])) { + $sandbox['ids'] = $config_factory->listAll('core.entity_view_display.'); + $sandbox['count'] = count($sandbox['ids']); + } + + $ids = array_splice($sandbox['ids'], 0, 50); + foreach ($ids as $id) { + $display = $config_factory->getEditable($id); + if ($display->get('third_party_settings.layout_builder')) { + $display + ->set('third_party_settings.layout_builder.enabled', TRUE) + ->save(); + } + } + + $sandbox['#finished'] = empty($sandbox['ids']) ? 1 : ($sandbox['count'] - count($sandbox['ids'])) / $sandbox['count']; } diff --git a/core/modules/layout_builder/layout_builder.post_update.php b/core/modules/layout_builder/layout_builder.post_update.php index 8ddd3fba98..1fcd74d437 100644 --- a/core/modules/layout_builder/layout_builder.post_update.php +++ b/core/modules/layout_builder/layout_builder.post_update.php @@ -6,7 +6,7 @@ */ use Drupal\Core\Config\Entity\ConfigEntityUpdater; -use Drupal\Core\Entity\Display\EntityViewDisplayInterface; +use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface; /** * Rebuild plugin dependencies for all entity view displays. @@ -39,7 +39,11 @@ function layout_builder_post_update_rebuild_plugin_dependencies(&$sandbox = NULL */ function layout_builder_post_update_add_extra_fields(&$sandbox = NULL) { $entity_field_manager = \Drupal::service('entity_field.manager'); - \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'entity_view_display', function (EntityViewDisplayInterface $display) use ($entity_field_manager) { + \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'entity_view_display', function (LayoutEntityDisplayInterface $display) use ($entity_field_manager) { + if (!$display->isLayoutBuilderEnabled()) { + return FALSE; + } + $extra_fields = $entity_field_manager->getExtraFields($display->getTargetEntityTypeId(), $display->getTargetBundle()); $components = $display->getComponents(); // Sort the components to avoid them being reordered by setComponent(). diff --git a/core/modules/layout_builder/layout_builder.routing.yml b/core/modules/layout_builder/layout_builder.routing.yml index 322e2301c1..54c9cf25b2 100644 --- a/core/modules/layout_builder/layout_builder.routing.yml +++ b/core/modules/layout_builder/layout_builder.routing.yml @@ -4,6 +4,7 @@ layout_builder.choose_section: _controller: '\Drupal\layout_builder\Controller\ChooseSectionController::build' requirements: _permission: 'configure any layout' + _layout_builder_access: 'view' options: _admin_route: TRUE parameters: @@ -16,6 +17,7 @@ layout_builder.add_section: _controller: '\Drupal\layout_builder\Controller\AddSectionController::build' requirements: _permission: 'configure any layout' + _layout_builder_access: 'view' options: _admin_route: TRUE parameters: @@ -32,6 +34,7 @@ layout_builder.configure_section: plugin_id: null requirements: _permission: 'configure any layout' + _layout_builder_access: 'view' options: _admin_route: TRUE parameters: @@ -44,6 +47,7 @@ layout_builder.remove_section: _form: '\Drupal\layout_builder\Form\RemoveSectionForm' requirements: _permission: 'configure any layout' + _layout_builder_access: 'view' options: _admin_route: TRUE parameters: @@ -56,6 +60,7 @@ layout_builder.choose_block: _controller: '\Drupal\layout_builder\Controller\ChooseBlockController::build' requirements: _permission: 'configure any layout' + _layout_builder_access: 'view' options: _admin_route: TRUE parameters: @@ -68,6 +73,7 @@ layout_builder.add_block: _form: '\Drupal\layout_builder\Form\AddBlockForm' requirements: _permission: 'configure any layout' + _layout_builder_access: 'view' options: _admin_route: TRUE parameters: @@ -80,6 +86,7 @@ layout_builder.update_block: _form: '\Drupal\layout_builder\Form\UpdateBlockForm' requirements: _permission: 'configure any layout' + _layout_builder_access: 'view' options: _admin_route: TRUE parameters: @@ -92,6 +99,7 @@ layout_builder.remove_block: _form: '\Drupal\layout_builder\Form\RemoveBlockForm' requirements: _permission: 'configure any layout' + _layout_builder_access: 'view' options: _admin_route: TRUE parameters: @@ -110,6 +118,7 @@ layout_builder.move_block: preceding_block_uuid: null requirements: _permission: 'configure any layout' + _layout_builder_access: 'view' options: _admin_route: TRUE parameters: diff --git a/core/modules/layout_builder/layout_builder.services.yml b/core/modules/layout_builder/layout_builder.services.yml index 4fe50929bd..f2360a50ff 100644 --- a/core/modules/layout_builder/layout_builder.services.yml +++ b/core/modules/layout_builder/layout_builder.services.yml @@ -2,6 +2,10 @@ services: layout_builder.tempstore_repository: class: Drupal\layout_builder\LayoutTempstoreRepository arguments: ['@tempstore.shared'] + access_check.entity.layout_builder_access: + class: Drupal\layout_builder\Access\LayoutBuilderAccessCheck + tags: + - { name: access_check, applies_to: _layout_builder_access } access_check.entity.layout: class: Drupal\layout_builder\Access\LayoutSectionAccessCheck tags: diff --git a/core/modules/layout_builder/src/Access/LayoutBuilderAccessCheck.php b/core/modules/layout_builder/src/Access/LayoutBuilderAccessCheck.php new file mode 100644 index 0000000000..0a70db0633 --- /dev/null +++ b/core/modules/layout_builder/src/Access/LayoutBuilderAccessCheck.php @@ -0,0 +1,40 @@ +getRequirement('_layout_builder_access'); + $access = $section_storage->access($operation, $account, TRUE); + if ($access instanceof RefinableCacheableDependencyInterface) { + $access->addCacheableDependency($section_storage); + } + return $access; + } + +} diff --git a/core/modules/layout_builder/src/DefaultsSectionStorageInterface.php b/core/modules/layout_builder/src/DefaultsSectionStorageInterface.php index 121d9c84eb..0bcaa387e0 100644 --- a/core/modules/layout_builder/src/DefaultsSectionStorageInterface.php +++ b/core/modules/layout_builder/src/DefaultsSectionStorageInterface.php @@ -11,8 +11,10 @@ * 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. + * + * @todo Refactor this interface in https://www.drupal.org/node/2985362. */ -interface DefaultsSectionStorageInterface extends SectionStorageInterface, ThirdPartySettingsInterface { +interface DefaultsSectionStorageInterface extends SectionStorageInterface, ThirdPartySettingsInterface, LayoutBuilderEnabledInterface { /** * Determines if the defaults allow custom overrides. diff --git a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php index ea0cd12ff0..5ab2d7264c 100644 --- a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php +++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php @@ -58,6 +58,30 @@ public function setOverridable($overridable = TRUE) { return $this; } + /** + * {@inheritdoc} + */ + public function isLayoutBuilderEnabled() { + return (bool) $this->getThirdPartySetting('layout_builder', 'enabled'); + } + + /** + * {@inheritdoc} + */ + public function enableLayoutBuilder() { + $this->setThirdPartySetting('layout_builder', 'enabled', TRUE); + return $this; + } + + /** + * {@inheritdoc} + */ + public function disableLayoutBuilder() { + $this->setOverridable(FALSE); + $this->setThirdPartySetting('layout_builder', 'enabled', FALSE); + return $this; + } + /** * {@inheritdoc} */ @@ -92,6 +116,27 @@ public function preSave(EntityStorageInterface $storage) { $field->delete(); } } + + $already_enabled = isset($this->original) ? $this->original->isLayoutBuilderEnabled() : FALSE; + $set_enabled = $this->isLayoutBuilderEnabled(); + if ($already_enabled !== $set_enabled) { + if ($set_enabled) { + // Loop through all existing field-based components and add them as + // section-based components. + $components = $this->getComponents(); + // Sort the components by weight. + uasort($components, 'Drupal\Component\Utility\SortArray::sortByWeightElement'); + foreach ($components as $name => $component) { + $this->setComponent($name, $component); + } + } + else { + // When being disabled, remove all existing section data. + while (count($this) > 0) { + $this->removeSection(0); + } + } + } } /** @@ -153,6 +198,9 @@ protected function contextRepository() { */ public function buildMultiple(array $entities) { $build_list = parent::buildMultiple($entities); + if (!$this->isLayoutBuilderEnabled()) { + return $build_list; + } /** @var \Drupal\Core\Entity\EntityInterface $entity */ foreach ($entities as $id => $entity) { @@ -272,6 +320,11 @@ public function setComponent($name, array $options = []) { return $this; } + // Only continue if Layout Builder is enabled. + if (!$this->isLayoutBuilderEnabled()) { + return $this; + } + // Retrieve the updated options after the parent:: call. $options = $this->content[$name]; // Provide backwards compatibility by converting to a section component. diff --git a/core/modules/layout_builder/src/Entity/LayoutEntityDisplayInterface.php b/core/modules/layout_builder/src/Entity/LayoutEntityDisplayInterface.php index b8fd21d822..51151bd81c 100644 --- a/core/modules/layout_builder/src/Entity/LayoutEntityDisplayInterface.php +++ b/core/modules/layout_builder/src/Entity/LayoutEntityDisplayInterface.php @@ -3,6 +3,7 @@ namespace Drupal\layout_builder\Entity; use Drupal\Core\Entity\Display\EntityDisplayInterface; +use Drupal\layout_builder\LayoutBuilderEnabledInterface; use Drupal\layout_builder\SectionListInterface; /** @@ -12,8 +13,10 @@ * 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. + * + * @todo Refactor this interface in https://www.drupal.org/node/2985362. */ -interface LayoutEntityDisplayInterface extends EntityDisplayInterface, SectionListInterface { +interface LayoutEntityDisplayInterface extends EntityDisplayInterface, SectionListInterface, LayoutBuilderEnabledInterface { /** * Determines if the display allows custom overrides. diff --git a/core/modules/layout_builder/src/Form/LayoutBuilderDisableForm.php b/core/modules/layout_builder/src/Form/LayoutBuilderDisableForm.php new file mode 100644 index 0000000000..48b42b740d --- /dev/null +++ b/core/modules/layout_builder/src/Form/LayoutBuilderDisableForm.php @@ -0,0 +1,106 @@ +layoutTempstoreRepository = $layout_tempstore_repository; + $this->setMessenger($messenger); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('layout_builder.tempstore_repository'), + $container->get('messenger') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'layout_builder_disable_form'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->t('Are you sure you want to disable Layout Builder?'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->t('All customizations will be removed. This action cannot be undone.'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return $this->sectionStorage->getRedirectUrl(); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL) { + if (!$section_storage instanceof DefaultsSectionStorageInterface) { + throw new \InvalidArgumentException(sprintf('The section storage with type "%s" and ID "%s" does not provide defaults', $section_storage->getStorageType(), $section_storage->getStorageId())); + } + + $this->sectionStorage = $section_storage; + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->sectionStorage->disableLayoutBuilder()->save(); + $this->layoutTempstoreRepository->delete($this->sectionStorage); + + $this->messenger()->addMessage($this->t('Layout Builder has been disabled.')); + $form_state->setRedirectUrl($this->getCancelUrl()); + } + +} diff --git a/core/modules/layout_builder/src/Form/LayoutBuilderEntityViewDisplayForm.php b/core/modules/layout_builder/src/Form/LayoutBuilderEntityViewDisplayForm.php index 63ec398eec..b24549d9ef 100644 --- a/core/modules/layout_builder/src/Form/LayoutBuilderEntityViewDisplayForm.php +++ b/core/modules/layout_builder/src/Form/LayoutBuilderEntityViewDisplayForm.php @@ -2,6 +2,7 @@ namespace Drupal\layout_builder\Form; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\field_ui\Form\EntityViewDisplayEditForm; @@ -28,7 +29,7 @@ class LayoutBuilderEntityViewDisplayForm extends EntityViewDisplayEditForm { /** * The storage section. * - * @var \Drupal\layout_builder\SectionStorageInterface + * @var \Drupal\layout_builder\DefaultsSectionStorageInterface */ protected $sectionStorage; @@ -46,10 +47,17 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); - // Hide the table of fields. - $form['fields']['#access'] = FALSE; - $form['#fields'] = []; - $form['#extra'] = []; + $is_enabled = $this->entity->isLayoutBuilderEnabled(); + if ($is_enabled) { + // Hide the table of fields. + $form['fields']['#access'] = FALSE; + $form['#fields'] = []; + $form['#extra'] = []; + } + else { + // Remove the Layout Builder field from the list. + $form['#fields'] = array_diff($form['#fields'], ['layout_builder__layout']); + } $form['manage_layout'] = [ '#type' => 'link', @@ -57,18 +65,26 @@ public function form(array $form, FormStateInterface $form_state) { '#weight' => -10, '#attributes' => ['class' => ['button']], '#url' => $this->sectionStorage->getLayoutBuilderUrl(), + '#access' => $is_enabled, ]; + $form['layout'] = [ + '#type' => 'details', + '#open' => TRUE, + '#title' => $this->t('Layout options'), + '#tree' => TRUE, + ]; + + $form['layout']['enabled'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Use Layout Builder'), + '#default_value' => $is_enabled, + ]; + $form['#entity_builders']['layout_builder'] = '::entityFormEntityBuild'; + // @todo Expand to work for all view modes in // https://www.drupal.org/node/2907413. if ($this->entity->getMode() === 'default') { - $form['layout'] = [ - '#type' => 'details', - '#open' => TRUE, - '#title' => $this->t('Layout options'), - '#tree' => TRUE, - ]; - $entity_type = $this->entityTypeManager->getDefinition($this->entity->getTargetEntityTypeId()); $form['layout']['allow_custom'] = [ '#type' => 'checkbox', @@ -76,14 +92,26 @@ public function form(array $form, FormStateInterface $form_state) { '@entity' => $entity_type->getSingularLabel(), ]), '#default_value' => $this->entity->isOverridable(), + '#states' => [ + 'disabled' => [ + ':input[name="layout[enabled]"]' => ['checked' => FALSE], + ], + 'invisible' => [ + ':input[name="layout[enabled]"]' => ['checked' => FALSE], + ], + ], ]; + if (!$is_enabled) { + $form['layout']['allow_custom']['#attributes']['disabled'] = 'disabled'; + } // Prevent turning off overrides while any exist. if ($this->hasOverrides($this->entity)) { + $form['layout']['enabled']['#disabled'] = TRUE; + $form['layout']['enabled']['#description'] = $this->t('You must revert all customized layouts of this display before you can disable this option.'); $form['layout']['allow_custom']['#disabled'] = TRUE; $form['layout']['allow_custom']['#description'] = $this->t('You must revert all customized layouts of this display before you can disable this option.'); - } - else { - $form['#entity_builders'][] = '::entityFormEntityBuild'; + unset($form['layout']['allow_custom']['#states']); + unset($form['#entity_builders']['layout_builder']); } } return $form; @@ -112,26 +140,62 @@ protected function hasOverrides(LayoutEntityDisplayInterface $display) { return (bool) $query->count()->execute(); } + /** + * {@inheritdoc} + */ + protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { + // Do not process field values if Layout Builder is or will be enabled. + $set_enabled = (bool) $form_state->getValue(['layout', 'enabled'], FALSE); + /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface $entity */ + $already_enabled = $entity->isLayoutBuilderEnabled(); + if ($already_enabled || $set_enabled) { + $form['#fields'] = []; + $form['#extra'] = []; + } + + parent::copyFormValuesToEntity($entity, $form, $form_state); + } + /** * Entity builder for layout options on the entity view display form. */ public function entityFormEntityBuild($entity_type_id, LayoutEntityDisplayInterface $display, &$form, FormStateInterface &$form_state) { - $new_value = (bool) $form_state->getValue(['layout', 'allow_custom'], FALSE); - $display->setOverridable($new_value); + $set_enabled = (bool) $form_state->getValue(['layout', 'enabled'], FALSE); + $already_enabled = $display->isLayoutBuilderEnabled(); + + if ($set_enabled) { + $overridable = (bool) $form_state->getValue(['layout', 'allow_custom'], FALSE); + $display->setOverridable($overridable); + + if (!$already_enabled) { + $display->enableLayoutBuilder(); + } + } + elseif ($already_enabled) { + $form_state->setRedirectUrl($this->sectionStorage->getLayoutBuilderUrl('disable')); + } } /** * {@inheritdoc} */ protected function buildFieldRow(FieldDefinitionInterface $field_definition, array $form, FormStateInterface $form_state) { - // Intentionally empty. + if ($this->entity->isLayoutBuilderEnabled() || $field_definition->getType() === 'layout_section') { + return []; + } + + return parent::buildFieldRow($field_definition, $form, $form_state); } /** * {@inheritdoc} */ protected function buildExtraFieldRow($field_id, $extra_field) { - // Intentionally empty. + if ($this->entity->isLayoutBuilderEnabled()) { + return []; + } + + return parent::buildExtraFieldRow($field_id, $extra_field); } } diff --git a/core/modules/layout_builder/src/LayoutBuilderEnabledInterface.php b/core/modules/layout_builder/src/LayoutBuilderEnabledInterface.php new file mode 100644 index 0000000000..dab8fc4bef --- /dev/null +++ b/core/modules/layout_builder/src/LayoutBuilderEnabledInterface.php @@ -0,0 +1,32 @@ +getStorageType()}.{$this->getDisplay()->getTargetEntityTypeId()}.view", $this->getRouteParameters()); + public function getLayoutBuilderUrl($rel = 'view') { + return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getDisplay()->getTargetEntityTypeId()}.$rel", $this->getRouteParameters()); } /** @@ -296,6 +298,29 @@ public function setThirdPartySetting($module, $key, $value) { return $this; } + /** + * {@inheritdoc} + */ + public function isLayoutBuilderEnabled() { + return $this->getDisplay()->isLayoutBuilderEnabled(); + } + + /** + * {@inheritdoc} + */ + public function enableLayoutBuilder() { + $this->getDisplay()->enableLayoutBuilder(); + return $this; + } + + /** + * {@inheritdoc} + */ + public function disableLayoutBuilder() { + $this->getDisplay()->disableLayoutBuilder(); + return $this; + } + /** * {@inheritdoc} */ @@ -325,4 +350,12 @@ public function getThirdPartyProviders() { return $this->getDisplay()->getThirdPartyProviders(); } + /** + * {@inheritdoc} + */ + public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) { + $result = AccessResult::allowedIf($this->isLayoutBuilderEnabled()); + return $return_as_object ? $result : $result->isAllowed(); + } + } diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php index 0da2947dd3..3a92ee63fa 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php @@ -2,6 +2,7 @@ namespace Drupal\layout_builder\Plugin\SectionStorage; +use Drupal\Core\Access\AccessResult; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; @@ -9,6 +10,7 @@ use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\Context\EntityContext; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay; use Drupal\layout_builder\OverridesSectionStorageInterface; @@ -200,10 +202,10 @@ public function getRedirectUrl() { /** * {@inheritdoc} */ - public function getLayoutBuilderUrl() { + public function getLayoutBuilderUrl($rel = 'view') { $entity = $this->getEntity(); $route_parameters[$entity->getEntityTypeId()] = $entity->id(); - return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getEntity()->getEntityTypeId()}.view", $route_parameters); + return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getEntity()->getEntityTypeId()}.$rel", $route_parameters); } /** @@ -229,4 +231,13 @@ public function save() { return $this->getEntity()->save(); } + /** + * {@inheritdoc} + */ + public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) { + $default_section_storage = $this->getDefaultSectionStorage(); + $result = AccessResult::allowedIf($default_section_storage->isLayoutBuilderEnabled())->addCacheableDependency($default_section_storage); + return $return_as_object ? $result : $result->isAllowed(); + } + } diff --git a/core/modules/layout_builder/src/Routing/LayoutBuilderRoutesTrait.php b/core/modules/layout_builder/src/Routing/LayoutBuilderRoutesTrait.php index febedbe31e..d06b95324b 100644 --- a/core/modules/layout_builder/src/Routing/LayoutBuilderRoutesTrait.php +++ b/core/modules/layout_builder/src/Routing/LayoutBuilderRoutesTrait.php @@ -3,6 +3,7 @@ namespace Drupal\layout_builder\Routing; use Drupal\Component\Utility\NestedArray; +use Drupal\layout_builder\DefaultsSectionStorageInterface; use Drupal\layout_builder\OverridesSectionStorageInterface; use Drupal\layout_builder\SectionStorage\SectionStorageDefinition; use Symfony\Component\Routing\Route; @@ -43,6 +44,7 @@ protected function buildLayoutRoutes(RouteCollection $collection, SectionStorage $defaults['section_storage'] = ''; // Trigger the layout builder access check. $requirements['_has_layout_section'] = 'true'; + $requirements['_layout_builder_access'] = 'view'; // Trigger the layout builder RouteEnhancer. $options['_layout_builder'] = TRUE; // Trigger the layout builder param converter. @@ -92,6 +94,17 @@ protected function buildLayoutRoutes(RouteCollection $collection, SectionStorage ->setOptions($options); $collection->add("$route_name_prefix.revert", $route); } + elseif (is_subclass_of($definition->getClass(), DefaultsSectionStorageInterface::class)) { + $disable_defaults = $defaults; + $disable_defaults['_form'] = '\Drupal\layout_builder\Form\LayoutBuilderDisableForm'; + $disable_options = $options; + unset($disable_options['_admin_route'], $disable_options['_layout_builder']); + $route = (new Route("$path/disable")) + ->setDefaults($disable_defaults) + ->setRequirements($requirements) + ->setOptions($disable_options); + $collection->add("$route_name_prefix.disable", $route); + } } } diff --git a/core/modules/layout_builder/src/SectionStorageInterface.php b/core/modules/layout_builder/src/SectionStorageInterface.php index e7fbb086b8..90ce9072fd 100644 --- a/core/modules/layout_builder/src/SectionStorageInterface.php +++ b/core/modules/layout_builder/src/SectionStorageInterface.php @@ -3,6 +3,7 @@ namespace Drupal\layout_builder; use Drupal\Component\Plugin\PluginInspectionInterface; +use Drupal\Core\Access\AccessibleInterface; use Symfony\Component\Routing\RouteCollection; /** @@ -13,7 +14,7 @@ * experimental modules and development releases of contributed modules. * See https://www.drupal.org/core/experimental for more information. */ -interface SectionStorageInterface extends SectionListInterface, PluginInspectionInterface { +interface SectionStorageInterface extends SectionListInterface, PluginInspectionInterface, AccessibleInterface { /** * Returns an identifier for this storage. @@ -88,10 +89,14 @@ public function getRedirectUrl(); /** * Gets the URL used to display the Layout Builder UI. * + * @param string $rel + * (optional) The link relationship type, for example: 'view' or 'disable'. + * Defaults to 'view'. + * * @return \Drupal\Core\Url * The URL object. */ - public function getLayoutBuilderUrl(); + public function getLayoutBuilderUrl($rel = 'view'); /** * Configures the plugin based on route values. diff --git a/core/modules/layout_builder/tests/fixtures/update/layout-builder-enable.php b/core/modules/layout_builder/tests/fixtures/update/layout-builder-enable.php new file mode 100644 index 0000000000..fc69baae33 --- /dev/null +++ b/core/modules/layout_builder/tests/fixtures/update/layout-builder-enable.php @@ -0,0 +1,44 @@ +select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.entity_view_display.block_content.basic.default') + ->execute() + ->fetchField(); +$display = unserialize($display); +$display['third_party_settings']['layout_builder']['sections'][] = [ + 'layout_id' => 'layout_onecol', + 'layout_settings' => [], + 'components' => [ + 'some-uuid' => [ + 'uuid' => 'some-uuid', + 'region' => 'content', + 'configuration' => [ + 'id' => 'system_powered_by_block', + ], + 'additional' => [], + 'weight' => 0, + ], + ], +]; +$connection->update('config') + ->fields([ + 'data' => serialize($display), + 'collection' => '', + 'name' => 'core.entity_view_display.block_content.basic.default', + ]) + ->condition('collection', '') + ->condition('name', 'core.entity_view_display.block_content.basic.default') + ->execute(); diff --git a/core/modules/layout_builder/tests/fixtures/update/layout-builder-extra.php b/core/modules/layout_builder/tests/fixtures/update/layout-builder-extra.php new file mode 100644 index 0000000000..7ec1b6954a --- /dev/null +++ b/core/modules/layout_builder/tests/fixtures/update/layout-builder-extra.php @@ -0,0 +1,29 @@ +select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.entity_view_display.node.article.default') + ->execute() + ->fetchField(); +$display = unserialize($display); +$display['third_party_settings']['layout_builder']['enabled'] = TRUE; +$connection->update('config') + ->fields([ + 'data' => serialize($display), + 'collection' => '', + 'name' => 'core.entity_view_display.node.article.default', + ]) + ->condition('collection', '') + ->condition('name', 'core.entity_view_display.node.article.default') + ->execute(); diff --git a/core/modules/layout_builder/tests/fixtures/update/layout-builder.php b/core/modules/layout_builder/tests/fixtures/update/layout-builder.php index f2e1509934..3f5adb8111 100644 --- a/core/modules/layout_builder/tests/fixtures/update/layout-builder.php +++ b/core/modules/layout_builder/tests/fixtures/update/layout-builder.php @@ -9,6 +9,17 @@ $connection = Database::getConnection(); +// Set the schema version. +$connection->merge('key_value') + ->fields([ + 'value' => 'i:8000;', + 'name' => 'layout_builder', + 'collection' => 'system.schema', + ]) + ->condition('collection', 'system.schema') + ->condition('name', 'layout_builder') + ->execute(); + // Update core.extension. $extensions = $connection->select('config') ->fields('config', ['data']) diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php index afc0801c1a..d39c7e673f 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php @@ -80,6 +80,10 @@ public function testLayoutBuilderUi() { // From the manage display page, go to manage the layout. $this->drupalGet("$field_ui_prefix/display/default"); + $assert_session->linkNotExists('Manage layout'); + $assert_session->fieldDisabled('layout[allow_custom]'); + + $this->drupalPostForm(NULL, ['layout[enabled]' => TRUE], 'Save'); $assert_session->linkExists('Manage layout'); $this->clickLink('Manage layout'); $assert_session->addressEquals("$field_ui_prefix/display-layout/default"); @@ -153,6 +157,7 @@ public function testLayoutBuilderUi() { // Assert that overrides cannot be turned off while overrides exist. $this->drupalGet("$field_ui_prefix/display/default"); + $assert_session->checkboxChecked('layout[allow_custom]'); $assert_session->fieldDisabled('layout[allow_custom]'); // Alter the defaults. @@ -243,7 +248,9 @@ public function testPluginDependencies() { $page->fillField('id', 'myothermenu'); $page->pressButton('Save'); - $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display-layout/default'); + $this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display', ['layout[enabled]' => TRUE], 'Save'); + $assert_session->linkExists('Manage layout'); + $this->clickLink('Manage layout'); $assert_session->linkExists('Add Section'); $this->clickLink('Add Section'); $assert_session->linkExists('Layout plugin (with dependencies)'); @@ -305,6 +312,7 @@ public function testLayoutBuilderUiFullViewMode() { $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field'; // 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'); // Customize the default view mode. @@ -365,7 +373,8 @@ public function testLayoutBuilderChooseBlocksAlter() { ])); // From the manage display page, go to manage the layout. - $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default'); + $this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display/default', ['layout[enabled]' => TRUE], 'Save'); + $assert_session->linkExists('Manage layout'); $this->clickLink('Manage layout'); // Add a new block. @@ -412,6 +421,7 @@ public function testDeletedView() { $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'); diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php index d5e0c7523c..e4af3290c4 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php @@ -41,6 +41,7 @@ protected function setUp() { ]); LayoutBuilderEntityViewDisplay::load('node.bundle_with_section_field.default') + ->enableLayoutBuilder() ->setOverridable() ->save(); diff --git a/core/modules/layout_builder/tests/src/Functional/Update/ExtraFieldUpdatePathTest.php b/core/modules/layout_builder/tests/src/Functional/Update/ExtraFieldUpdatePathTest.php index f33ab299e1..43474c26b1 100644 --- a/core/modules/layout_builder/tests/src/Functional/Update/ExtraFieldUpdatePathTest.php +++ b/core/modules/layout_builder/tests/src/Functional/Update/ExtraFieldUpdatePathTest.php @@ -20,6 +20,7 @@ protected function setDatabaseDumpFiles() { $this->databaseDumpFiles = [ __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz', __DIR__ . '/../../../fixtures/update/layout-builder.php', + __DIR__ . '/../../../fixtures/update/layout-builder-extra.php', ]; } @@ -27,15 +28,26 @@ protected function setDatabaseDumpFiles() { * Tests the upgrade path for Layout Builder extra fields. */ public function testRunUpdates() { + // The default view mode has Layout Builder enabled. + $data = EntityViewDisplay::load('node.article.default')->toArray(); + $this->assertArrayHasKey('third_party_settings', $data); + $this->assertArrayNotHasKey('sections', $data['third_party_settings']['layout_builder']); + + // The teaser view mode does not have Layout Builder enabled. $data = EntityViewDisplay::load('node.article.teaser')->toArray(); $this->assertArrayNotHasKey('third_party_settings', $data); $this->runUpdates(); - $data = EntityViewDisplay::load('node.article.teaser')->toArray(); + // The extra links have been added. + $data = EntityViewDisplay::load('node.article.default')->toArray(); $components = $data['third_party_settings']['layout_builder']['sections'][0]->getComponents(); $component = reset($components); $this->assertSame('extra_field_block:node:article:links', $component->getPluginId()); + + // No extra links have been added. + $data = EntityViewDisplay::load('node.article.teaser')->toArray(); + $this->assertArrayNotHasKey('third_party_settings', $data); } } diff --git a/core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderEnableUpdatePathTest.php b/core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderEnableUpdatePathTest.php new file mode 100644 index 0000000000..5577539359 --- /dev/null +++ b/core/modules/layout_builder/tests/src/Functional/Update/LayoutBuilderEnableUpdatePathTest.php @@ -0,0 +1,86 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz', + __DIR__ . '/../../../fixtures/update/layout-builder.php', + __DIR__ . '/../../../fixtures/update/layout-builder-enable.php', + ]; + } + + /** + * Tests the upgrade path for enabling Layout Builder. + */ + public function testRunUpdates() { + $assert_session = $this->assertSession(); + + $expected = [ + 'sections' => [ + [ + 'layout_id' => 'layout_onecol', + 'layout_settings' => [], + 'components' => [ + 'some-uuid' => [ + 'uuid' => 'some-uuid', + 'region' => 'content', + 'configuration' => [ + 'id' => 'system_powered_by_block', + ], + 'additional' => [], + 'weight' => 0, + ], + ], + ], + ], + ]; + $this->assertLayoutBuilderSettings($expected, 'block_content', 'basic', 'default'); + $this->assertLayoutBuilderSettings(NULL, 'node', 'page', 'default'); + + $this->runUpdates(); + + // The display with existing sections is enabled while the other is not. + $expected['enabled'] = TRUE; + $this->assertLayoutBuilderSettings($expected, 'block_content', 'basic', 'default'); + $this->assertLayoutBuilderSettings(NULL, 'node', 'page', 'default'); + + $this->drupalLogin($this->rootUser); + $this->drupalGet('admin/structure/block/block-content/manage/basic/display'); + $assert_session->checkboxChecked('layout[enabled]'); + $this->drupalGet('admin/structure/types/manage/page/display'); + $assert_session->checkboxNotChecked('layout[enabled]'); + } + + /** + * Asserts the Layout Builder settings for a given display. + * + * @param mixed $expected + * The expected value. + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle + * The bundle. + * @param string $view_mode + * The view mode. + */ + protected function assertLayoutBuilderSettings($expected, $entity_type_id, $bundle, $view_mode) { + $this->assertEquals($expected, \Drupal::config("core.entity_view_display.$entity_type_id.$bundle.$view_mode")->get('third_party_settings.layout_builder')); + } + +} diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/AjaxBlockTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/AjaxBlockTest.php index 1f7849ef9a..653185fe89 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/AjaxBlockTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/AjaxBlockTest.php @@ -60,7 +60,7 @@ public function testAddAjaxBlock() { $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field'; // From the manage display page, go to manage the layout. - $this->drupalGet("$field_ui_prefix/display/default"); + $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save'); $assert_session->linkExists('Manage layout'); $this->clickLink('Manage layout'); $assert_session->addressEquals("$field_ui_prefix/display-layout/default"); diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/ItemLayoutFieldBlockTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/ItemLayoutFieldBlockTest.php index 8f425e4e61..92b48a7762 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/ItemLayoutFieldBlockTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/ItemLayoutFieldBlockTest.php @@ -44,7 +44,10 @@ public function testAddAjaxBlock() { $page = $this->getSession()->getPage(); // Allow overrides for the layout. - $this->drupalPostForm('admin/structure/types/manage/bundle_with_layout_overrides/display/default', ['layout[allow_custom]' => TRUE], 'Save'); + $this->drupalGet('admin/structure/types/manage/bundle_with_layout_overrides/display/default'); + $page->checkField('layout[enabled]'); + $page->checkField('layout[allow_custom]'); + $page->pressButton('Save'); // Start by creating a node of type with layout overrides. $node = $this->createNode([ diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php new file mode 100644 index 0000000000..d75636564f --- /dev/null +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderOptInTest.php @@ -0,0 +1,184 @@ +drupalPlaceBlock('local_tasks_block'); + + // Create one content type before installing Layout Builder and one after. + $this->createContentType(['type' => 'before']); + $this->container->get('module_installer')->install(['layout_builder']); + $this->rebuildAll(); + $this->createContentType(['type' => 'after']); + + $this->drupalLogin($this->drupalCreateUser([ + 'configure any layout', + 'administer node display', + ])); + } + + /** + * Tests the interaction between the two layout checkboxes. + */ + public function testCheckboxLogic() { + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + $this->drupalGet('admin/structure/types/manage/before/display/default'); + // Both fields are unchecked and allow_custom is disabled and hidden. + $assert_session->checkboxNotChecked('layout[enabled]'); + $assert_session->checkboxNotChecked('layout[allow_custom]'); + $assert_session->fieldDisabled('layout[allow_custom]'); + $this->assertFalse($page->findField('layout[allow_custom]')->isVisible()); + + // Checking is_enable will show allow_custom. + $page->checkField('layout[enabled]'); + $assert_session->checkboxNotChecked('layout[allow_custom]'); + $this->assertTrue($page->findField('layout[allow_custom]')->isVisible()); + $page->pressButton('Save'); + $assert_session->checkboxChecked('layout[enabled]'); + $assert_session->checkboxNotChecked('layout[allow_custom]'); + + // Check and submit allow_custom. + $page->checkField('layout[allow_custom]'); + $page->pressButton('Save'); + $assert_session->checkboxChecked('layout[enabled]'); + $assert_session->checkboxChecked('layout[allow_custom]'); + + // Reset the checkboxes. + $page->uncheckField('layout[enabled]'); + $page->pressButton('Save'); + $page->pressButton('Confirm'); + $assert_session->checkboxNotChecked('layout[enabled]'); + $assert_session->checkboxNotChecked('layout[allow_custom]'); + + // Check both at the same time. + $page->checkField('layout[enabled]'); + $page->checkField('layout[allow_custom]'); + $page->pressButton('Save'); + $assert_session->checkboxChecked('layout[enabled]'); + $assert_session->checkboxChecked('layout[allow_custom]'); + } + + /** + * Tests the expected default values for enabling Layout Builder. + */ + public function testDefaultValues() { + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + // Both the content type created before and after Layout Builder was + // installed is still using the Field UI. + $this->drupalGet('admin/structure/types/manage/before/display/default'); + $assert_session->checkboxNotChecked('layout[enabled]'); + + $field_ui_prefix = 'admin/structure/types/manage/after/display/default'; + $this->drupalGet($field_ui_prefix); + $assert_session->checkboxNotChecked('layout[enabled]'); + $page->checkField('layout[enabled]'); + $page->pressButton('Save'); + + $layout_builder_ui = $this->getPathForFieldBlock('node', 'after', 'default', 'body'); + + $assert_session->linkExists('Manage layout'); + $this->clickLink('Manage layout'); + // Ensure the body appears once and only once. + $assert_session->elementsCount('css', '.field--name-body', 1); + + // Change the body formatter to Trimmed. + $this->drupalGet($layout_builder_ui); + $assert_session->fieldValueEquals('settings[formatter][type]', 'text_default'); + $page->selectFieldOption('settings[formatter][type]', 'text_trimmed'); + $assert_session->assertWaitOnAjaxRequest(); + $page->pressButton('Update'); + $assert_session->linkExists('Save Layout'); + $this->clickLink('Save Layout'); + + $this->drupalGet($layout_builder_ui); + $assert_session->fieldValueEquals('settings[formatter][type]', 'text_trimmed'); + + // Disable Layout Builder. + $this->drupalPostForm($field_ui_prefix, ['layout[enabled]' => FALSE], 'Save'); + $page->pressButton('Confirm'); + + // The Layout Builder UI is no longer accessible. + $this->drupalGet($layout_builder_ui); + $assert_session->pageTextContains('You are not authorized to access this page.'); + + // The original body formatter is reflected in Field UI. + $this->drupalGet($field_ui_prefix); + $assert_session->fieldValueEquals('fields[body][type]', 'text_default'); + + // Change the body formatter to Summary. + $page->selectFieldOption('fields[body][type]', 'text_summary_or_trimmed'); + $assert_session->assertWaitOnAjaxRequest(); + $page->pressButton('Save'); + $assert_session->fieldValueEquals('fields[body][type]', 'text_summary_or_trimmed'); + + // Reactivate Layout Builder. + $this->drupalPostForm($field_ui_prefix, ['layout[enabled]' => TRUE], 'Save'); + $assert_session->linkExists('Manage layout'); + $this->clickLink('Manage layout'); + // Ensure the body appears once and only once. + $assert_session->elementsCount('css', '.field--name-body', 1); + + // The changed body formatter is reflected in Layout Builder UI. + $this->drupalGet($this->getPathForFieldBlock('node', 'after', 'default', 'body')); + $assert_session->fieldValueEquals('settings[formatter][type]', 'text_summary_or_trimmed'); + } + + /** + * Returns the path to update a field block in the UI. + * + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle + * The bundle. + * @param string $view_mode + * The view mode. + * @param string $field_name + * The field name. + * + * @return string + * The path. + */ + protected function getPathForFieldBlock($entity_type_id, $bundle, $view_mode, $field_name) { + $delta = 0; + /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface $display */ + $display = $this->container->get('entity_type.manager')->getStorage('entity_view_display')->load("$entity_type_id.$bundle.$view_mode"); + $body_component = NULL; + foreach ($display->getSection($delta)->getComponents() as $component) { + if ($component->getPluginId() === "field_block:$entity_type_id:$bundle:$field_name") { + $body_component = $component; + } + } + $this->assertNotNull($body_component); + return 'layout_builder/update/block/defaults/node.after.default/0/content/' . $body_component->getUuid(); + } + +} diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php index d5a4cd0b8a..d924c688b4 100644 --- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderCompatibilityTestBase.php @@ -23,7 +23,7 @@ /** * The entity view display. * - * @var \Drupal\field_layout\Display\EntityDisplayWithLayoutInterface + * @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface */ protected $display; @@ -86,13 +86,21 @@ protected function setUp() { /** * Installs the Layout Builder. * - * Also configures and reloads the entity display, and reloads the entity. + * Also configures and reloads the entity display. */ protected function installLayoutBuilder() { $this->container->get('module_installer')->install(['layout_builder']); $this->refreshServices(); $this->display = $this->reloadEntity($this->display); + $this->display->enableLayoutBuilder()->save(); + $this->entity = $this->reloadEntity($this->entity); + } + + /** + * Enables overrides for the display and reloads the entity. + */ + protected function enableOverrides() { $this->display->setOverridable()->save(); $this->entity = $this->reloadEntity($this->entity); } diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderFieldLayoutCompatibilityTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderFieldLayoutCompatibilityTest.php index 59a76615c1..57dacb5b18 100644 --- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderFieldLayoutCompatibilityTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderFieldLayoutCompatibilityTest.php @@ -54,6 +54,7 @@ public function testCompatibility() { $this->assertFieldAttributes($this->entity, $expected_fields); // Add a layout override. + $this->enableOverrides(); /** @var \Drupal\layout_builder\SectionStorageInterface $field_list */ $field_list = $this->entity->get('layout_builder__layout'); $field_list->appendSection(new Section('layout_onecol')); diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php index fa646df1c9..aa1b9942c8 100644 --- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php @@ -33,6 +33,8 @@ public function testCompatibility() { $this->assertFieldAttributes($this->entity, $expected_fields); // Add a layout override. + $this->enableOverrides(); + $this->entity = $this->reloadEntity($this->entity); $this->entity->get('layout_builder__layout')->appendSection(new Section('layout_onecol')); $this->entity->save(); diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutSectionItemListTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutSectionItemListTest.php index af8395f173..5bd354675c 100644 --- a/core/modules/layout_builder/tests/src/Kernel/LayoutSectionItemListTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutSectionItemListTest.php @@ -32,7 +32,10 @@ protected function getSectionStorage(array $section_data) { 'bundle' => 'entity_test_base_field_display', 'mode' => 'default', 'status' => TRUE, - ])->setOverridable()->save(); + ]) + ->enableLayoutBuilder() + ->setOverridable() + ->save(); array_map(function ($row) { return ['section' => $row]; diff --git a/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php b/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php index 7e5304dbbe..d30917a446 100644 --- a/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php @@ -254,6 +254,7 @@ public function testBuildRoutes() { [ '_field_ui_view_mode_access' => 'administer with_bundle_key display', '_has_layout_section' => 'true', + '_layout_builder_access' => 'view', ], [ 'parameters' => [ @@ -275,6 +276,7 @@ public function testBuildRoutes() { [ '_field_ui_view_mode_access' => 'administer with_bundle_key display', '_has_layout_section' => 'true', + '_layout_builder_access' => 'view', ], [ 'parameters' => [ @@ -296,6 +298,7 @@ public function testBuildRoutes() { [ '_field_ui_view_mode_access' => 'administer with_bundle_key display', '_has_layout_section' => 'true', + '_layout_builder_access' => 'view', ], [ 'parameters' => [ @@ -305,6 +308,26 @@ public function testBuildRoutes() { '_admin_route' => FALSE, ] ), + 'layout_builder.defaults.with_bundle_key.disable' => new Route( + '/admin/entity/whatever/display-layout/{view_mode_name}/disable', + [ + 'entity_type_id' => 'with_bundle_key', + 'bundle_key' => 'my_bundle_type', + 'section_storage_type' => 'defaults', + 'section_storage' => '', + '_form' => '\Drupal\layout_builder\Form\LayoutBuilderDisableForm', + ], + [ + '_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.defaults.with_bundle_parameter.view' => new Route( '/admin/entity/{bundle}/display-layout/{view_mode_name}', [ @@ -318,6 +341,7 @@ public function testBuildRoutes() { [ '_field_ui_view_mode_access' => 'administer with_bundle_parameter display', '_has_layout_section' => 'true', + '_layout_builder_access' => 'view', ], [ 'parameters' => [ @@ -338,6 +362,7 @@ public function testBuildRoutes() { [ '_field_ui_view_mode_access' => 'administer with_bundle_parameter display', '_has_layout_section' => 'true', + '_layout_builder_access' => 'view', ], [ 'parameters' => [ @@ -358,6 +383,7 @@ public function testBuildRoutes() { [ '_field_ui_view_mode_access' => 'administer with_bundle_parameter display', '_has_layout_section' => 'true', + '_layout_builder_access' => 'view', ], [ 'parameters' => [ @@ -367,6 +393,25 @@ public function testBuildRoutes() { '_admin_route' => FALSE, ] ), + 'layout_builder.defaults.with_bundle_parameter.disable' => new Route( + '/admin/entity/{bundle}/display-layout/{view_mode_name}/disable', + [ + 'entity_type_id' => 'with_bundle_parameter', + 'section_storage_type' => 'defaults', + 'section_storage' => '', + '_form' => '\Drupal\layout_builder\Form\LayoutBuilderDisableForm', + ], + [ + '_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], + ], + ] + ), ]; $collection = new RouteCollection(); diff --git a/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php index bbeb049a61..110ec2021e 100644 --- a/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php @@ -223,6 +223,7 @@ public function testBuildRoutes() { ], [ '_has_layout_section' => 'true', + '_layout_builder_access' => 'view', ], [ 'parameters' => [ @@ -242,6 +243,7 @@ public function testBuildRoutes() { ], [ '_has_layout_section' => 'true', + '_layout_builder_access' => 'view', ], [ 'parameters' => [ @@ -261,6 +263,7 @@ public function testBuildRoutes() { ], [ '_has_layout_section' => 'true', + '_layout_builder_access' => 'view', ], [ 'parameters' => [ @@ -280,6 +283,7 @@ public function testBuildRoutes() { ], [ '_has_layout_section' => 'true', + '_layout_builder_access' => 'view', ], [ 'parameters' => [ @@ -301,6 +305,7 @@ public function testBuildRoutes() { ], [ '_has_layout_section' => 'true', + '_layout_builder_access' => 'view', 'with_integer_id' => '\d+', ], [ @@ -321,6 +326,7 @@ public function testBuildRoutes() { ], [ '_has_layout_section' => 'true', + '_layout_builder_access' => 'view', 'with_integer_id' => '\d+', ], [ @@ -341,6 +347,7 @@ public function testBuildRoutes() { ], [ '_has_layout_section' => 'true', + '_layout_builder_access' => 'view', 'with_integer_id' => '\d+', ], [ @@ -361,6 +368,7 @@ public function testBuildRoutes() { ], [ '_has_layout_section' => 'true', + '_layout_builder_access' => 'view', 'with_integer_id' => '\d+', ], [