diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php index f73872baa36e4bca652ec078fd306f93a4bbe181..33b3a37ef0280ed7f022be652ea07ee1d82a0bd0 100644 --- a/core/lib/Drupal/Core/Field/FieldItemList.php +++ b/core/lib/Drupal/Core/Field/FieldItemList.php @@ -350,13 +350,27 @@ protected function defaultValueWidget(FormStateInterface $form_state) { $definition->setRequired(FALSE); $definition->setDescription(''); - // Use the widget currently configured for the 'default' form mode, or - // fallback to the default widget for the field type. + /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $entity_form_display */ $entity_form_display = \Drupal::service('entity_display.repository') ->getFormDisplay($entity->getEntityTypeId(), $entity->bundle()); - $widget = $entity_form_display->getRenderer($this->getFieldDefinition()->getName()); - if (!$widget) { - $widget = \Drupal::service('plugin.manager.field.widget')->getInstance(['field_definition' => $this->getFieldDefinition()]); + /** @var \Drupal\Core\Field\WidgetPluginManager $field_widget_plugin_manager */ + $field_widget_plugin_manager = \Drupal::service('plugin.manager.field.widget'); + + // Use the widget currently configured for the 'default' form mode, or + // fallback to the default widget for the field type. + if (($configuration = $entity_form_display->getComponent($definition->getName())) && isset($configuration['type'])) { + // Get the plugin instance manually to ensure an up-to-date field + // definition is used. + // @see \Drupal\Core\Entity\Entity\EntityFormDisplay::getRenderer + $widget = $field_widget_plugin_manager->getInstance([ + 'field_definition' => $definition, + 'form_mode' => $entity_form_display->getOriginalMode(), + 'prepare' => FALSE, + 'configuration' => $configuration, + ]); + } + else { + $widget = $field_widget_plugin_manager->getInstance(['field_definition' => $definition]); } $form_state->set('default_value_widget', $widget); diff --git a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceFieldDefaultValueTest.php b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceFieldDefaultValueTest.php index efcce0e307f834058fcc51efa01664856783c3b2..f0c55c80ab911a80a24a2dac65595ed54e7de6af 100644 --- a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceFieldDefaultValueTest.php +++ b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceFieldDefaultValueTest.php @@ -157,6 +157,10 @@ public function testEntityReferenceDefaultConfigValue() { 'default_value_input[' . $field_name . '][1][target_id]' => $referenced_node_type2->label() . ' (' . $referenced_node_type2->id() . ')', ]; $this->drupalGet('admin/structure/types/manage/reference_content/fields/node.reference_content.' . $field_name); + $this->assertSession()->fieldExists("default_value_input[{$field_name}][0][target_id]"); + $this->assertSession()->fieldNotExists("default_value_input[{$field_name}][1][target_id]"); + $this->submitForm([], 'Add another item'); + $this->assertSession()->fieldExists("default_value_input[{$field_name}][1][target_id]"); $this->submitForm($field_edit, 'Save settings'); // Check that the field has a dependency on the default value. diff --git a/core/modules/field/tests/src/Functional/FormTest.php b/core/modules/field/tests/src/Functional/FormTest.php index 3fbe668953e0e2e38032bfaa938c79139617744f..85e2d8a1b2cc1ac8dc504827ff0b8679a058de49 100644 --- a/core/modules/field/tests/src/Functional/FormTest.php +++ b/core/modules/field/tests/src/Functional/FormTest.php @@ -31,6 +31,7 @@ class FormTest extends FieldTestBase { 'options', 'entity_test', 'locale', + 'field_ui', ]; /** @@ -75,6 +76,7 @@ protected function setUp(): void { $web_user = $this->drupalCreateUser([ 'view test entity', 'administer entity_test content', + 'administer entity_test fields', ]); $this->drupalLogin($web_user); @@ -271,6 +273,11 @@ public function testFieldFormUnlimited() { ->setComponent($field_name) ->save(); + // Verify that only one "Default value" field + // exists on the Manage field display. + $this->drupalGet("entity_test/structure/entity_test/fields/entity_test.entity_test.{$field_name}"); + $this->assertSession()->elementsCount('xpath', "//table[@id='field-unlimited-values']/tbody/tr//input[contains(@class, 'form-text')]", 1); + // Display creation form -> 1 widget. $this->drupalGet('entity_test/add'); $this->assertSession()->fieldValueEquals("{$field_name}[0][value]", ''); diff --git a/core/modules/field_ui/src/Form/FieldConfigEditForm.php b/core/modules/field_ui/src/Form/FieldConfigEditForm.php index b8a004eaaf483bd2f37ca021347cf02e948f1558..acd27872636dcf9383d1f49884c5e532375de2eb 100644 --- a/core/modules/field_ui/src/Form/FieldConfigEditForm.php +++ b/core/modules/field_ui/src/Form/FieldConfigEditForm.php @@ -4,9 +4,13 @@ use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; +use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Entity\Plugin\DataType\EntityAdapter; use Drupal\Core\Field\FieldFilteredMarkup; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; +use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\TypedData\TypedDataManagerInterface; use Drupal\Core\Url; use Drupal\field\FieldConfigInterface; use Drupal\field_ui\FieldUI; @@ -38,8 +42,10 @@ class FieldConfigEditForm extends EntityForm { * * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info * The entity type bundle info service. + * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typedDataManager + * The type data manger. */ - public function __construct(EntityTypeBundleInfoInterface $entity_type_bundle_info) { + public function __construct(EntityTypeBundleInfoInterface $entity_type_bundle_info, protected TypedDataManagerInterface $typedDataManager) { $this->entityTypeBundleInfo = $entity_type_bundle_info; } @@ -48,7 +54,8 @@ public function __construct(EntityTypeBundleInfoInterface $entity_type_bundle_in */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity_type.bundle.info') + $container->get('entity_type.bundle.info'), + $container->get('typed_data_manager') ); } @@ -107,7 +114,7 @@ public function form(array $form, FormStateInterface $form_state) { 'entity_id' => NULL, ]; $form['#entity'] = _field_create_entity_from_ids($ids); - $items = $form['#entity']->get($this->entity->getName()); + $items = $this->getTypedData($form['#entity']); $item = $items->first() ?: $items->appendItem(); // Add field settings for the field type and a container for third party @@ -122,6 +129,10 @@ public function form(array $form, FormStateInterface $form_state) { '#weight' => 11, ]; + // Create a new instance of typed data for the field to ensure that default + // value widget is always rendered from a clean state. + $items = $this->getTypedData($form['#entity']); + // Add handling for default value. if ($element = $items->defaultValuesForm($form, $form_state)) { $has_required = $this->hasAnyRequired($element); @@ -219,9 +230,15 @@ protected function actions(array $form, FormStateInterface $form_state) { public function validateForm(array &$form, FormStateInterface $form_state) { parent::validateForm($form, $form_state); + // Before proceeding validation, rebuild the entity to make sure it's + // up-to-date. This is needed because element validators may update form + // state, and other validators use the entity for validating the field. + // @todo remove in https://www.drupal.org/project/drupal/issues/3372934. + $this->entity = $this->buildEntity($form, $form_state); + if (isset($form['default_value']) && (!isset($form['set_default_value']) || $form_state->getValue('set_default_value'))) { - $item = $form['#entity']->get($this->entity->getName()); - $item->defaultValuesFormValidate($form['default_value'], $form, $form_state); + $items = $this->getTypedData($form['#entity']); + $items->defaultValuesFormValidate($form['default_value'], $form, $form_state); } } @@ -234,7 +251,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { // Handle the default value. $default_value = []; if (isset($form['default_value']) && (!isset($form['set_default_value']) || $form_state->getValue('set_default_value'))) { - $items = $form['#entity']->get($this->entity->getName()); + $items = $this->getTypedData($form['#entity']); $default_value = $items->defaultValuesFormSubmit($form['default_value'], $form, $form_state); } $this->entity->setDefaultValue($default_value); @@ -271,4 +288,17 @@ public function getTitle(FieldConfigInterface $field_config) { return $field_config->label(); } + /** + * Gets typed data object for the field. + * + * @param \Drupal\Core\Entity\FieldableEntityInterface $parent + * The parent entity that the field is attached to. + * + * @return \Drupal\Core\TypedData\TypedDataInterface + */ + private function getTypedData(FieldableEntityInterface $parent): TypedDataInterface { + $entity_adapter = EntityAdapter::createFromEntity($parent); + return $this->typedDataManager->create($this->entity, $this->entity->getDefaultValue($parent), $this->entity->getName(), $entity_adapter); + } + } diff --git a/core/modules/field_ui/tests/src/Unit/FieldConfigEditFormTest.php b/core/modules/field_ui/tests/src/Unit/FieldConfigEditFormTest.php index 5478d9826fca389388533b2547d548b6447c0929..04ecaf4ecd5a5fe1e49fafb9c6a4f461ca3696e9 100644 --- a/core/modules/field_ui/tests/src/Unit/FieldConfigEditFormTest.php +++ b/core/modules/field_ui/tests/src/Unit/FieldConfigEditFormTest.php @@ -26,7 +26,8 @@ protected function setUp(): void { parent::setUp(); $entity_type_bundle_info = $this->createMock('\Drupal\Core\Entity\EntityTypeBundleInfoInterface'); - $this->fieldConfigEditForm = new FieldConfigEditForm($entity_type_bundle_info); + $typed_data = $this->createMock('\Drupal\Core\TypedData\TypedDataManagerInterface'); + $this->fieldConfigEditForm = new FieldConfigEditForm($entity_type_bundle_info, $typed_data); } /**