Skip to content
Snippets Groups Projects
Unverified Commit c6cdc439 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #2936358 by tim.plunkett, johndevman, tedbow, eiriksm, AaronMcHale,...

Issue #2936358 by tim.plunkett, johndevman, tedbow, eiriksm, AaronMcHale, phenaproxima, pookmish, alexpott: Layout Builder should be opt-in per display (entity type/bundle/view mode)
parent df94b0c1
No related branches found
No related tags found
2 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10
Showing
with 541 additions and 41 deletions
...@@ -2,6 +2,9 @@ core.entity_view_display.*.*.*.third_party.layout_builder: ...@@ -2,6 +2,9 @@ core.entity_view_display.*.*.*.third_party.layout_builder:
type: mapping type: mapping
label: 'Per-view-mode Layout Builder settings' label: 'Per-view-mode Layout Builder settings'
mapping: mapping:
enabled:
type: boolean
label: 'Whether the Layout Builder is enabled for this display'
allow_custom: allow_custom:
type: boolean type: boolean
label: 'Allow a customized layout' label: 'Allow a customized layout'
......
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
* Implements hook_install(). * Implements hook_install().
*/ */
function layout_builder_install() { function layout_builder_install() {
$display_changed = FALSE;
$displays = LayoutBuilderEntityViewDisplay::loadMultiple(); $displays = LayoutBuilderEntityViewDisplay::loadMultiple();
/** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface[] $displays */ /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface[] $displays */
foreach ($displays as $display) { foreach ($displays as $display) {
...@@ -20,21 +22,43 @@ function layout_builder_install() { ...@@ -20,21 +22,43 @@ function layout_builder_install() {
$field_layout = $display->getThirdPartySettings('field_layout'); $field_layout = $display->getThirdPartySettings('field_layout');
if (isset($field_layout['id'])) { if (isset($field_layout['id'])) {
$field_layout += ['settings' => []]; $field_layout += ['settings' => []];
$display->appendSection(new Section($field_layout['id'], $field_layout['settings'])); $display
} ->enableLayoutBuilder()
->appendSection(new Section($field_layout['id'], $field_layout['settings']))
// Sort the components by weight. ->save();
$components = $display->get('content'); $display_changed = TRUE;
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. // 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, // While in many cases the above change will not affect the rendered output,
// the cacheability metadata will have changed and should be processed to // the cacheability metadata will have changed and should be processed to
// prepare for future changes. // 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'];
} }
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
*/ */
use Drupal\Core\Config\Entity\ConfigEntityUpdater; 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. * Rebuild plugin dependencies for all entity view displays.
...@@ -39,7 +39,11 @@ function layout_builder_post_update_rebuild_plugin_dependencies(&$sandbox = NULL ...@@ -39,7 +39,11 @@ function layout_builder_post_update_rebuild_plugin_dependencies(&$sandbox = NULL
*/ */
function layout_builder_post_update_add_extra_fields(&$sandbox = NULL) { function layout_builder_post_update_add_extra_fields(&$sandbox = NULL) {
$entity_field_manager = \Drupal::service('entity_field.manager'); $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()); $extra_fields = $entity_field_manager->getExtraFields($display->getTargetEntityTypeId(), $display->getTargetBundle());
$components = $display->getComponents(); $components = $display->getComponents();
// Sort the components to avoid them being reordered by setComponent(). // Sort the components to avoid them being reordered by setComponent().
......
...@@ -4,6 +4,7 @@ layout_builder.choose_section: ...@@ -4,6 +4,7 @@ layout_builder.choose_section:
_controller: '\Drupal\layout_builder\Controller\ChooseSectionController::build' _controller: '\Drupal\layout_builder\Controller\ChooseSectionController::build'
requirements: requirements:
_permission: 'configure any layout' _permission: 'configure any layout'
_layout_builder_access: 'view'
options: options:
_admin_route: TRUE _admin_route: TRUE
parameters: parameters:
...@@ -16,6 +17,7 @@ layout_builder.add_section: ...@@ -16,6 +17,7 @@ layout_builder.add_section:
_controller: '\Drupal\layout_builder\Controller\AddSectionController::build' _controller: '\Drupal\layout_builder\Controller\AddSectionController::build'
requirements: requirements:
_permission: 'configure any layout' _permission: 'configure any layout'
_layout_builder_access: 'view'
options: options:
_admin_route: TRUE _admin_route: TRUE
parameters: parameters:
...@@ -32,6 +34,7 @@ layout_builder.configure_section: ...@@ -32,6 +34,7 @@ layout_builder.configure_section:
plugin_id: null plugin_id: null
requirements: requirements:
_permission: 'configure any layout' _permission: 'configure any layout'
_layout_builder_access: 'view'
options: options:
_admin_route: TRUE _admin_route: TRUE
parameters: parameters:
...@@ -44,6 +47,7 @@ layout_builder.remove_section: ...@@ -44,6 +47,7 @@ layout_builder.remove_section:
_form: '\Drupal\layout_builder\Form\RemoveSectionForm' _form: '\Drupal\layout_builder\Form\RemoveSectionForm'
requirements: requirements:
_permission: 'configure any layout' _permission: 'configure any layout'
_layout_builder_access: 'view'
options: options:
_admin_route: TRUE _admin_route: TRUE
parameters: parameters:
...@@ -56,6 +60,7 @@ layout_builder.choose_block: ...@@ -56,6 +60,7 @@ layout_builder.choose_block:
_controller: '\Drupal\layout_builder\Controller\ChooseBlockController::build' _controller: '\Drupal\layout_builder\Controller\ChooseBlockController::build'
requirements: requirements:
_permission: 'configure any layout' _permission: 'configure any layout'
_layout_builder_access: 'view'
options: options:
_admin_route: TRUE _admin_route: TRUE
parameters: parameters:
...@@ -68,6 +73,7 @@ layout_builder.add_block: ...@@ -68,6 +73,7 @@ layout_builder.add_block:
_form: '\Drupal\layout_builder\Form\AddBlockForm' _form: '\Drupal\layout_builder\Form\AddBlockForm'
requirements: requirements:
_permission: 'configure any layout' _permission: 'configure any layout'
_layout_builder_access: 'view'
options: options:
_admin_route: TRUE _admin_route: TRUE
parameters: parameters:
...@@ -80,6 +86,7 @@ layout_builder.update_block: ...@@ -80,6 +86,7 @@ layout_builder.update_block:
_form: '\Drupal\layout_builder\Form\UpdateBlockForm' _form: '\Drupal\layout_builder\Form\UpdateBlockForm'
requirements: requirements:
_permission: 'configure any layout' _permission: 'configure any layout'
_layout_builder_access: 'view'
options: options:
_admin_route: TRUE _admin_route: TRUE
parameters: parameters:
...@@ -92,6 +99,7 @@ layout_builder.remove_block: ...@@ -92,6 +99,7 @@ layout_builder.remove_block:
_form: '\Drupal\layout_builder\Form\RemoveBlockForm' _form: '\Drupal\layout_builder\Form\RemoveBlockForm'
requirements: requirements:
_permission: 'configure any layout' _permission: 'configure any layout'
_layout_builder_access: 'view'
options: options:
_admin_route: TRUE _admin_route: TRUE
parameters: parameters:
...@@ -110,6 +118,7 @@ layout_builder.move_block: ...@@ -110,6 +118,7 @@ layout_builder.move_block:
preceding_block_uuid: null preceding_block_uuid: null
requirements: requirements:
_permission: 'configure any layout' _permission: 'configure any layout'
_layout_builder_access: 'view'
options: options:
_admin_route: TRUE _admin_route: TRUE
parameters: parameters:
......
...@@ -2,6 +2,10 @@ services: ...@@ -2,6 +2,10 @@ services:
layout_builder.tempstore_repository: layout_builder.tempstore_repository:
class: Drupal\layout_builder\LayoutTempstoreRepository class: Drupal\layout_builder\LayoutTempstoreRepository
arguments: ['@tempstore.shared'] 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: access_check.entity.layout:
class: Drupal\layout_builder\Access\LayoutSectionAccessCheck class: Drupal\layout_builder\Access\LayoutSectionAccessCheck
tags: tags:
......
<?php
namespace Drupal\layout_builder\Access;
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\layout_builder\SectionStorageInterface;
use Symfony\Component\Routing\Route;
/**
* Provides an access check for the Layout Builder defaults.
*
* @internal
*/
class LayoutBuilderAccessCheck implements AccessInterface {
/**
* Checks routing access to the layout.
*
* @param \Drupal\layout_builder\SectionStorageInterface $section_storage
* The section storage.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(SectionStorageInterface $section_storage, AccountInterface $account, Route $route) {
$operation = $route->getRequirement('_layout_builder_access');
$access = $section_storage->access($operation, $account, TRUE);
if ($access instanceof RefinableCacheableDependencyInterface) {
$access->addCacheableDependency($section_storage);
}
return $access;
}
}
...@@ -11,8 +11,10 @@ ...@@ -11,8 +11,10 @@
* Layout Builder is currently experimental and should only be leveraged by * Layout Builder is currently experimental and should only be leveraged by
* experimental modules and development releases of contributed modules. * experimental modules and development releases of contributed modules.
* See https://www.drupal.org/core/experimental for more information. * 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. * Determines if the defaults allow custom overrides.
......
...@@ -58,6 +58,30 @@ public function setOverridable($overridable = TRUE) { ...@@ -58,6 +58,30 @@ public function setOverridable($overridable = TRUE) {
return $this; 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} * {@inheritdoc}
*/ */
...@@ -92,6 +116,27 @@ public function preSave(EntityStorageInterface $storage) { ...@@ -92,6 +116,27 @@ public function preSave(EntityStorageInterface $storage) {
$field->delete(); $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() { ...@@ -153,6 +198,9 @@ protected function contextRepository() {
*/ */
public function buildMultiple(array $entities) { public function buildMultiple(array $entities) {
$build_list = parent::buildMultiple($entities); $build_list = parent::buildMultiple($entities);
if (!$this->isLayoutBuilderEnabled()) {
return $build_list;
}
/** @var \Drupal\Core\Entity\EntityInterface $entity */ /** @var \Drupal\Core\Entity\EntityInterface $entity */
foreach ($entities as $id => $entity) { foreach ($entities as $id => $entity) {
...@@ -272,6 +320,11 @@ public function setComponent($name, array $options = []) { ...@@ -272,6 +320,11 @@ public function setComponent($name, array $options = []) {
return $this; return $this;
} }
// Only continue if Layout Builder is enabled.
if (!$this->isLayoutBuilderEnabled()) {
return $this;
}
// Retrieve the updated options after the parent:: call. // Retrieve the updated options after the parent:: call.
$options = $this->content[$name]; $options = $this->content[$name];
// Provide backwards compatibility by converting to a section component. // Provide backwards compatibility by converting to a section component.
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace Drupal\layout_builder\Entity; namespace Drupal\layout_builder\Entity;
use Drupal\Core\Entity\Display\EntityDisplayInterface; use Drupal\Core\Entity\Display\EntityDisplayInterface;
use Drupal\layout_builder\LayoutBuilderEnabledInterface;
use Drupal\layout_builder\SectionListInterface; use Drupal\layout_builder\SectionListInterface;
/** /**
...@@ -12,8 +13,10 @@ ...@@ -12,8 +13,10 @@
* Layout Builder is currently experimental and should only be leveraged by * Layout Builder is currently experimental and should only be leveraged by
* experimental modules and development releases of contributed modules. * experimental modules and development releases of contributed modules.
* See https://www.drupal.org/core/experimental for more information. * 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. * Determines if the display allows custom overrides.
......
<?php
namespace Drupal\layout_builder\Form;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\layout_builder\DefaultsSectionStorageInterface;
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
use Drupal\layout_builder\SectionStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Disables Layout Builder for a given default.
*/
class LayoutBuilderDisableForm extends ConfirmFormBase {
/**
* The layout tempstore repository.
*
* @var \Drupal\layout_builder\LayoutTempstoreRepositoryInterface
*/
protected $layoutTempstoreRepository;
/**
* The section storage.
*
* @var \Drupal\layout_builder\DefaultsSectionStorageInterface
*/
protected $sectionStorage;
/**
* Constructs a new RevertOverridesForm.
*
* @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->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());
}
}
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Drupal\layout_builder\Form; namespace Drupal\layout_builder\Form;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\field_ui\Form\EntityViewDisplayEditForm; use Drupal\field_ui\Form\EntityViewDisplayEditForm;
...@@ -28,7 +29,7 @@ class LayoutBuilderEntityViewDisplayForm extends EntityViewDisplayEditForm { ...@@ -28,7 +29,7 @@ class LayoutBuilderEntityViewDisplayForm extends EntityViewDisplayEditForm {
/** /**
* The storage section. * The storage section.
* *
* @var \Drupal\layout_builder\SectionStorageInterface * @var \Drupal\layout_builder\DefaultsSectionStorageInterface
*/ */
protected $sectionStorage; protected $sectionStorage;
...@@ -46,10 +47,17 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt ...@@ -46,10 +47,17 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt
public function form(array $form, FormStateInterface $form_state) { public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state); $form = parent::form($form, $form_state);
// Hide the table of fields. $is_enabled = $this->entity->isLayoutBuilderEnabled();
$form['fields']['#access'] = FALSE; if ($is_enabled) {
$form['#fields'] = []; // Hide the table of fields.
$form['#extra'] = []; $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'] = [ $form['manage_layout'] = [
'#type' => 'link', '#type' => 'link',
...@@ -57,18 +65,26 @@ public function form(array $form, FormStateInterface $form_state) { ...@@ -57,18 +65,26 @@ public function form(array $form, FormStateInterface $form_state) {
'#weight' => -10, '#weight' => -10,
'#attributes' => ['class' => ['button']], '#attributes' => ['class' => ['button']],
'#url' => $this->sectionStorage->getLayoutBuilderUrl(), '#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 // @todo Expand to work for all view modes in
// https://www.drupal.org/node/2907413. // https://www.drupal.org/node/2907413.
if ($this->entity->getMode() === 'default') { 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()); $entity_type = $this->entityTypeManager->getDefinition($this->entity->getTargetEntityTypeId());
$form['layout']['allow_custom'] = [ $form['layout']['allow_custom'] = [
'#type' => 'checkbox', '#type' => 'checkbox',
...@@ -76,14 +92,26 @@ public function form(array $form, FormStateInterface $form_state) { ...@@ -76,14 +92,26 @@ public function form(array $form, FormStateInterface $form_state) {
'@entity' => $entity_type->getSingularLabel(), '@entity' => $entity_type->getSingularLabel(),
]), ]),
'#default_value' => $this->entity->isOverridable(), '#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. // Prevent turning off overrides while any exist.
if ($this->hasOverrides($this->entity)) { 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']['#disabled'] = TRUE;
$form['layout']['allow_custom']['#description'] = $this->t('You must revert all customized layouts of this display before you can disable this option.'); $form['layout']['allow_custom']['#description'] = $this->t('You must revert all customized layouts of this display before you can disable this option.');
} unset($form['layout']['allow_custom']['#states']);
else { unset($form['#entity_builders']['layout_builder']);
$form['#entity_builders'][] = '::entityFormEntityBuild';
} }
} }
return $form; return $form;
...@@ -112,26 +140,62 @@ protected function hasOverrides(LayoutEntityDisplayInterface $display) { ...@@ -112,26 +140,62 @@ protected function hasOverrides(LayoutEntityDisplayInterface $display) {
return (bool) $query->count()->execute(); 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. * Entity builder for layout options on the entity view display form.
*/ */
public function entityFormEntityBuild($entity_type_id, LayoutEntityDisplayInterface $display, &$form, FormStateInterface &$form_state) { public function entityFormEntityBuild($entity_type_id, LayoutEntityDisplayInterface $display, &$form, FormStateInterface &$form_state) {
$new_value = (bool) $form_state->getValue(['layout', 'allow_custom'], FALSE); $set_enabled = (bool) $form_state->getValue(['layout', 'enabled'], FALSE);
$display->setOverridable($new_value); $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} * {@inheritdoc}
*/ */
protected function buildFieldRow(FieldDefinitionInterface $field_definition, array $form, FormStateInterface $form_state) { 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} * {@inheritdoc}
*/ */
protected function buildExtraFieldRow($field_id, $extra_field) { protected function buildExtraFieldRow($field_id, $extra_field) {
// Intentionally empty. if ($this->entity->isLayoutBuilderEnabled()) {
return [];
}
return parent::buildExtraFieldRow($field_id, $extra_field);
} }
} }
<?php
namespace Drupal\layout_builder;
/**
* Provides methods for enabling and disabling Layout Builder.
*/
interface LayoutBuilderEnabledInterface {
/**
* Determines if Layout Builder is enabled.
*
* @return bool
* TRUE if Layout Builder is enabled, FALSE otherwise.
*/
public function isLayoutBuilderEnabled();
/**
* Enables the Layout Builder.
*
* @return $this
*/
public function enableLayoutBuilder();
/**
* Disables the Layout Builder.
*
* @return $this
*/
public function disableLayoutBuilder();
}
...@@ -3,12 +3,14 @@ ...@@ -3,12 +3,14 @@
namespace Drupal\layout_builder\Plugin\SectionStorage; namespace Drupal\layout_builder\Plugin\SectionStorage;
use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\Context\EntityContext; use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\field_ui\FieldUI; use Drupal\field_ui\FieldUI;
use Drupal\layout_builder\DefaultsSectionStorageInterface; use Drupal\layout_builder\DefaultsSectionStorageInterface;
...@@ -123,8 +125,8 @@ public function getRedirectUrl() { ...@@ -123,8 +125,8 @@ public function getRedirectUrl() {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getLayoutBuilderUrl() { public function getLayoutBuilderUrl($rel = 'view') {
return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getDisplay()->getTargetEntityTypeId()}.view", $this->getRouteParameters()); return Url::fromRoute("layout_builder.{$this->getStorageType()}.{$this->getDisplay()->getTargetEntityTypeId()}.$rel", $this->getRouteParameters());
} }
/** /**
...@@ -296,6 +298,29 @@ public function setThirdPartySetting($module, $key, $value) { ...@@ -296,6 +298,29 @@ public function setThirdPartySetting($module, $key, $value) {
return $this; 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} * {@inheritdoc}
*/ */
...@@ -325,4 +350,12 @@ public function getThirdPartyProviders() { ...@@ -325,4 +350,12 @@ public function getThirdPartyProviders() {
return $this->getDisplay()->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();
}
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Drupal\layout_builder\Plugin\SectionStorage; namespace Drupal\layout_builder\Plugin\SectionStorage;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface;
...@@ -9,6 +10,7 @@ ...@@ -9,6 +10,7 @@
use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\Context\EntityContext; use Drupal\Core\Plugin\Context\EntityContext;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay; use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
use Drupal\layout_builder\OverridesSectionStorageInterface; use Drupal\layout_builder\OverridesSectionStorageInterface;
...@@ -200,10 +202,10 @@ public function getRedirectUrl() { ...@@ -200,10 +202,10 @@ public function getRedirectUrl() {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getLayoutBuilderUrl() { public function getLayoutBuilderUrl($rel = 'view') {
$entity = $this->getEntity(); $entity = $this->getEntity();
$route_parameters[$entity->getEntityTypeId()] = $entity->id(); $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() { ...@@ -229,4 +231,13 @@ public function save() {
return $this->getEntity()->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();
}
} }
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace Drupal\layout_builder\Routing; namespace Drupal\layout_builder\Routing;
use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\NestedArray;
use Drupal\layout_builder\DefaultsSectionStorageInterface;
use Drupal\layout_builder\OverridesSectionStorageInterface; use Drupal\layout_builder\OverridesSectionStorageInterface;
use Drupal\layout_builder\SectionStorage\SectionStorageDefinition; use Drupal\layout_builder\SectionStorage\SectionStorageDefinition;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;
...@@ -43,6 +44,7 @@ protected function buildLayoutRoutes(RouteCollection $collection, SectionStorage ...@@ -43,6 +44,7 @@ protected function buildLayoutRoutes(RouteCollection $collection, SectionStorage
$defaults['section_storage'] = ''; $defaults['section_storage'] = '';
// Trigger the layout builder access check. // Trigger the layout builder access check.
$requirements['_has_layout_section'] = 'true'; $requirements['_has_layout_section'] = 'true';
$requirements['_layout_builder_access'] = 'view';
// Trigger the layout builder RouteEnhancer. // Trigger the layout builder RouteEnhancer.
$options['_layout_builder'] = TRUE; $options['_layout_builder'] = TRUE;
// Trigger the layout builder param converter. // Trigger the layout builder param converter.
...@@ -92,6 +94,17 @@ protected function buildLayoutRoutes(RouteCollection $collection, SectionStorage ...@@ -92,6 +94,17 @@ protected function buildLayoutRoutes(RouteCollection $collection, SectionStorage
->setOptions($options); ->setOptions($options);
$collection->add("$route_name_prefix.revert", $route); $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);
}
} }
} }
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace Drupal\layout_builder; namespace Drupal\layout_builder;
use Drupal\Component\Plugin\PluginInspectionInterface; use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\Access\AccessibleInterface;
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouteCollection;
/** /**
...@@ -13,7 +14,7 @@ ...@@ -13,7 +14,7 @@
* experimental modules and development releases of contributed modules. * experimental modules and development releases of contributed modules.
* See https://www.drupal.org/core/experimental for more information. * 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. * Returns an identifier for this storage.
...@@ -88,10 +89,14 @@ public function getRedirectUrl(); ...@@ -88,10 +89,14 @@ public function getRedirectUrl();
/** /**
* Gets the URL used to display the Layout Builder UI. * 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 * @return \Drupal\Core\Url
* The URL object. * The URL object.
*/ */
public function getLayoutBuilderUrl(); public function getLayoutBuilderUrl($rel = 'view');
/** /**
* Configures the plugin based on route values. * Configures the plugin based on route values.
......
<?php
/**
* @file
* Test fixture.
*/
use Drupal\Core\Database\Database;
$connection = Database::getConnection();
// Add a layout plugin to an existing entity view display without explicitly
// enabling Layout Builder for this display.
$display = $connection->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();
<?php
/**
* @file
* Test fixture.
*/
use Drupal\Core\Database\Database;
$connection = Database::getConnection();
// Enable Layout Builder on an existing entity view display.
$display = $connection->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();
...@@ -9,6 +9,17 @@ ...@@ -9,6 +9,17 @@
$connection = Database::getConnection(); $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. // Update core.extension.
$extensions = $connection->select('config') $extensions = $connection->select('config')
->fields('config', ['data']) ->fields('config', ['data'])
......
...@@ -80,6 +80,10 @@ public function testLayoutBuilderUi() { ...@@ -80,6 +80,10 @@ public function testLayoutBuilderUi() {
// From the manage display page, go to manage the layout. // From the manage display page, go to manage the layout.
$this->drupalGet("$field_ui_prefix/display/default"); $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'); $assert_session->linkExists('Manage layout');
$this->clickLink('Manage layout'); $this->clickLink('Manage layout');
$assert_session->addressEquals("$field_ui_prefix/display-layout/default"); $assert_session->addressEquals("$field_ui_prefix/display-layout/default");
...@@ -153,6 +157,7 @@ public function testLayoutBuilderUi() { ...@@ -153,6 +157,7 @@ public function testLayoutBuilderUi() {
// Assert that overrides cannot be turned off while overrides exist. // Assert that overrides cannot be turned off while overrides exist.
$this->drupalGet("$field_ui_prefix/display/default"); $this->drupalGet("$field_ui_prefix/display/default");
$assert_session->checkboxChecked('layout[allow_custom]');
$assert_session->fieldDisabled('layout[allow_custom]'); $assert_session->fieldDisabled('layout[allow_custom]');
// Alter the defaults. // Alter the defaults.
...@@ -243,7 +248,9 @@ public function testPluginDependencies() { ...@@ -243,7 +248,9 @@ public function testPluginDependencies() {
$page->fillField('id', 'myothermenu'); $page->fillField('id', 'myothermenu');
$page->pressButton('Save'); $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'); $assert_session->linkExists('Add Section');
$this->clickLink('Add Section'); $this->clickLink('Add Section');
$assert_session->linkExists('Layout plugin (with dependencies)'); $assert_session->linkExists('Layout plugin (with dependencies)');
...@@ -305,6 +312,7 @@ public function testLayoutBuilderUiFullViewMode() { ...@@ -305,6 +312,7 @@ public function testLayoutBuilderUiFullViewMode() {
$field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field'; $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
// Allow overrides for 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->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
// Customize the default view mode. // Customize the default view mode.
...@@ -365,7 +373,8 @@ public function testLayoutBuilderChooseBlocksAlter() { ...@@ -365,7 +373,8 @@ public function testLayoutBuilderChooseBlocksAlter() {
])); ]));
// From the manage display page, go to manage the layout. // 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'); $this->clickLink('Manage layout');
// Add a new block. // Add a new block.
...@@ -412,6 +421,7 @@ public function testDeletedView() { ...@@ -412,6 +421,7 @@ public function testDeletedView() {
$field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field'; $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
// Enable overrides. // 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->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
$this->drupalGet('node/1'); $this->drupalGet('node/1');
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment