diff --git a/core/composer.json b/core/composer.json
index 53ab8fa..862b5a3 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -94,6 +94,7 @@
"drupal/editor": "self.version",
"drupal/entity_reference": "self.version",
"drupal/field": "self.version",
+ "drupal/field_layout": "self.version",
"drupal/field_ui": "self.version",
"drupal/file": "self.version",
"drupal/filter": "self.version",
diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml
index bf0e12d..711ca0f 100644
--- a/core/config/schema/core.entity.schema.yml
+++ b/core/config/schema/core.entity.schema.yml
@@ -64,6 +64,9 @@ core.entity_view_display.*.*.*:
weight:
type: integer
label: 'Weight'
+ region:
+ type: string
+ label: 'Region'
label:
type: string
label: 'Label setting machine name'
@@ -115,6 +118,9 @@ core.entity_form_display.*.*.*:
weight:
type: integer
label: 'Weight'
+ region:
+ type: string
+ label: 'Region'
settings:
type: field.widget.settings.[%parent.type]
label: 'Settings'
diff --git a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php
index 4de891a..0835aa0 100644
--- a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php
@@ -154,6 +154,7 @@ public function __construct(array $values, $entity_type) {
protected function init() {
// Only populate defaults for "official" view modes and form modes.
if ($this->mode !== static::CUSTOM_MODE) {
+ $default_region = $this->getDefaultRegion();
// Fill in defaults for extra fields.
$context = $this->displayContext == 'view' ? 'display' : $this->displayContext;
$extra_fields = \Drupal::entityManager()->getExtraFields($this->targetEntityType, $this->bundle);
@@ -163,6 +164,8 @@ protected function init() {
// Extra fields are visible by default unless they explicitly say so.
if (!isset($definition['visible']) || $definition['visible'] == TRUE) {
$this->content[$name] = array(
+ 'type' => 'visible',
+ 'region' => $default_region,
'weight' => $definition['weight']
);
}
@@ -178,10 +181,13 @@ protected function init() {
if (!$definition->isDisplayConfigurable($this->displayContext) || (!isset($this->content[$name]) && !isset($this->hidden[$name]))) {
$options = $definition->getDisplayOptions($this->displayContext);
- if (!empty($options['type']) && $options['type'] == 'hidden') {
+ // Check if either 'type' or 'region' is set to hidden.
+ // @todo Remove handling of 'type' in https://www.drupal.org/node/2799641.
+ if ((!empty($options['type']) && $options['type'] === 'hidden') || (!empty($options['region']) && $options['region'] === 'hidden')) {
$this->hidden[$name] = TRUE;
}
elseif ($options) {
+ $options += ['region' => $default_region];
$this->content[$name] = $this->pluginManager->prepareConfiguration($definition->getType(), $options);
}
// Note: (base) fields that do not specify display options are not
@@ -334,6 +340,12 @@ public function setComponent($name, array $options = array()) {
// Ensure we always have an empty settings and array.
$options += ['settings' => [], 'third_party_settings' => []];
+ // Ensure that a region is set.
+ // @todo Make 'region' required in https://www.drupal.org/node/2799641.
+ if (!isset($options['region'])) {
+ $options['region'] = (isset($options['type']) && $options['type'] === 'hidden') ? 'hidden' : $this->getDefaultRegion();
+ }
+
$this->content[$name] = $options;
unset($this->hidden[$name]);
unset($this->plugins[$name]);
@@ -505,6 +517,16 @@ protected function getPluginRemovedDependencies(array $plugin_dependencies, arra
}
/**
+ * Gets the default region.
+ *
+ * @return string
+ * The default region for this display.
+ */
+ protected function getDefaultRegion() {
+ return 'content';
+ }
+
+ /**
* {@inheritdoc}
*/
public function __sleep() {
diff --git a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
index 594f31d..dd96fc9 100644
--- a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
+++ b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php
@@ -414,7 +414,8 @@ public function setDisplayOptions($display_context, array $options) {
public function setDisplayConfigurable($display_context, $configurable) {
// If no explicit display options have been specified, default to 'hidden'.
if (empty($this->definition['display'][$display_context])) {
- $this->definition['display'][$display_context]['options'] = array('type' => 'hidden');
+ // @todo Remove handling of 'type' in https://www.drupal.org/node/2799641.
+ $this->definition['display'][$display_context]['options'] = array('type' => 'hidden', 'region' => 'hidden');
}
$this->definition['display'][$display_context]['configurable'] = $configurable;
return $this;
diff --git a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.default.yml b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.default.yml
index e3c23cf..e0232cf 100644
--- a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.default.yml
+++ b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.default.yml
@@ -11,20 +11,26 @@ content:
checked:
type: timestamp_ago
weight: 1
+ region: content
settings: { }
third_party_settings: { }
label: inline
description:
weight: 3
+ region: content
feed_icon:
weight: 5
+ region: content
image:
weight: 2
+ region: content
items:
weight: 0
+ region: content
link:
type: uri_link
weight: 4
+ region: content
settings: { }
third_party_settings: { }
label: inline
diff --git a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.summary.yml b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.summary.yml
index 40425f2..5e5e468 100644
--- a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.summary.yml
+++ b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_feed.aggregator_feed.summary.yml
@@ -12,8 +12,10 @@ mode: summary
content:
items:
weight: 0
+ region: content
more_link:
weight: 1
+ region: content
hidden:
checked: true
description: true
diff --git a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_item.aggregator_item.summary.yml b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_item.aggregator_item.summary.yml
index 837bee0..8e29395 100644
--- a/core/modules/aggregator/config/install/core.entity_view_display.aggregator_item.aggregator_item.summary.yml
+++ b/core/modules/aggregator/config/install/core.entity_view_display.aggregator_item.aggregator_item.summary.yml
@@ -12,6 +12,7 @@ mode: summary
content:
timestamp:
weight: 0
+ region: content
hidden:
author: true
description: true
diff --git a/core/modules/book/config/install/core.entity_form_display.node.book.default.yml b/core/modules/book/config/install/core.entity_form_display.node.book.default.yml
index 1ec4eb1..58aba45 100644
--- a/core/modules/book/config/install/core.entity_form_display.node.book.default.yml
+++ b/core/modules/book/config/install/core.entity_form_display.node.book.default.yml
@@ -14,6 +14,7 @@ content:
body:
type: text_textarea_with_summary
weight: 26
+ region: content
settings:
rows: 9
summary_rows: 3
@@ -22,6 +23,7 @@ content:
created:
type: datetime_timestamp
weight: 10
+ region: content
settings: { }
third_party_settings: { }
promote:
@@ -29,16 +31,19 @@ content:
settings:
display_label: true
weight: 15
+ region: content
third_party_settings: { }
sticky:
type: boolean_checkbox
settings:
display_label: true
weight: 16
+ region: content
third_party_settings: { }
title:
type: string_textfield
weight: -5
+ region: content
settings:
size: 60
placeholder: ''
@@ -46,6 +51,7 @@ content:
uid:
type: entity_reference_autocomplete
weight: 5
+ region: content
settings:
match_operator: CONTAINS
size: 60
diff --git a/core/modules/book/config/install/core.entity_view_display.node.book.default.yml b/core/modules/book/config/install/core.entity_view_display.node.book.default.yml
index 729516e..d6ef64d 100644
--- a/core/modules/book/config/install/core.entity_view_display.node.book.default.yml
+++ b/core/modules/book/config/install/core.entity_view_display.node.book.default.yml
@@ -16,8 +16,10 @@ content:
label: hidden
type: text_default
weight: 100
+ region: content
settings: { }
third_party_settings: { }
links:
weight: 101
+ region: content
hidden: { }
diff --git a/core/modules/book/config/install/core.entity_view_display.node.book.teaser.yml b/core/modules/book/config/install/core.entity_view_display.node.book.teaser.yml
index fb22db6..77a62c3 100644
--- a/core/modules/book/config/install/core.entity_view_display.node.book.teaser.yml
+++ b/core/modules/book/config/install/core.entity_view_display.node.book.teaser.yml
@@ -17,9 +17,11 @@ content:
label: hidden
type: text_summary_or_trimmed
weight: 100
+ region: content
settings:
trim_length: 600
third_party_settings: { }
links:
weight: 101
+ region: content
hidden: { }
diff --git a/core/modules/field/src/Entity/FieldConfig.php b/core/modules/field/src/Entity/FieldConfig.php
index d7ccc30..1b79b6b 100644
--- a/core/modules/field/src/Entity/FieldConfig.php
+++ b/core/modules/field/src/Entity/FieldConfig.php
@@ -306,7 +306,8 @@ public function isDisplayConfigurable($context) {
*/
public function getDisplayOptions($display_context) {
// Hide configurable fields by default.
- return array('type' => 'hidden');
+ // @todo Remove handling of 'type' in https://www.drupal.org/node/2799641.
+ return array('type' => 'hidden', 'region' => 'hidden');
}
/**
diff --git a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php
index 4c9cbf1..2ee3c77 100644
--- a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php
+++ b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php
@@ -32,6 +32,7 @@ public function testEntityDisplaySettings() {
'type' => 'text_trimmed',
'settings' => array('trim_length' => 600),
'third_party_settings' => array(),
+ 'region' => 'content',
);
// Can we load any entity display.
diff --git a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php
index a16040f..e7bb636 100644
--- a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php
+++ b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php
@@ -33,6 +33,7 @@ public function testWidgetSettings() {
$expected = array('weight' => 1, 'type' => 'text_textfield');
$expected['settings'] = array('size' => 60, 'placeholder' => '');
$expected['third_party_settings'] = array();
+ $expected['region'] = 'content';
$this->assertIdentical($expected, $component, 'Text field settings are correct.');
// Integer field.
diff --git a/core/modules/field_layout/config/schema/field_layout.schema.yml b/core/modules/field_layout/config/schema/field_layout.schema.yml
new file mode 100644
index 0000000..3460b05
--- /dev/null
+++ b/core/modules/field_layout/config/schema/field_layout.schema.yml
@@ -0,0 +1,13 @@
+core.entity_view_display.*.*.*.third_party.field_layout:
+ type: field_layout.third_party_settings
+
+core.entity_form_display.*.*.*.third_party.field_layout:
+ type: field_layout.third_party_settings
+
+field_layout.third_party_settings:
+ type: mapping
+ label: 'Per-view mode field layout settings'
+ mapping:
+ layout:
+ type: string
+ label: 'Layout'
diff --git a/core/modules/field_layout/css/twocol.layout.css b/core/modules/field_layout/css/twocol.layout.css
new file mode 100644
index 0000000..6803d5b
--- /dev/null
+++ b/core/modules/field_layout/css/twocol.layout.css
@@ -0,0 +1,8 @@
+.field-layout--twocol {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+}
+.field-layout--twocol > .field-layout-region {
+ flex: 0 1 50%;
+}
diff --git a/core/modules/field_layout/field_layout.info.yml b/core/modules/field_layout/field_layout.info.yml
new file mode 100644
index 0000000..62f225a
--- /dev/null
+++ b/core/modules/field_layout/field_layout.info.yml
@@ -0,0 +1,6 @@
+name: 'Field Layout'
+type: module
+description: 'Adds layout capabilities to the Field UI.'
+package: Core (Experimental)
+version: VERSION
+core: 8.x
diff --git a/core/modules/field_layout/field_layout.install b/core/modules/field_layout/field_layout.install
new file mode 100644
index 0000000..456054e
--- /dev/null
+++ b/core/modules/field_layout/field_layout.install
@@ -0,0 +1,26 @@
+save();
+ };
+ array_map($entity_save, EntityViewDisplay::loadMultiple());
+ array_map($entity_save, EntityFormDisplay::loadMultiple());
+
+ // Invalidate the render cache since all content will now have a layout.
+ Cache::invalidateTags(['rendered']);
+}
diff --git a/core/modules/field_layout/field_layout.libraries.yml b/core/modules/field_layout/field_layout.libraries.yml
new file mode 100644
index 0000000..9798509
--- /dev/null
+++ b/core/modules/field_layout/field_layout.libraries.yml
@@ -0,0 +1,5 @@
+drupal.field_layout.twocol:
+ version: VERSION
+ css:
+ layout:
+ css/twocol.layout.css: {}
diff --git a/core/modules/field_layout/field_layout.module b/core/modules/field_layout/field_layout.module
new file mode 100644
index 0000000..0f7b16f
--- /dev/null
+++ b/core/modules/field_layout/field_layout.module
@@ -0,0 +1,181 @@
+' . t('About') . '';
+ $output .= '
' . t('The Field Layout module allows you to arrange fields into regions for content forms and displays.') . '
';
+ $output .= '.
+ * - region: The name of the region variable.
+ *
+ * @ingroup themeable
+ */
+#}
+{%
+set classes = [
+'field-layout-region',
+'field-layout-region--' ~ region|clean_class,
+]
+%}
+{% if content %}
+
+ {{ content }}
+
+{% endif %}
diff --git a/core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.info.yml b/core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.info.yml
new file mode 100644
index 0000000..4d699e4
--- /dev/null
+++ b/core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.info.yml
@@ -0,0 +1,8 @@
+name: 'Field Layout test'
+type: module
+description: 'Support module for Field Layout tests.'
+core: 8.x
+package: Testing
+version: VERSION
+dependencies:
+ - entity_test
diff --git a/core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.routing.yml b/core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.routing.yml
new file mode 100644
index 0000000..bcea288
--- /dev/null
+++ b/core/modules/field_layout/tests/modules/field_layout_test/field_layout_test.routing.yml
@@ -0,0 +1,7 @@
+entity.entity_test.test_view_mode:
+ path: '/entity_test/{entity_test}/test'
+ defaults:
+ _entity_view: 'entity_test.test'
+ _title: 'Test test view mode'
+ requirements:
+ _entity_access: 'entity_test.view'
diff --git a/core/modules/field_layout/tests/src/FunctionalJavascript/FieldLayoutTest.php b/core/modules/field_layout/tests/src/FunctionalJavascript/FieldLayoutTest.php
new file mode 100644
index 0000000..04d0810
--- /dev/null
+++ b/core/modules/field_layout/tests/src/FunctionalJavascript/FieldLayoutTest.php
@@ -0,0 +1,253 @@
+createContentType([
+ 'type' => 'article',
+ ]);
+ $this->createNode([
+ 'type' => 'article',
+ 'title' => 'The node title',
+ 'body' => [[
+ 'value' => 'The node body',
+ ]],
+ ]);
+ $entity = EntityTest::create([
+ 'name' => 'The name for this entity',
+ 'field_test_text' => [[
+ 'value' => 'The field test text value',
+ ]],
+ ]);
+ $entity->save();
+ $this->drupalLogin($this->drupalCreateUser([
+ 'access administration pages',
+ 'administer content types',
+ 'administer nodes',
+ 'administer node fields',
+ 'administer node display',
+ 'administer node form display',
+ 'view test entity',
+ 'administer entity_test content',
+ 'administer entity_test fields',
+ 'administer entity_test display',
+ 'administer entity_test form display',
+ 'view the administration theme',
+ ]));
+ }
+
+ /**
+ * Tests that layouts are unique per-view mode.
+ */
+ public function testEntityViewModes() {
+ // By default, the field is not visible.
+ $this->drupalGet('entity_test/1/test');
+ $this->assertSession()->elementNotExists('css', '.field-layout-region--content .field--name-field-test-text');
+ $this->drupalGet('entity_test/1');
+ $this->assertSession()->elementNotExists('css', '.field-layout-region--content .field--name-field-test-text');
+
+ // Change the layout for the "test" view mode. See
+ // core.entity_view_mode.entity_test.test.yml.
+ $this->drupalGet('entity_test/structure/entity_test/display');
+ $this->click('#edit-modes');
+ $this->getSession()->getPage()->checkField('display_modes_custom[test]');
+ $this->submitForm([], 'Save');
+ $this->clickLink('configure them');
+ $this->getSession()->getPage()->pressButton('Show row weights');
+ $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'content');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $this->submitForm([], 'Save');
+
+ // Each view mode has a different layout.
+ $this->drupalGet('entity_test/1/test');
+ $this->assertSession()->elementExists('css', '.field-layout-region--content .field--name-field-test-text');
+ $this->drupalGet('entity_test/1');
+ $this->assertSession()->elementNotExists('css', '.field-layout-region--content .field--name-field-test-text');
+ }
+
+ /**
+ * Tests the use of field layout for entity form displays.
+ */
+ public function testEntityForm() {
+ // By default, the default layout is used.
+ $this->drupalGet('entity_test/manage/1/edit');
+ $this->assertFieldInRegion('field_test_text[0][value]', 'content');
+
+ // The default layout is in use.
+ $this->drupalGet('entity_test/structure/entity_test/form-display');
+ $this->assertEquals(['Content', 'Disabled'], $this->getRegionTitles());
+
+ // Switch the layout to two columns.
+ $this->click('#edit-field-layouts');
+ $this->getSession()->getPage()->selectFieldOption('field_layout', 'twocol');
+ $this->submitForm([], 'Save');
+
+ // The field is moved to the default region for the new layout.
+ $this->assertSession()->pageTextContains('Your settings have been saved.');
+ $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles());
+
+ $this->drupalGet('entity_test/manage/1/edit');
+ // No fields are visible, and the regions don't display when empty.
+ $this->assertFieldInRegion('field_test_text[0][value]', 'left');
+ $this->assertSession()->elementExists('css', '.field-layout-region--left .field--name-field-test-text');
+
+ // After a refresh the new regions are still there.
+ $this->drupalGet('entity_test/structure/entity_test/form-display');
+ $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles());
+
+ // Drag the field to the right region.
+ $field_test_text_row = $this->getSession()->getPage()->find('css', '#field-test-text');
+ $left_region_row = $this->getSession()->getPage()->find('css', '.region-right-message');
+ $field_test_text_row->find('css', '.handle')->dragTo($left_region_row);
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $this->submitForm([], 'Save');
+ $this->assertSession()->pageTextContains('Your settings have been saved.');
+
+ // The new layout is used.
+ $this->drupalGet('entity_test/manage/1/edit');
+ $this->assertSession()->elementExists('css', '.field-layout-region--right .field--name-field-test-text');
+ $this->assertFieldInRegion('field_test_text[0][value]', 'right');
+
+ // Move the field to the right region without tabledrag.
+ $this->drupalGet('entity_test/structure/entity_test/form-display');
+ $this->getSession()->getPage()->pressButton('Show row weights');
+ $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'right');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $this->submitForm([], 'Save');
+ $this->assertSession()->pageTextContains('Your settings have been saved.');
+
+ // The updated region is used.
+ $this->drupalGet('entity_test/manage/1/edit');
+ $this->assertFieldInRegion('field_test_text[0][value]', 'right');
+
+ // The layout is still in use without Field UI.
+ $this->container->get('module_installer')->uninstall(['field_ui']);
+ $this->drupalGet('entity_test/manage/1/edit');
+ $this->assertFieldInRegion('field_test_text[0][value]', 'right');
+ }
+
+ /**
+ * Tests the use of field layout for entity view displays.
+ */
+ public function testEntityView() {
+ // The default layout is in use.
+ $this->drupalGet('entity_test/structure/entity_test/display');
+ $this->assertEquals(['Content', 'Disabled'], $this->getRegionTitles());
+
+ // Switch the layout to two columns.
+ $this->click('#edit-field-layouts');
+ $this->getSession()->getPage()->selectFieldOption('field_layout', 'twocol');
+ $this->submitForm([], 'Save');
+
+ $this->assertSession()->pageTextContains('Your settings have been saved.');
+ $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles());
+
+ $this->drupalGet('entity_test/1');
+ // No fields are visible, and the regions don't display when empty.
+ $this->assertSession()->elementNotExists('css', '.field-layout--twocol');
+ $this->assertSession()->elementNotExists('css', '.field-layout-region');
+ $this->assertSession()->elementNotExists('css', '.field--name-field-test-text');
+
+ // After a refresh the new regions are still there.
+ $this->drupalGet('entity_test/structure/entity_test/display');
+ $this->assertEquals(['Left', 'Right', 'Disabled'], $this->getRegionTitles());
+
+ // Drag the field to the left region.
+ $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][type]', 'hidden')->isSelected());
+ $field_test_text_row = $this->getSession()->getPage()->find('css', '#field-test-text');
+ $left_region_row = $this->getSession()->getPage()->find('css', '.region-left-message');
+ $field_test_text_row->find('css', '.handle')->dragTo($left_region_row);
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $this->assertFalse($this->assertSession()->optionExists('fields[field_test_text][type]', 'hidden')->isSelected());
+ $this->submitForm([], 'Save');
+ $this->assertSession()->pageTextContains('Your settings have been saved.');
+
+ // The new layout is used.
+ $this->drupalGet('entity_test/1');
+ $this->assertSession()->elementExists('css', '.field-layout--twocol');
+ $this->assertSession()->elementExists('css', '.field-layout-region--left .field--name-field-test-text');
+
+ // Move the field to the right region without tabledrag.
+ $this->drupalGet('entity_test/structure/entity_test/display');
+ $this->getSession()->getPage()->pressButton('Show row weights');
+ $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'right');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $this->submitForm([], 'Save');
+ $this->assertSession()->pageTextContains('Your settings have been saved.');
+
+ // The updated region is used.
+ $this->drupalGet('entity_test/1');
+ $this->assertSession()->elementExists('css', '.field-layout-region--right .field--name-field-test-text');
+
+ // The layout is still in use without Field UI.
+ $this->container->get('module_installer')->uninstall(['field_ui']);
+ $this->drupalGet('entity_test/1');
+ $this->assertSession()->elementExists('css', '.field-layout--twocol');
+ $this->assertSession()->elementExists('css', '.field-layout-region--right .field--name-field-test-text');
+ }
+
+ /**
+ * Tests an entity type that has fields shown by default.
+ */
+ public function testNodeView() {
+ // By default, the default layout is used.
+ $this->drupalGet('node/1');
+ $this->assertSession()->elementExists('css', '.field-layout--default');
+ $this->assertSession()->elementExists('css', '.field-layout-region--content .field--name-body');
+
+ $this->drupalGet('admin/structure/types/manage/article/display');
+ $this->assertEquals(['Content', 'Disabled'], $this->getRegionTitles());
+ $this->assertSession()->optionExists('fields[body][region]', 'content');
+ }
+
+ /**
+ * Gets the region titles on the page.
+ *
+ * @return string[]
+ * An array of region titles.
+ */
+ protected function getRegionTitles() {
+ $region_titles = [];
+ $region_title_elements = $this->getSession()->getPage()->findAll('css', '.region-title td');
+ /** @var \Behat\Mink\Element\NodeElement[] $region_title_elements */
+ foreach ($region_title_elements as $region_title_element) {
+ $region_titles[] = $region_title_element->getText();
+ }
+ return $region_titles;
+ }
+
+ /**
+ * Asserts that a field exists in a given region.
+ *
+ * @param string $field_selector
+ * The field selector, one of field id|name|label|value.
+ * @param string $region_name
+ * The machine name of the region.
+ */
+ protected function assertFieldInRegion($field_selector, $region_name) {
+ $region_element = $this->getSession()->getPage()->find('css', ".field-layout-region--$region_name");
+ $this->assertNotNull($region_element);
+ $this->assertSession()->fieldExists($field_selector, $region_element);
+ }
+
+}
diff --git a/core/modules/field_layout/tests/src/Unit/LayoutRepositoryTest.php b/core/modules/field_layout/tests/src/Unit/LayoutRepositoryTest.php
new file mode 100644
index 0000000..bf55e11
--- /dev/null
+++ b/core/modules/field_layout/tests/src/Unit/LayoutRepositoryTest.php
@@ -0,0 +1,59 @@
+getStringTranslationStub());
+
+ $expected = ['default', 'twocol'];
+
+ $layout_definitions = $layout_repository->getLayoutDefinitions();
+ $this->assertEquals($expected, array_keys($layout_definitions));
+ }
+
+ /**
+ * @covers ::getLayoutForDisplay
+ */
+ public function testGetLayoutForDisplay() {
+ $layout_repository = new LayoutRepository($this->getStringTranslationStub());
+
+ $display = $this->prophesize(EntityDisplayWithLayoutInterface::class);
+ $display->getLayoutId()->willReturn('twocol');
+
+ $expected = ['left', 'right'];
+
+ $layout_definition = $layout_repository->getLayoutForDisplay($display->reveal());
+ $this->assertSame('twocol', $layout_definition['layout']);
+ $this->assertEquals($expected, array_keys($layout_definition['regions']));
+ }
+
+ /**
+ * @covers ::getLayoutForDisplay
+ */
+ public function testGetLayoutForDisplayEmpty() {
+ $layout_repository = new LayoutRepository($this->getStringTranslationStub());
+
+ $display = $this->prophesize(EntityDisplayWithLayoutInterface::class);
+ $display->getLayoutId()->willReturn(NULL);
+
+ $expected = ['content'];
+
+ $layout_definition = $layout_repository->getLayoutForDisplay($display->reveal());
+ $this->assertSame('default', $layout_definition['layout']);
+ $this->assertEquals($expected, array_keys($layout_definition['regions']));
+ }
+
+}
diff --git a/core/modules/field_ui/field_ui.js b/core/modules/field_ui/field_ui.js
index a53553d..e11a2e8 100644
--- a/core/modules/field_ui/field_ui.js
+++ b/core/modules/field_ui/field_ui.js
@@ -128,9 +128,14 @@
var refreshRows = {};
refreshRows[rowHandler.name] = $trigger.get(0);
- // Handle region change.
+ // Handle region or type change.
var region = rowHandler.getRegion();
- if (region !== rowHandler.region) {
+ // @todo Remove handling of 'type' in https://www.drupal.org/node/2799641.
+ var typeRegion = rowHandler.getType();
+ if (region !== rowHandler.region || typeRegion !== rowHandler.region) {
+ if (region === rowHandler.region) {
+ region = typeRegion;
+ }
// Remove parenting.
$row.find('select.js-field-parent').val('');
// Let the row handler deal with the region change.
@@ -270,6 +275,10 @@ else if ($this.is('.region-empty')) {
this.$pluginSelect = $(row).find('select.field-plugin-type');
this.$pluginSelect.on('change', Drupal.fieldUIOverview.onChange);
+ // Attach change listener to the 'region' select.
+ this.$regionSelect = $(row).find('select.field-region');
+ this.$regionSelect.on('change', Drupal.fieldUIOverview.onChange);
+
return this;
};
@@ -282,6 +291,16 @@ else if ($this.is('.region-empty')) {
* Either 'hidden' or 'content'.
*/
getRegion: function () {
+ return this.$regionSelect.val();
+ },
+
+ /**
+ * Returns the region corresponding to the current form values of the row.
+ *
+ * @returns {string}
+ * Either 'hidden' or 'content'.
+ */
+ getType: function () {
return (this.$pluginSelect.val() === 'hidden') ? 'hidden' : 'content';
},
@@ -305,14 +324,17 @@ else if ($this.is('.region-empty')) {
* {@link Drupal.fieldUIOverview.AJAXRefreshRows}.
*/
regionChange: function (region) {
+ // Replace dashes with underscores.
+ region = region.replace(/-/g, '_');
+
+ // Set the region of the select list.
+ this.$regionSelect.val(region);
// When triggered by a row drag, the 'format' select needs to be adjusted
// to the new region.
var currentValue = this.$pluginSelect.val();
var value;
- // @TODO Check if this couldn't just be like
- // if (region !== 'hidden') {
- if (region === 'content') {
+ if (region !== 'hidden') {
if (currentValue === 'hidden') {
// Restore the formatter back to the default formatter. Pseudo-fields
// do not have default formatters, we just return to 'visible' for
diff --git a/core/modules/field_ui/src/Form/EntityDisplayFormBase.php b/core/modules/field_ui/src/Form/EntityDisplayFormBase.php
index 586f1ef..f4fb39c 100644
--- a/core/modules/field_ui/src/Form/EntityDisplayFormBase.php
+++ b/core/modules/field_ui/src/Form/EntityDisplayFormBase.php
@@ -4,6 +4,7 @@
use Drupal\Component\Plugin\Factory\DefaultFactory;
use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Core\Entity\Display\EntityDisplayInterface;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
@@ -172,6 +173,13 @@ public function form(array $form, FormStateInterface $form_state) {
'subgroup' => 'field-parent',
'source' => 'field-name',
),
+ array(
+ 'action' => 'match',
+ 'relationship' => 'parent',
+ 'group' => 'field-region',
+ 'subgroup' => 'field-region',
+ 'source' => 'field-name',
+ ),
),
);
@@ -309,6 +317,15 @@ protected function buildFieldRow(FieldDefinitionInterface $field_definition, arr
'#attributes' => array('class' => array('field-name')),
),
),
+ 'region' => array(
+ '#type' => 'select',
+ '#title' => $this->t('Region for @title', array('@title' => $label)),
+ '#title_display' => 'invisible',
+ '#options' => $this->getRegionOptions(),
+ '#empty_value' => 'hidden',
+ '#default_value' => isset($display_options['region']) ? $display_options['region'] : 'hidden',
+ '#attributes' => array('class' => array('field-region')),
+ ),
);
$field_row['plugin'] = array(
@@ -474,6 +491,15 @@ protected function buildExtraFieldRow($field_id, $extra_field) {
'#attributes' => array('class' => array('field-name')),
),
),
+ 'region' => array(
+ '#type' => 'select',
+ '#title' => $this->t('Region for @title', array('@title' => $extra_field['label'])),
+ '#title_display' => 'invisible',
+ '#options' => $this->getRegionOptions(),
+ '#empty_value' => 'hidden',
+ '#default_value' => $display_options ? $display_options['region'] : 'hidden',
+ '#attributes' => array('class' => array('field-region')),
+ ),
'plugin' => array(
'type' => array(
'#type' => 'select',
@@ -548,44 +574,119 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form,
// Collect data for 'regular' fields.
foreach ($form['#fields'] as $field_name) {
- $values = $form_values['fields'][$field_name];
+ $this->processFieldUpdates($field_name, $form_values['fields'][$field_name], $entity, $form_state);
+ }
+
+ // Collect data for 'extra' fields.
+ foreach ($form['#extra'] as $name) {
+ $this->processFieldUpdates($name, $form_values['fields'][$name], $entity, $form_state);
+ }
- if ($values['type'] == 'hidden') {
- $entity->removeComponent($field_name);
+ $form_state->setTemporaryValue('entity_display_components_updated', TRUE);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save(array $form, FormStateInterface $form_state) {
+ $form_state->setTemporaryValue('entity_display_components_updated', NULL);
+ return parent::save($form, $form_state);
+ }
+
+ /**
+ * Processes updates to the components for a given field.
+ *
+ * @param string $field_name
+ * The field name being processed.
+ * @param array $values
+ * The submitted form values.
+ * @param \Drupal\Core\Entity\Display\EntityDisplayInterface $entity
+ * The entity being updated.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ */
+ protected function processFieldUpdates($field_name, array $values, EntityDisplayInterface $entity, FormStateInterface $form_state) {
+ // If the component is not found, it is initially hidden.
+ $options = $entity->getComponent($field_name) ?: ['type' => 'hidden', 'region' => 'hidden'];
+ $remove_component = $options['region'] === 'hidden';
+ if ($form_state->getTemporaryValue('entity_display_components_updated')) {
+ // Since the component has already been updated, replace $values with the
+ // relevant parts of $options.
+ $values = array_intersect_key($options, $values) + $values;
+ }
+ // @todo In https://www.drupal.org/node/2799641, remove this else statement.
+ else {
+ $remove_component = $this->determineComponentAction($options, $values);
+ }
+
+ if ($remove_component) {
+ $entity->removeComponent($field_name);
+ }
+ else {
+ // Update field settings only if the submit handler told us to.
+ if ($form_state->get('plugin_settings_update') === $field_name) {
+ // Only store settings actually used by the selected plugin.
+ $default_settings = $this->pluginManager->getDefaultSettings($options['type']);
+ $options['settings'] = isset($values['settings_edit_form']['settings']) ? array_intersect_key($values['settings_edit_form']['settings'], $default_settings) : [];
+ $options['third_party_settings'] = isset($values['settings_edit_form']['third_party_settings']) ? $values['settings_edit_form']['third_party_settings'] : [];
+ $form_state->set('plugin_settings_update', NULL);
}
- else {
- $options = $entity->getComponent($field_name);
-
- // Update field settings only if the submit handler told us to.
- if ($form_state->get('plugin_settings_update') === $field_name) {
- // Only store settings actually used by the selected plugin.
- $default_settings = $this->pluginManager->getDefaultSettings($options['type']);
- $options['settings'] = isset($values['settings_edit_form']['settings']) ? array_intersect_key($values['settings_edit_form']['settings'], $default_settings) : [];
- $options['third_party_settings'] = isset($values['settings_edit_form']['third_party_settings']) ? $values['settings_edit_form']['third_party_settings'] : [];
- $form_state->set('plugin_settings_update', NULL);
- }
+ if (isset($values['type'])) {
$options['type'] = $values['type'];
- $options['weight'] = $values['weight'];
- // Only formatters have configurable label visibility.
- if (isset($values['label'])) {
- $options['label'] = $values['label'];
- }
- $entity->setComponent($field_name, $options);
}
+ $options['weight'] = $values['weight'];
+ if (isset($values['region'])) {
+ $options['region'] = $values['region'];
+ }
+ // Only formatters have configurable label visibility.
+ if (isset($values['label'])) {
+ $options['label'] = $values['label'];
+ }
+ $entity->setComponent($field_name, $options);
}
+ }
- // Collect data for 'extra' fields.
- foreach ($form['#extra'] as $name) {
- if ($form_values['fields'][$name]['type'] == 'hidden') {
- $entity->removeComponent($name);
+ /**
+ * Determines whether a component should be updated or removed.
+ *
+ * @todo Remove handling of 'type' in https://www.drupal.org/node/2799641.
+ *
+ * @param array $old_values
+ * An array of the old values for a given component.
+ * @param array $new_values
+ * An array of the new values for a given component.
+ *
+ * @return bool
+ * TRUE if the component should be removed, FALSE if it should be updated.
+ */
+ protected function determineComponentAction(array &$old_values, array &$new_values) {
+ $has_type_change = $new_values['type'] !== $old_values['type'];
+ $has_region_change = $new_values['region'] !== $old_values['region'];
+ // If the type and region both changed or neither changed, the action will
+ // be the same. Base the decision on whether the region is hidden.
+ if ($has_type_change === $has_region_change) {
+ $remove_component = $new_values['region'] === 'hidden';
+ }
+ else {
+ if ($has_region_change) {
+ // If only the region changed, remove the component if it is now hidden.
+ $remove_component = $new_values['region'] === 'hidden';
+ // If the region and type mismatch, remove the invalid type.
+ if ($new_values['region'] !== 'hidden' && $new_values['type'] === 'hidden') {
+ unset($new_values['type'], $old_values['type']);
+ }
}
else {
- $entity->setComponent($name, array(
- 'weight' => $form_values['fields'][$name]['weight'],
- ));
+ // If only the type changed, remove the component if it is now hidden.
+ $remove_component = $new_values['type'] === 'hidden';
+ // If the region and type mismatch, remove the invalid region.
+ if ($new_values['region'] === 'hidden' && $new_values['type'] !== 'hidden') {
+ unset($new_values['region'], $old_values['region']);
+ }
}
}
+ return $remove_component;
}
/**
@@ -813,7 +914,7 @@ public function getRowRegion($row) {
switch ($row['#row_type']) {
case 'field':
case 'extra_field':
- return ($row['plugin']['type']['#value'] == 'hidden' ? 'hidden' : 'content');
+ return $row['region']['#value'] ?: 'hidden';
}
}
diff --git a/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php b/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php
index 741b98d..af8e2ed 100644
--- a/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php
+++ b/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php
@@ -94,6 +94,7 @@ protected function getTableHeader() {
$this->t('Field'),
$this->t('Weight'),
$this->t('Parent'),
+ $this->t('Region'),
array('data' => $this->t('Widget'), 'colspan' => 3),
);
}
diff --git a/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php b/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php
index f273325..174726f 100644
--- a/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php
+++ b/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php
@@ -127,6 +127,7 @@ protected function getTableHeader() {
$this->t('Field'),
$this->t('Weight'),
$this->t('Parent'),
+ $this->t('Region'),
$this->t('Label'),
array('data' => $this->t('Format'), 'colspan' => 3),
);
diff --git a/core/modules/field_ui/tests/src/FunctionalJavascript/EntityDisplayTest.php b/core/modules/field_ui/tests/src/FunctionalJavascript/EntityDisplayTest.php
new file mode 100644
index 0000000..7cde00c
--- /dev/null
+++ b/core/modules/field_ui/tests/src/FunctionalJavascript/EntityDisplayTest.php
@@ -0,0 +1,95 @@
+ 'The name for this entity',
+ 'field_test_text' => [[
+ 'value' => 'The field test text value',
+ ]],
+ ]);
+ $entity->save();
+ $this->drupalLogin($this->drupalCreateUser([
+ 'access administration pages',
+ 'view test entity',
+ 'administer entity_test content',
+ 'administer entity_test fields',
+ 'administer entity_test display',
+ 'administer entity_test form display',
+ 'view the administration theme',
+ ]));
+ }
+
+ /**
+ * Tests the use of regions for entity form displays.
+ */
+ public function testEntityForm() {
+ $this->drupalGet('entity_test/manage/1/edit');
+ $this->assertSession()->fieldExists('field_test_text[0][value]');
+
+ $this->drupalGet('entity_test/structure/entity_test/form-display');
+ $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'content')->isSelected());
+ $this->assertFalse($this->assertSession()->optionExists('fields[field_test_text][type]', 'hidden')->isSelected());
+
+ $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'hidden');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected());
+ $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][type]', 'hidden')->isSelected());
+
+ $this->submitForm([], 'Save');
+ $this->assertSession()->pageTextContains('Your settings have been saved.');
+ $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected());
+ $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][type]', 'hidden')->isSelected());
+
+ $this->drupalGet('entity_test/manage/1/edit');
+ $this->assertSession()->fieldNotExists('field_test_text[0][value]');
+ }
+
+ /**
+ * Tests the use of regions for entity view displays.
+ */
+ public function testEntityView() {
+ $this->drupalGet('entity_test/1');
+ $this->assertSession()->elementNotExists('css', '.field--name-field-test-text');
+
+ $this->drupalGet('entity_test/structure/entity_test/display');
+ $this->assertSession()->elementExists('css', '.region-content-message.region-empty');
+ $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected());
+ $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][type]', 'hidden')->isSelected());
+
+ $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'content');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'content')->isSelected());
+ $this->assertFalse($this->assertSession()->optionExists('fields[field_test_text][type]', 'hidden')->isSelected());
+
+ $this->submitForm([], 'Save');
+ $this->assertSession()->pageTextContains('Your settings have been saved.');
+ $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'content')->isSelected());
+ $this->assertFalse($this->assertSession()->optionExists('fields[field_test_text][type]', 'hidden')->isSelected());
+
+ $this->drupalGet('entity_test/1');
+ $this->assertSession()->elementExists('css', '.field--name-field-test-text');
+ }
+
+}
diff --git a/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php b/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php
index be188db..756b9a7 100644
--- a/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php
+++ b/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php
@@ -53,15 +53,15 @@ public function testEntityDisplayCRUD() {
// Check that providing no 'weight' results in the highest current weight
// being assigned. The 'name' field's formatter has weight -5, therefore
// these follow.
- $expected['component_1'] = array('weight' => -4, 'settings' => array(), 'third_party_settings' => array());
- $expected['component_2'] = array('weight' => -3, 'settings' => array(), 'third_party_settings' => array());
+ $expected['component_1'] = array('weight' => -4, 'settings' => array(), 'third_party_settings' => array(), 'region' => 'content');
+ $expected['component_2'] = array('weight' => -3, 'settings' => array(), 'third_party_settings' => array(), 'region' => 'content');
$display->setComponent('component_1');
$display->setComponent('component_2');
$this->assertEqual($display->getComponent('component_1'), $expected['component_1']);
$this->assertEqual($display->getComponent('component_2'), $expected['component_2']);
// Check that arbitrary options are correctly stored.
- $expected['component_3'] = array('weight' => 10, 'third_party_settings' => array('field_test' => array('foo' => 'bar')), 'settings' => array());
+ $expected['component_3'] = array('weight' => 10, 'third_party_settings' => array('field_test' => array('foo' => 'bar')), 'settings' => array(), 'region' => 'content');
$display->setComponent('component_3', $expected['component_3']);
$this->assertEqual($display->getComponent('component_3'), $expected['component_3']);
@@ -86,6 +86,7 @@ public function testEntityDisplayCRUD() {
'link_to_entity' => FALSE,
),
'third_party_settings' => array(),
+ 'region' => 'content',
);
$this->assertEqual($display->getComponents(), $expected);
@@ -148,7 +149,7 @@ public function testEntityGetDisplay() {
$display = entity_get_display('entity_test', 'entity_test', 'default');
$this->assertFalse($display->isNew());
$this->assertEqual($display->id(), 'entity_test.entity_test.default');
- $this->assertEqual($display->getComponent('component_1'), array( 'weight' => 10, 'settings' => array(), 'third_party_settings' => array()));
+ $this->assertEqual($display->getComponent('component_1'), array( 'weight' => 10, 'settings' => array(), 'third_party_settings' => array(), 'region' => 'content'));
}
/**
@@ -164,14 +165,14 @@ public function testExtraFieldComponent() {
// Check that the default visibility taken into account for extra fields
// unknown in the display.
- $this->assertEqual($display->getComponent('display_extra_field'), array('weight' => 5));
+ $this->assertEqual($display->getComponent('display_extra_field'), array('weight' => 5, 'type' => 'visible', 'region' => 'content'));
$this->assertNull($display->getComponent('display_extra_field_hidden'));
// Check that setting explicit options overrides the defaults.
$display->removeComponent('display_extra_field');
$display->setComponent('display_extra_field_hidden', array('weight' => 10));
$this->assertNull($display->getComponent('display_extra_field'));
- $this->assertEqual($display->getComponent('display_extra_field_hidden'), array('weight' => 10, 'settings' => array(), 'third_party_settings' => array()));
+ $this->assertEqual($display->getComponent('display_extra_field_hidden'), array('weight' => 10, 'settings' => array(), 'third_party_settings' => array(), 'region' => 'content'));
}
/**
@@ -209,6 +210,7 @@ public function testFieldComponent() {
'type' => $default_formatter,
'settings' => $formatter_settings,
'third_party_settings' => array(),
+ 'region' => 'content',
);
$this->assertEqual($display->getComponent($field_name), $expected);
@@ -258,6 +260,7 @@ public function testBaseFieldComponent() {
'settings' => $formatter_settings,
'third_party_settings' => array(),
'weight' => 10,
+ 'region' => 'content',
),
'test_display_non_configurable' => array(
'label' => 'above',
@@ -265,6 +268,7 @@ public function testBaseFieldComponent() {
'settings' => $formatter_settings,
'third_party_settings' => array(),
'weight' => 11,
+ 'region' => 'content',
),
);
foreach ($expected as $field_name => $options) {
diff --git a/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php b/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php
index e38db7c..fb343fd 100644
--- a/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php
+++ b/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php
@@ -43,7 +43,7 @@ public function testEntityGetFromDisplay() {
$form_display = entity_get_form_display('entity_test', 'entity_test', 'default');
$this->assertFalse($form_display->isNew());
$this->assertEqual($form_display->id(), 'entity_test.entity_test.default');
- $this->assertEqual($form_display->getComponent('component_1'), array('weight' => 10, 'settings' => array(), 'third_party_settings' => array()));
+ $this->assertEqual($form_display->getComponent('component_1'), array('weight' => 10, 'settings' => array(), 'third_party_settings' => array(), 'region' => 'content'));
}
/**
@@ -80,6 +80,7 @@ public function testFieldComponent() {
'type' => $default_widget,
'settings' => $widget_settings,
'third_party_settings' => array(),
+ 'region' => 'content',
);
$this->assertEqual($form_display->getComponent($field_name), $expected);
@@ -134,12 +135,14 @@ public function testBaseFieldComponent() {
'settings' => $formatter_settings,
'third_party_settings' => array(),
'weight' => 10,
+ 'region' => 'content',
),
'test_display_non_configurable' => array(
'type' => 'text_textfield',
'settings' => $formatter_settings,
'third_party_settings' => array(),
'weight' => 11,
+ 'region' => 'content',
),
);
foreach ($expected as $field_name => $options) {
diff --git a/core/modules/forum/config/optional/core.entity_form_display.comment.comment_forum.default.yml b/core/modules/forum/config/optional/core.entity_form_display.comment.comment_forum.default.yml
index a09c30b..4738bbb 100644
--- a/core/modules/forum/config/optional/core.entity_form_display.comment.comment_forum.default.yml
+++ b/core/modules/forum/config/optional/core.entity_form_display.comment.comment_forum.default.yml
@@ -13,9 +13,11 @@ mode: default
content:
author:
weight: -2
+ region: content
comment_body:
type: text_textarea
weight: 11
+ region: content
settings:
rows: 5
placeholder: ''
@@ -23,6 +25,7 @@ content:
subject:
type: string_textfield
weight: 10
+ region: content
settings:
size: 60
placeholder: ''
diff --git a/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml b/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml
index c66ba23..6773d32 100644
--- a/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml
+++ b/core/modules/forum/config/optional/core.entity_form_display.node.forum.default.yml
@@ -17,6 +17,7 @@ content:
body:
type: text_textarea_with_summary
weight: 27
+ region: content
settings:
rows: 9
summary_rows: 3
@@ -25,11 +26,13 @@ content:
comment_forum:
type: comment_default
weight: 20
+ region: content
settings: { }
third_party_settings: { }
created:
type: datetime_timestamp
weight: 10
+ region: content
settings: { }
third_party_settings: { }
promote:
@@ -37,21 +40,25 @@ content:
settings:
display_label: true
weight: 15
+ region: content
third_party_settings: { }
sticky:
type: boolean_checkbox
settings:
display_label: true
weight: 16
+ region: content
third_party_settings: { }
taxonomy_forums:
type: options_select
weight: 26
+ region: content
settings: { }
third_party_settings: { }
title:
type: string_textfield
weight: -5
+ region: content
settings:
size: 60
placeholder: ''
@@ -59,6 +66,7 @@ content:
uid:
type: entity_reference_autocomplete
weight: 5
+ region: content
settings:
match_operator: CONTAINS
size: 60
diff --git a/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml b/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml
index b18c869..50df98a 100644
--- a/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml
+++ b/core/modules/forum/config/optional/core.entity_form_display.taxonomy_term.forums.default.yml
@@ -14,11 +14,13 @@ content:
description:
type: text_textfield
weight: 0
+ region: content
settings: { }
third_party_settings: { }
name:
type: string_textfield
weight: -5
+ region: content
settings:
size: 60
placeholder: ''
diff --git a/core/modules/forum/config/optional/core.entity_view_display.comment.comment_forum.default.yml b/core/modules/forum/config/optional/core.entity_view_display.comment.comment_forum.default.yml
index f4f0112..befeba8 100644
--- a/core/modules/forum/config/optional/core.entity_view_display.comment.comment_forum.default.yml
+++ b/core/modules/forum/config/optional/core.entity_view_display.comment.comment_forum.default.yml
@@ -15,8 +15,10 @@ content:
label: hidden
type: text_default
weight: 0
+ region: content
settings: { }
third_party_settings: { }
links:
weight: 100
+ region: content
hidden: { }
diff --git a/core/modules/forum/config/optional/core.entity_view_display.node.forum.default.yml b/core/modules/forum/config/optional/core.entity_view_display.node.forum.default.yml
index b157c83..f3e8c5c 100644
--- a/core/modules/forum/config/optional/core.entity_view_display.node.forum.default.yml
+++ b/core/modules/forum/config/optional/core.entity_view_display.node.forum.default.yml
@@ -20,21 +20,25 @@ content:
label: hidden
type: text_default
weight: 0
+ region: content
settings: { }
third_party_settings: { }
comment_forum:
label: hidden
type: comment_default
weight: 20
+ region: content
settings:
view_mode: default
pager_id: 0
third_party_settings: { }
links:
weight: 100
+ region: content
taxonomy_forums:
type: entity_reference_label
weight: -1
+ region: content
label: above
settings:
link: true
diff --git a/core/modules/forum/config/optional/core.entity_view_display.node.forum.teaser.yml b/core/modules/forum/config/optional/core.entity_view_display.node.forum.teaser.yml
index 4405e71..7b174f4 100644
--- a/core/modules/forum/config/optional/core.entity_view_display.node.forum.teaser.yml
+++ b/core/modules/forum/config/optional/core.entity_view_display.node.forum.teaser.yml
@@ -19,14 +19,17 @@ content:
label: hidden
type: text_summary_or_trimmed
weight: 100
+ region: content
settings:
trim_length: 600
third_party_settings: { }
links:
weight: 101
+ region: content
taxonomy_forums:
type: entity_reference_label
weight: 10
+ region: content
label: above
settings:
link: true
diff --git a/core/modules/forum/config/optional/core.entity_view_display.taxonomy_term.forums.default.yml b/core/modules/forum/config/optional/core.entity_view_display.taxonomy_term.forums.default.yml
index d1242d9..b326039 100644
--- a/core/modules/forum/config/optional/core.entity_view_display.taxonomy_term.forums.default.yml
+++ b/core/modules/forum/config/optional/core.entity_view_display.taxonomy_term.forums.default.yml
@@ -14,6 +14,7 @@ content:
description:
type: text_default
weight: 0
+ region: content
settings: { }
third_party_settings: { }
label: above
diff --git a/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml b/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml
index 19a2ef7..ff5f0ec 100644
--- a/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml
+++ b/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml
@@ -14,6 +14,7 @@ content:
title:
type: string_textfield
weight: -5
+ region: content
settings:
size: 60
placeholder: ''
@@ -21,6 +22,7 @@ content:
uid:
type: entity_reference_autocomplete
weight: 5
+ region: content
settings:
match_operator: CONTAINS
size: 60
@@ -29,6 +31,7 @@ content:
created:
type: datetime_timestamp
weight: 10
+ region: content
settings: { }
third_party_settings: { }
promote:
@@ -36,16 +39,19 @@ content:
settings:
display_label: true
weight: 15
+ region: content
third_party_settings: { }
sticky:
type: boolean_checkbox
settings:
display_label: true
weight: 16
+ region: content
third_party_settings: { }
body:
type: text_textarea_with_summary
weight: 26
+ region: content
settings:
rows: 9
summary_rows: 3
diff --git a/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml
index c107b10..aaea1cb 100644
--- a/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml
+++ b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml
@@ -14,10 +14,12 @@ mode: default
content:
links:
weight: 100
+ region: content
body:
label: hidden
type: text_default
weight: 101
+ region: content
settings: { }
third_party_settings: { }
hidden:
diff --git a/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml
index 3b472a7..6e79af9 100644
--- a/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml
+++ b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml
@@ -15,10 +15,12 @@ mode: teaser
content:
links:
weight: 100
+ region: content
body:
label: hidden
type: text_summary_or_trimmed
weight: 101
+ region: content
settings:
trim_length: 600
third_party_settings: { }
diff --git a/core/modules/system/src/Tests/Update/UpdateEntityDisplayTest.php b/core/modules/system/src/Tests/Update/UpdateEntityDisplayTest.php
new file mode 100644
index 0000000..0a2cbf4
--- /dev/null
+++ b/core/modules/system/src/Tests/Update/UpdateEntityDisplayTest.php
@@ -0,0 +1,49 @@
+databaseDumpFiles = [
+ __DIR__ . '/../../../tests/fixtures/update/drupal-8.bare.standard.php.gz',
+ ];
+ }
+
+ /**
+ * Tests that entity displays are updated with regions for their fields.
+ */
+ public function testUpdate() {
+ // No region key appears pre-update.
+ $entity_form_display = EntityFormDisplay::load('node.article.default');
+ $options = $entity_form_display->getComponent('body');
+ $this->assertFalse(array_key_exists('region', $options));
+
+ $entity_view_display = EntityViewDisplay::load('node.article.default');
+ $options = $entity_view_display->getComponent('body');
+ $this->assertFalse(array_key_exists('region', $options));
+
+ $this->runUpdates();
+
+ // The region key has been populated with 'content'.
+ $entity_form_display = EntityFormDisplay::load('node.article.default');
+ $options = $entity_form_display->getComponent('body');
+ $this->assertIdentical('content', $options['region']);
+
+ $entity_view_display = EntityViewDisplay::load('node.article.default');
+ $options = $entity_view_display->getComponent('body');
+ $this->assertIdentical('content', $options['region']);
+ }
+
+}
diff --git a/core/modules/system/system.post_update.php b/core/modules/system/system.post_update.php
index b75625c..1bd11a8 100644
--- a/core/modules/system/system.post_update.php
+++ b/core/modules/system/system.post_update.php
@@ -5,6 +5,10 @@
* Post update functions for System.
*/
+use Drupal\Core\Entity\Display\EntityDisplayInterface;
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+
/**
* @addtogroup updates-8.0.0-beta
* @{
@@ -41,3 +45,18 @@ function system_post_update_recalculate_configuration_entity_dependencies(&$sand
/**
* @} End of "addtogroup updates-8.0.0-beta".
*/
+
+/**
+ * Update entity displays to contain the region for each field.
+ */
+function system_post_update_add_region_to_entity_displays() {
+ $entity_save = function (EntityDisplayInterface $entity) {
+ foreach ($entity->getComponents() as $name => $component) {
+ // setComponent() will fill in the correct region based on the 'type'.
+ $entity->setComponent($name, $component);
+ }
+ $entity->save();
+ };
+ array_map($entity_save, EntityViewDisplay::loadMultiple());
+ array_map($entity_save, EntityFormDisplay::loadMultiple());
+}
diff --git a/core/profiles/standard/config/install/core.entity_form_display.block_content.basic.default.yml b/core/profiles/standard/config/install/core.entity_form_display.block_content.basic.default.yml
index ee0c138..7ccb5b0 100644
--- a/core/profiles/standard/config/install/core.entity_form_display.block_content.basic.default.yml
+++ b/core/profiles/standard/config/install/core.entity_form_display.block_content.basic.default.yml
@@ -14,6 +14,7 @@ content:
body:
type: text_textarea_with_summary
weight: -4
+ region: content
settings:
rows: 9
summary_rows: 3
@@ -22,6 +23,7 @@ content:
info:
type: string_textfield
weight: -5
+ region: content
settings:
size: 60
placeholder: ''
diff --git a/core/profiles/standard/config/install/core.entity_form_display.comment.comment.default.yml b/core/profiles/standard/config/install/core.entity_form_display.comment.comment.default.yml
index fa5d834..1010be2 100644
--- a/core/profiles/standard/config/install/core.entity_form_display.comment.comment.default.yml
+++ b/core/profiles/standard/config/install/core.entity_form_display.comment.comment.default.yml
@@ -13,9 +13,11 @@ mode: default
content:
author:
weight: -2
+ region: content
comment_body:
type: text_textarea
weight: 11
+ region: content
settings:
rows: 5
placeholder: ''
@@ -23,6 +25,7 @@ content:
subject:
type: string_textfield
weight: 10
+ region: content
settings:
size: 60
placeholder: ''
diff --git a/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml b/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml
index 79156b2..c94e36e 100644
--- a/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml
+++ b/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml
@@ -21,6 +21,7 @@ content:
body:
type: text_textarea_with_summary
weight: 1
+ region: content
settings:
rows: 9
summary_rows: 3
@@ -29,16 +30,19 @@ content:
comment:
type: comment_default
weight: 20
+ region: content
settings: { }
third_party_settings: { }
created:
type: datetime_timestamp
weight: 10
+ region: content
settings: { }
third_party_settings: { }
field_image:
type: image_image
weight: 4
+ region: content
settings:
progress_indicator: throbber
preview_image_style: thumbnail
@@ -46,11 +50,13 @@ content:
field_tags:
type: entity_reference_autocomplete_tags
weight: 3
+ region: content
settings: { }
third_party_settings: { }
path:
type: path
weight: 30
+ region: content
settings: { }
third_party_settings: { }
promote:
@@ -58,16 +64,19 @@ content:
settings:
display_label: true
weight: 15
+ region: content
third_party_settings: { }
sticky:
type: boolean_checkbox
settings:
display_label: true
weight: 16
+ region: content
third_party_settings: { }
title:
type: string_textfield
weight: 0
+ region: content
settings:
size: 60
placeholder: ''
@@ -75,6 +84,7 @@ content:
uid:
type: entity_reference_autocomplete
weight: 5
+ region: content
settings:
match_operator: CONTAINS
size: 60
diff --git a/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml b/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml
index 1fef06d..0b7ffd1 100644
--- a/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml
+++ b/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml
@@ -15,6 +15,7 @@ content:
body:
type: text_textarea_with_summary
weight: 31
+ region: content
settings:
rows: 9
summary_rows: 3
@@ -23,11 +24,13 @@ content:
created:
type: datetime_timestamp
weight: 10
+ region: content
settings: { }
third_party_settings: { }
path:
type: path
weight: 30
+ region: content
settings: { }
third_party_settings: { }
promote:
@@ -35,16 +38,19 @@ content:
settings:
display_label: true
weight: 15
+ region: content
third_party_settings: { }
sticky:
type: boolean_checkbox
settings:
display_label: true
weight: 16
+ region: content
third_party_settings: { }
title:
type: string_textfield
weight: -5
+ region: content
settings:
size: 60
placeholder: ''
@@ -52,6 +58,7 @@ content:
uid:
type: entity_reference_autocomplete
weight: 5
+ region: content
settings:
match_operator: CONTAINS
size: 60
diff --git a/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml b/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml
index 466b6e0..6832229 100644
--- a/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml
+++ b/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml
@@ -14,12 +14,16 @@ mode: default
content:
account:
weight: -10
+ region: content
contact:
weight: 5
+ region: content
language:
weight: 0
+ region: content
timezone:
weight: 6
+ region: content
user_picture:
type: image_image
settings:
@@ -27,4 +31,5 @@ content:
preview_image_style: thumbnail
third_party_settings: { }
weight: -1
+ region: content
hidden: { }
diff --git a/core/profiles/standard/config/install/core.entity_view_display.block_content.basic.default.yml b/core/profiles/standard/config/install/core.entity_view_display.block_content.basic.default.yml
index bd52f77..e494882 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.block_content.basic.default.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.block_content.basic.default.yml
@@ -15,6 +15,7 @@ content:
label: hidden
type: text_default
weight: 0
+ region: content
settings: { }
third_party_settings: { }
hidden: { }
diff --git a/core/profiles/standard/config/install/core.entity_view_display.comment.comment.default.yml b/core/profiles/standard/config/install/core.entity_view_display.comment.comment.default.yml
index 1ed49ce..6ae213d 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.comment.comment.default.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.comment.comment.default.yml
@@ -15,8 +15,10 @@ content:
label: hidden
type: text_default
weight: 0
+ region: content
settings: { }
third_party_settings: { }
links:
weight: 100
+ region: content
hidden: { }
diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml b/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml
index 98a2de8..5c43252 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml
@@ -22,12 +22,14 @@ content:
body:
type: text_default
weight: 0
+ region: content
settings: { }
third_party_settings: { }
label: hidden
comment:
type: comment_default
weight: 110
+ region: content
label: above
settings:
view_mode: default
@@ -36,6 +38,7 @@ content:
field_image:
type: image
weight: -1
+ region: content
settings:
image_style: large
image_link: ''
@@ -44,12 +47,14 @@ content:
field_tags:
type: entity_reference_label
weight: 10
+ region: content
label: above
settings:
link: true
third_party_settings: { }
links:
weight: 100
+ region: content
hidden:
field_image: true
field_tags: true
diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.article.rss.yml b/core/profiles/standard/config/install/core.entity_view_display.node.article.rss.yml
index 75a14a3..84660b6 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.node.article.rss.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.node.article.rss.yml
@@ -17,6 +17,7 @@ mode: rss
content:
links:
weight: 100
+ region: content
hidden:
body: true
comment: true
diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml b/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml
index 43ee079..7b96908 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml
@@ -21,6 +21,7 @@ content:
body:
type: text_summary_or_trimmed
weight: 0
+ region: content
settings:
trim_length: 600
third_party_settings: { }
@@ -28,6 +29,7 @@ content:
field_image:
type: image
weight: -1
+ region: content
settings:
image_style: medium
image_link: content
@@ -36,12 +38,14 @@ content:
field_tags:
type: entity_reference_label
weight: 10
+ region: content
settings:
link: true
third_party_settings: { }
label: above
links:
weight: 100
+ region: content
hidden:
comment: true
field_image: true
diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.page.default.yml b/core/profiles/standard/config/install/core.entity_view_display.node.page.default.yml
index dcb2d3e..8afd942 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.node.page.default.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.node.page.default.yml
@@ -16,8 +16,10 @@ content:
label: hidden
type: text_default
weight: 100
+ region: content
settings: { }
third_party_settings: { }
links:
weight: 101
+ region: content
hidden: { }
diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.page.teaser.yml b/core/profiles/standard/config/install/core.entity_view_display.node.page.teaser.yml
index f235a10..bc7a68c 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.node.page.teaser.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.node.page.teaser.yml
@@ -17,9 +17,11 @@ content:
label: hidden
type: text_summary_or_trimmed
weight: 100
+ region: content
settings:
trim_length: 600
third_party_settings: { }
links:
weight: 101
+ region: content
hidden: { }
diff --git a/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml b/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml
index 4c13792..2ff13ad 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml
@@ -16,6 +16,7 @@ content:
user_picture:
type: image
weight: 0
+ region: content
settings:
image_style: thumbnail
image_link: content
diff --git a/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml b/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml
index 9e4621d..ef1fdd7 100644
--- a/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml
+++ b/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml
@@ -14,9 +14,11 @@ mode: default
content:
member_for:
weight: 5
+ region: content
user_picture:
type: image
weight: 0
+ region: content
settings:
image_style: thumbnail
image_link: content
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDisplayFormBaseTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDisplayFormBaseTest.php
new file mode 100644
index 0000000..09124c0
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDisplayFormBaseTest.php
@@ -0,0 +1,296 @@
+prophesize(EntityDisplayInterface::class);
+ $entity->getPluginCollections()->willReturn([]);
+
+ // A field with no initial values, with mismatched submitted values, type is
+ // hidden.
+ $entity->getComponent('new_field_mismatch_type_hidden')->willReturn([]);
+ $field_values['new_field_mismatch_type_hidden'] = [
+ 'weight' => 0,
+ 'type' => 'hidden',
+ 'region' => 'content',
+ ];
+ $entity
+ ->setComponent('new_field_mismatch_type_hidden', [
+ 'weight' => 0,
+ 'region' => 'content',
+ ])
+ ->will(function($args) {
+ // On subsequent calls, getComponent() will return the newly set values,
+ // plus the updated type value.
+ $args[1] += ['type' => 'textfield'];
+ $this->getComponent($args[0])->willReturn($args[1]);
+ $this->setComponent($args[0], $args[1])->shouldBeCalled();
+ })
+ ->shouldBeCalled();
+
+ // A field with no initial values, with mismatched submitted values, type is
+ // visible.
+ $entity->getComponent('new_field_mismatch_type_visible')->willReturn([]);
+ $field_values['new_field_mismatch_type_visible'] = [
+ 'weight' => 0,
+ 'type' => 'textfield',
+ 'region' => 'hidden',
+ ];
+ $entity
+ ->setComponent('new_field_mismatch_type_visible', [
+ 'weight' => 0,
+ 'type' => 'textfield',
+ ])
+ ->will(function($args) {
+ // On subsequent calls, getComponent() will return the newly set values,
+ // plus the updated region value.
+ $args[1] += ['region' => 'content'];
+ $this->getComponent($args[0])->willReturn($args[1]);
+ $this->setComponent($args[0], $args[1])->shouldBeCalled();
+ })
+ ->shouldBeCalled();
+
+ // An initially hidden field, with identical submitted values.
+ $entity->getComponent('field_hidden_no_changes')
+ ->willReturn([
+ 'weight' => 0,
+ 'type' => 'hidden',
+ 'region' => 'hidden',
+ ]);
+ $field_values['field_hidden_no_changes'] = [
+ 'weight' => 0,
+ 'type' => 'hidden',
+ 'region' => 'hidden',
+ ];
+ $entity->removeComponent('field_hidden_no_changes')
+ ->will(function ($args) {
+ // On subsequent calls, getComponent() will return an empty array.
+ $this->getComponent($args[0])->willReturn([]);
+ })
+ ->shouldBeCalled();
+
+ // An initially visible field, with identical submitted values.
+ $entity->getComponent('field_visible_no_changes')
+ ->willReturn([
+ 'weight' => 0,
+ 'type' => 'textfield',
+ 'region' => 'content',
+ ]);
+ $field_values['field_visible_no_changes'] = [
+ 'weight' => 0,
+ 'type' => 'textfield',
+ 'region' => 'content',
+ ];
+ $entity
+ ->setComponent('field_visible_no_changes', [
+ 'weight' => 0,
+ 'type' => 'textfield',
+ 'region' => 'content',
+ ])
+ ->shouldBeCalled();
+
+ // An initially hidden field, with a submitted type change.
+ $entity->getComponent('field_start_hidden_change_type')
+ ->willReturn([
+ 'weight' => 0,
+ 'type' => 'hidden',
+ 'region' => 'hidden',
+ ]);
+ $field_values['field_start_hidden_change_type'] = [
+ 'weight' => 0,
+ 'type' => 'textfield',
+ 'region' => 'hidden',
+ ];
+ $entity
+ ->setComponent('field_start_hidden_change_type', [
+ 'weight' => 0,
+ 'type' => 'textfield',
+ ])
+ ->will(function($args) {
+ // On subsequent calls, getComponent() will return the newly set values,
+ // plus the updated region value.
+ $args[1] += ['region' => 'content'];
+ $this->getComponent($args[0])->willReturn($args[1]);
+ $this->setComponent($args[0], $args[1])->shouldBeCalled();
+ })
+ ->shouldBeCalled();
+
+ // An initially hidden field, with a submitted region change.
+ $entity->getComponent('field_start_hidden_change_region')
+ ->willReturn([
+ 'weight' => 0,
+ 'type' => 'hidden',
+ 'region' => 'hidden',
+ ]);
+ $field_values['field_start_hidden_change_region'] = [
+ 'weight' => 0,
+ 'type' => 'hidden',
+ 'region' => 'content',
+ ];
+ $entity
+ ->setComponent('field_start_hidden_change_region', [
+ 'weight' => 0,
+ 'region' => 'content',
+ ])
+ ->will(function($args) {
+ // On subsequent calls, getComponent() will return the newly set values,
+ // plus the updated type value.
+ $args[1] += ['type' => 'textfield'];
+ $this->getComponent($args[0])->willReturn($args[1]);
+ $this->setComponent($args[0], $args[1])->shouldBeCalled();
+ })
+ ->shouldBeCalled();
+
+ // An initially hidden field, with a submitted region and type change.
+ $entity->getComponent('field_start_hidden_change_both')
+ ->willReturn([
+ 'weight' => 0,
+ 'type' => 'hidden',
+ 'region' => 'hidden',
+ ]);
+ $field_values['field_start_hidden_change_both'] = [
+ 'weight' => 0,
+ 'type' => 'textfield',
+ 'region' => 'content',
+ ];
+ $entity
+ ->setComponent('field_start_hidden_change_both', [
+ 'weight' => 0,
+ 'type' => 'textfield',
+ 'region' => 'content',
+ ])
+ ->will(function($args) {
+ // On subsequent calls, getComponent() will return the newly set values.
+ $this->getComponent($args[0])->willReturn($args[1]);
+ })
+ ->shouldBeCalled();
+
+ // An initially visible field, with a submitted type change.
+ $entity->getComponent('field_start_visible_change_type')
+ ->willReturn([
+ 'weight' => 0,
+ 'type' => 'textfield',
+ 'region' => 'content',
+ ]);
+ $field_values['field_start_visible_change_type'] = [
+ 'weight' => 0,
+ 'type' => 'hidden',
+ 'region' => 'content',
+ ];
+ $entity->removeComponent('field_start_visible_change_type')
+ ->will(function ($args) {
+ // On subsequent calls, getComponent() will return an empty array.
+ $this->getComponent($args[0])->willReturn([]);
+ })
+ ->shouldBeCalled();
+
+ // An initially visible field, with a submitted region change.
+ $entity->getComponent('field_start_visible_change_region')
+ ->willReturn([
+ 'weight' => 0,
+ 'type' => 'textfield',
+ 'region' => 'content',
+ ]);
+ $field_values['field_start_visible_change_region'] = [
+ 'weight' => 0,
+ 'type' => 'textfield',
+ 'region' => 'hidden',
+ ];
+ $entity->removeComponent('field_start_visible_change_region')
+ ->will(function ($args) {
+ // On subsequent calls, getComponent() will return an empty array.
+ $this->getComponent($args[0])->willReturn([]);
+ })
+ ->shouldBeCalled();
+
+ // An initially visible field, with a submitted region and type change.
+ $entity->getComponent('field_start_visible_change_both')
+ ->willReturn([
+ 'weight' => 0,
+ 'type' => 'textfield',
+ 'region' => 'content',
+ ]);
+ $field_values['field_start_visible_change_both'] = [
+ 'weight' => 0,
+ 'type' => 'hidden',
+ 'region' => 'hidden',
+ ];
+ $entity->removeComponent('field_start_visible_change_both')
+ ->will(function ($args) {
+ // On subsequent calls, getComponent() will return an empty array.
+ $this->getComponent($args[0])->willReturn([]);
+ })
+ ->shouldBeCalled();
+
+ // A field that is flagged for plugin settings update on the second build.
+ $entity->getComponent('field_plugin_settings_update')
+ ->willReturn([
+ 'weight' => 0,
+ 'type' => 'textfield',
+ 'region' => 'content',
+ ]);
+ $field_values['field_plugin_settings_update'] = [
+ 'weight' => 0,
+ 'type' => 'textfield',
+ 'region' => 'content',
+ 'settings_edit_form' => [
+ 'third_party_settings' => [
+ 'foo' => 'bar',
+ ],
+ ],
+ ];
+ $entity
+ ->setComponent('field_plugin_settings_update', [
+ 'weight' => 0,
+ 'type' => 'textfield',
+ 'region' => 'content',
+ ])
+ ->will(function ($args) {
+ // On subsequent calls, getComponent() will return the newly set values.
+ $this->getComponent($args[0])->willReturn($args[1]);
+ $args[1] += [
+ 'settings' => [],
+ 'third_party_settings' => [
+ 'foo' => 'bar',
+ ],
+ ];
+ $this->setComponent($args[0], $args[1])->shouldBeCalled();
+ })
+ ->shouldBeCalled();
+
+ $form_object = new EntityViewDisplayEditForm($this->container->get('plugin.manager.field.field_type'), $this->container->get('plugin.manager.field.formatter'));
+ $form_object->setEntity($entity->reveal());
+
+ $form = [
+ '#fields' => array_keys($field_values),
+ '#extra' => [],
+ ];
+ $form_state = new FormState();
+ $form_state->setValues(['fields' => $field_values]);
+
+ $form_object->buildEntity($form, $form_state);
+
+ // Flag one field for updating plugin settings.
+ $form_state->set('plugin_settings_update', 'field_plugin_settings_update');
+ // During form submission, buildEntity() will be called twice. Simulate that
+ // here to prove copyFormValuesToEntity() is idempotent.
+ $form_object->buildEntity($form, $form_state);
+ }
+
+}