diff --git a/core/modules/layout_builder/layout_builder.install b/core/modules/layout_builder/layout_builder.install index 4a02f2380b..48ce49fce9 100644 --- a/core/modules/layout_builder/layout_builder.install +++ b/core/modules/layout_builder/layout_builder.install @@ -8,42 +8,27 @@ use Drupal\Core\Cache\Cache; use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay; use Drupal\layout_builder\Section; -use Drupal\layout_builder\SectionComponent; /** * Implements hook_install(). */ function layout_builder_install() { - $uuid_generator = \Drupal::service('uuid'); - $entity_field_manager = \Drupal::service('entity_field.manager'); - $displays = LayoutBuilderEntityViewDisplay::loadMultiple(); /** @var \Drupal\layout_builder\Entity\LayoutEntityDisplayInterface[] $displays */ foreach ($displays as $display) { + $components = []; + foreach ($display->get('content') as $name => $component) { + if ($new_component = _layout_builder_create_section_component_from_options($display->getTargetEntityTypeId(), $display->getTargetBundle(), $name, $component)) { + $components[] = $new_component; + } + } + // Create the first section from any existing Field Layout settings. $field_layout = $display->getThirdPartySettings('field_layout') + [ 'id' => 'layout_onecol', 'settings' => [], ]; - /** @var \Drupal\Core\Field\FieldDefinitionInterface[] $field_definitions */ - $field_definitions = $entity_field_manager->getFieldDefinitions($display->getTargetEntityTypeId(), $display->getTargetBundle()); - - $components = []; - foreach ($display->getComponents() as $name => $component) { - if (isset($field_definitions[$name]) && $field_definitions[$name]->isDisplayConfigurable('view') && isset($component['type'])) { - $uuid = $uuid_generator->generate(); - $configuration = []; - $configuration['id'] = 'field_block:' . $display->getTargetEntityTypeId() . ':' . $name; - $configuration['label_display'] = FALSE; - $configuration['formatter']['type'] = $component['type']; - $configuration['formatter']['label'] = $component['label']; - $configuration['formatter']['settings'] = $component['settings']; - $configuration['formatter']['third_party_settings'] = $component['third_party_settings']; - $configuration['context_mapping']['entity'] = 'layout_builder.entity'; - $components[] = (new SectionComponent($uuid, $component['region'], $configuration))->setWeight($component['weight']); - } - } $display ->appendSection(new Section($field_layout['id'], $field_layout['settings'], $components)) ->save(); diff --git a/core/modules/layout_builder/layout_builder.module b/core/modules/layout_builder/layout_builder.module index 0b85ab9909..10be312cf0 100644 --- a/core/modules/layout_builder/layout_builder.module +++ b/core/modules/layout_builder/layout_builder.module @@ -11,6 +11,7 @@ use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay; use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplayStorage; use Drupal\layout_builder\Form\LayoutBuilderEntityViewDisplayForm; +use Drupal\layout_builder\SectionComponent; /** * Implements hook_help(). @@ -62,6 +63,7 @@ function layout_builder_field_config_insert(FieldConfigInterface $field_config) /** @var \Drupal\Core\TempStore\SharedTempStore $tempstore */ $tempstore = \Drupal::service('tempstore.shared')->get('layout_builder.sample_entity'); $tempstore->delete($field_config->getTargetEntityTypeId() . '.' . $field_config->getTargetBundle()); + \Drupal::service('plugin.manager.block')->clearCachedDefinitions(); } /** @@ -72,4 +74,36 @@ function layout_builder_field_config_delete(FieldConfigInterface $field_config) /** @var \Drupal\Core\TempStore\SharedTempStore $tempstore */ $tempstore = \Drupal::service('tempstore.shared')->get('layout_builder.sample_entity'); $tempstore->delete($field_config->getTargetEntityTypeId() . '.' . $field_config->getTargetBundle()); + \Drupal::service('plugin.manager.block')->clearCachedDefinitions(); +} + +/** + * Converts field options into a SectionComponent. + * + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle_id + * The bundle ID. + * @param string $name + * The field name. + * @param array $options + * The array of field options. + * + * @return \Drupal\layout_builder\SectionComponent|null + * The new section component, if one can be created. + */ +function _layout_builder_create_section_component_from_options($entity_type_id, $bundle_id, $name, array $options) { + /** @var \Drupal\Core\Field\FieldDefinitionInterface[] $field_definitions */ + $field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type_id, $bundle_id); + if (isset($field_definitions[$name]) && $field_definitions[$name]->isDisplayConfigurable('view') && isset($options['type'])) { + $configuration = []; + $configuration['id'] = 'field_block:' . $entity_type_id . ':' . $name; + $configuration['label_display'] = FALSE; + $configuration['formatter']['type'] = $options['type']; + $configuration['formatter']['label'] = $options['label']; + $configuration['formatter']['settings'] = $options['settings']; + $configuration['formatter']['third_party_settings'] = $options['third_party_settings']; + $configuration['context_mapping']['entity'] = 'layout_builder.entity'; + return (new SectionComponent(\Drupal::service('uuid')->generate(), $options['region'], $configuration))->setWeight($options['weight']); + } } diff --git a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php index faa4421d1a..246fb521ac 100644 --- a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php +++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php @@ -52,7 +52,7 @@ public function getSections() { * @return $this */ protected function setSections(array $sections) { - $this->setThirdPartySetting('layout_builder', 'sections', $sections); + $this->setThirdPartySetting('layout_builder', 'sections', array_values($sections)); return $this; } @@ -67,11 +67,11 @@ public function count() { * {@inheritdoc} */ public function getSection($delta) { - $sections = $this->getSections(); - if (!isset($sections[$delta])) { + if (!$this->hasSection($delta)) { throw new \OutOfBoundsException(sprintf('Invalid delta "%s" for the "%s" entity', $delta, $this->id())); } - return $sections[$delta]; + + return $this->getSections()[$delta]; } /** @@ -105,8 +105,8 @@ public function appendSection(Section $section) { * {@inheritdoc} */ public function insertSection($delta, Section $section) { - $sections = $this->getSections(); - if (isset($sections[$delta])) { + if ($this->hasSection($delta)) { + $sections = $this->getSections(); // @todo Use https://www.drupal.org/node/66183 once resolved. $start = array_slice($sections, 0, $delta); $end = array_slice($sections, $delta); @@ -124,10 +124,24 @@ public function insertSection($delta, Section $section) { public function removeSection($delta) { $sections = $this->getSections(); unset($sections[$delta]); - $this->setSections(array_values($sections)); + $this->setSections($sections); return $this; } + /** + * Indicates if there is a section at the specified delta. + * + * @param int $delta + * The delta of the section. + * + * @return bool + * TRUE if there is a section for this delta, FALSE otherwise. + */ + protected function hasSection($delta) { + $sections = $this->getSections(); + return isset($sections[$delta]); + } + /** * {@inheritdoc} */ @@ -193,13 +207,12 @@ protected function addSectionField($entity_type_id, $bundle, $field_name) { /** * {@inheritdoc} */ - protected function getDefaultRegion($delta = 0) { - $sections = $this->getSections(); - if (!isset($sections[$delta])) { + protected function getDefaultRegion() { + if (!$this->hasSection(0)) { return parent::getDefaultRegion(); } - $section = $this->getSection($delta); + $section = $this->getSection(0); return $this->getLayoutDefinition($section->getLayoutId())->getDefaultRegion(); } @@ -247,7 +260,7 @@ public function buildMultiple(array $entities) { if ($sections) { foreach ($build_list[$id] as $name => $build_part) { $field_definition = $this->getFieldDefinition($name); - if ($field_definition && $field_definition->isDisplayConfigurable('view')) { + if ($field_definition && $field_definition->isDisplayConfigurable($this->displayContext)) { unset($build_list[$id][$name]); } } @@ -383,4 +396,29 @@ protected function getRouteParameters() { return $route_parameters; } + /** + * {@inheritdoc} + */ + public function setComponent($name, array $options = []) { + parent::setComponent($name, $options); + + // @todo Remove workaround for EntityViewBuilder::getSingleFieldDisplay() in + // https://www.drupal.org/project/drupal/issues/2936464. + if ($this->isNew()) { + return $this; + } + + if ($new_component = _layout_builder_create_section_component_from_options($this->getTargetEntityTypeId(), $this->getTargetBundle(), $name, $this->content[$name])) { + if (!$this->hasSection(0)) { + $section = new Section('layout_onecol'); + $this->appendSection($section); + } + else { + $section = $this->getSection(0); + } + $section->appendComponent($new_component); + } + return $this; + } + } diff --git a/core/modules/layout_builder/src/Form/LayoutBuilderEntityViewDisplayForm.php b/core/modules/layout_builder/src/Form/LayoutBuilderEntityViewDisplayForm.php index e99ce1edbc..c68f2b77c5 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\Field\FieldDefinitionInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\field_ui\Form\EntityViewDisplayEditForm; use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface; @@ -28,6 +29,8 @@ public function form(array $form, FormStateInterface $form_state) { // Hide the table of fields. $form['fields']['#access'] = FALSE; + $form['#fields'] = []; + $form['#extra'] = []; $form['manage_layout'] = [ '#type' => 'link', @@ -71,4 +74,18 @@ public function entityFormEntityBuild($entity_type_id, LayoutEntityDisplayInterf $display->setOverridable($new_value); } + /** + * {@inheritdoc} + */ + protected function buildFieldRow(FieldDefinitionInterface $field_definition, array $form, FormStateInterface $form_state) { + // Intentionally empty. + } + + /** + * {@inheritdoc} + */ + protected function buildExtraFieldRow($field_id, $extra_field) { + // Intentionally empty. + } + } diff --git a/core/modules/layout_builder/src/Section.php b/core/modules/layout_builder/src/Section.php index e183fb6713..20e9dec579 100644 --- a/core/modules/layout_builder/src/Section.php +++ b/core/modules/layout_builder/src/Section.php @@ -73,20 +73,14 @@ public function __construct($layout_id, array $layout_settings = [], array $comp * A renderable array representing the content of the section. */ public function toRenderArray(array $contexts = []) { - $layout = $this->getLayout(); - - // @todo Add the regions to the $build in the correct order. This is done - // for parity with \Drupal\field_layout\FieldLayoutBuilder::buildView(), - // which incorrectly adds all regions to the build. - $regions = array_fill_keys($layout->getPluginDefinition()->getRegionNames(), []); - + $regions = []; foreach ($this->getComponents() as $component) { if ($output = $component->toRenderArray($contexts)) { $regions[$component->getRegion()][$component->getUuid()] = $output; } } - return $layout->build($regions); + return $this->getLayout()->build($regions); } /** diff --git a/core/modules/layout_builder/src/SectionStorageInterface.php b/core/modules/layout_builder/src/SectionStorageInterface.php index 13217d82b4..b564793458 100644 --- a/core/modules/layout_builder/src/SectionStorageInterface.php +++ b/core/modules/layout_builder/src/SectionStorageInterface.php @@ -18,7 +18,7 @@ * Gets the layout sections. * * @return \Drupal\layout_builder\Section[] - * An array of sections. + * An sequentially and numerically keyed array of section objects. */ public function getSections(); @@ -61,6 +61,8 @@ public function insertSection($delta, Section $section); /** * Removes the section at the given delta. * + * This will re-key every subsequent section. + * * @param int $delta * The delta of the section. * diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php index 3873af81b0..59ce3535db 100644 --- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderInstallTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\layout_builder\Kernel; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; use Drupal\layout_builder\Section; /** @@ -49,6 +51,40 @@ public function testCompatibility() { $this->entity->get('layout_builder__layout')->removeSection(0); $this->entity->save(); $this->assertFieldAttributes($this->entity, $expected_fields); + + // Test that adding a new field after Layout Builder has been installed will + // add the new field to the default region of the first section. + $field_storage = FieldStorageConfig::create([ + 'entity_type' => 'entity_test_base_field_display', + 'field_name' => 'test_field_display_post_install', + 'type' => 'text', + ]); + $field_storage->save(); + FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => 'entity_test_base_field_display', + 'label' => 'FieldConfig with configurable display', + ])->save(); + + $this->entity = $this->reloadEntity($this->entity); + $this->entity->test_field_display_post_install = 'Test string'; + $this->entity->save(); + + $this->display = $this->reloadEntity($this->display); + $this->display + ->setComponent('test_field_display_post_install', ['region' => 'content', 'weight' => 50]) + ->save(); + $new_expected_fields = [ + 'field field--name-name field--type-string field--label-hidden field__item', + 'field field--name-test-field-display-configurable field--type-boolean field--label-above', + 'clearfix text-formatted field field--name-test-display-configurable field--type-text field--label-above', + 'clearfix text-formatted field field--name-test-field-display-post-install field--type-text field--label-above', + 'clearfix text-formatted field field--name-test-display-non-configurable field--type-text field--label-above', + 'clearfix text-formatted field field--name-test-display-multiple field--type-text field--label-above', + ]; + $this->assertFieldAttributes($this->entity, $new_expected_fields); + $this->assertNotEmpty($this->cssSelect('.layout--onecol')); + $this->assertText('Test string'); } }