diff --git a/core/lib/Drupal/Core/Field/FieldConfigBase.php b/core/lib/Drupal/Core/Field/FieldConfigBase.php index e9032e8cc9f6708f4c2a62663e36fea952bc327e..ca9a5b5285ead4c5285ec4cf6b7a091b670f6405 100644 --- a/core/lib/Drupal/Core/Field/FieldConfigBase.php +++ b/core/lib/Drupal/Core/Field/FieldConfigBase.php @@ -6,7 +6,6 @@ use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Field\TypedData\FieldItemDataDefinition; -use Drupal\field\Entity\FieldStorageConfig; /** * Base class for configurable field definitions. @@ -475,11 +474,6 @@ public function __sleep() { // recalculated. unset($properties['itemDefinition'], $properties['original']); - // Field storage can be recalculated if it's not new. - if (array_key_exists('fieldStorage', $properties) && $properties['fieldStorage'] instanceof FieldStorageConfig && !$properties['fieldStorage']->isNew()) { - unset($properties['fieldStorage']); - } - return array_keys($properties); } diff --git a/core/lib/Drupal/Core/Form/SubformState.php b/core/lib/Drupal/Core/Form/SubformState.php index 50f39117c25859522406767a618268a4bcc87e5d..bec19be6205177ecd8ddbc85dacdbda823c5d0c1 100644 --- a/core/lib/Drupal/Core/Form/SubformState.php +++ b/core/lib/Drupal/Core/Form/SubformState.php @@ -34,8 +34,10 @@ class SubformState extends FormStateDecoratorBase implements SubformStateInterfa * The subform's parent form. * @param \Drupal\Core\Form\FormStateInterface $parent_form_state * The parent form state. + * @param \Drupal\Core\Form\FormInterface|null $subformFormObject + * The subform form object when it's not the same as the parent form. */ - protected function __construct(array &$subform, array &$parent_form, FormStateInterface $parent_form_state) { + protected function __construct(array &$subform, array &$parent_form, FormStateInterface $parent_form_state, protected readonly ?FormInterface $subformFormObject = NULL) { $this->decoratedFormState = $parent_form_state; $this->parentForm = $parent_form; $this->subform = $subform; @@ -50,11 +52,13 @@ protected function __construct(array &$subform, array &$parent_form, FormStateIn * The subform's parent form. * @param \Drupal\Core\Form\FormStateInterface $parent_form_state * The parent form state. + * @param \Drupal\Core\Form\FormInterface|null $subform_form_object + * The subform form object when it's not the same as the parent form. * * @return static */ - public static function createForSubform(array &$subform, array &$parent_form, FormStateInterface $parent_form_state) { - return new static($subform, $parent_form, $parent_form_state); + public static function createForSubform(array &$subform, array &$parent_form, FormStateInterface $parent_form_state, ?FormInterface $subform_form_object = NULL) { + return new static($subform, $parent_form, $parent_form_state, $subform_form_object); } /** @@ -151,4 +155,15 @@ public function setErrorByName($name, $message = '') { return $this; } + /** + * {@inheritdoc} + */ + public function getFormObject() { + if ($this->subformFormObject) { + return $this->subformFormObject; + } + + return parent::getFormObject(); + } + } diff --git a/core/modules/comment/tests/src/Functional/CommentFieldsTest.php b/core/modules/comment/tests/src/Functional/CommentFieldsTest.php index f67ebea79c7a45df4488ede97aedc464fb22953f..ad42c61a6553021ff816fbd12dd0be50443ab621 100644 --- a/core/modules/comment/tests/src/Functional/CommentFieldsTest.php +++ b/core/modules/comment/tests/src/Functional/CommentFieldsTest.php @@ -162,8 +162,7 @@ public function testCommentFieldCreate() { // Try to save the comment field without selecting a comment type. $edit = []; - $this->drupalGet('admin/config/people/accounts/add-storage/user/field_user_comment'); - $this->submitForm($edit, 'Continue'); + $this->submitForm($edit, 'Update settings'); // We should get an error message. $this->assertSession()->pageTextContains('The submitted value in the Comment type element is not allowed.'); @@ -178,10 +177,10 @@ public function testCommentFieldCreate() { // Select a comment type and try to save again. $edit = [ - 'settings[comment_type]' => 'user_comment_type', + 'field_storage[subform][settings][comment_type]' => 'user_comment_type', ]; - $this->drupalGet('admin/config/people/accounts/add-storage/user/field_user_comment'); - $this->submitForm($edit, 'Continue'); + $this->drupalGet('admin/config/people/accounts/add-field/user/field_user_comment'); + $this->submitForm($edit, 'Update settings'); // We shouldn't get an error message. $this->assertSession()->pageTextNotContains('The submitted value in the Comment type element is not allowed.'); diff --git a/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php b/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php index c66865da10175bfe046e05327f918fbc01a4eb16..ca5b8f111ade2dee3262b15d7eac30809fd582b2 100644 --- a/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php +++ b/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php @@ -277,8 +277,6 @@ public function testCommentFunctionality() { $this->assertSession()->statusCodeEquals(200); $this->assertSession()->fieldNotExists('edit-default-value-input-comment-und-0-status-0'); // Test that field to change cardinality is not available. - $this->drupalGet('entity_test/structure/entity_test/fields/entity_test.entity_test.comment/storage'); - $this->assertSession()->statusCodeEquals(200); $this->assertSession()->fieldNotExists('cardinality_number'); $this->assertSession()->fieldNotExists('cardinality'); @@ -439,7 +437,7 @@ public function testCommentFunctionality() { // Add a new comment field. $storage_edit = [ - 'settings[comment_type]' => 'foobar', + 'field_storage[subform][settings][comment_type]' => 'foobar', ]; $this->fieldUIAddNewField('entity_test/structure/entity_test', 'foobar', 'Foobar', 'comment', $storage_edit); diff --git a/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php b/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php index ca201a398f72376e507bca26ac4e4f15f17969fa..9bcd0acc7c13d551592465165a04264a37a62db6 100644 --- a/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php +++ b/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php @@ -932,8 +932,8 @@ public function testDateStorageSettings() { ]; $this->drupalGet('node/add/date_content'); $this->submitForm($edit, 'Save'); - $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name . '/storage'); - $this->assertSession()->elementsCount('xpath', "//*[@id='edit-settings-datetime-type' and contains(@disabled, 'disabled')]", 1); + $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name); + $this->assertSession()->elementsCount('xpath', "//*[@name='field_storage[subform][settings][datetime_type]' and contains(@disabled, 'disabled')]", 1); } } diff --git a/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php b/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php index f19d18fd1b1e543503308ddf2b16f409b2370da9..ecbf3aa335b66316ba8e170eec242eac5b92d673 100644 --- a/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php +++ b/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php @@ -1422,8 +1422,8 @@ public function testDateStorageSettings() { ]; $this->drupalGet('node/add/date_content'); $this->submitForm($edit, 'Save'); - $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name . '/storage'); - $this->assertSession()->elementsCount('xpath', "//*[@id='edit-settings-datetime-type' and contains(@disabled, 'disabled')]", 1); + $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name); + $this->assertSession()->elementsCount('xpath', "//*[@name='field_storage[subform][settings][datetime_type]' and contains(@disabled, 'disabled')]", 1); } } diff --git a/core/modules/field/field.module b/core/modules/field/field.module index a5d0ee3b585f8461f2229fa03659884a4a26865d..f2c600715641b424659ee0d0155ed5ffdd64bbf4 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -448,3 +448,51 @@ function field_field_config_presave(FieldConfigInterface $field) { ]); } } + +/** + * Entity form builder for field config edit form. + * + * @param string $entity_type_id + * The entity type identifier. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity updated with the submitted values. + * @param array $form + * The complete form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @see \Drupal\field_ui\Form\FieldConfigEditForm::form + * @see \Drupal\field_ui\Form\FieldConfigEditForm::copyFormValuesToEntity + */ +function field_form_field_config_edit_form_entity_builder($entity_type_id, $entity, &$form, FormStateInterface $form_state) { + assert($entity instanceof FieldConfigInterface); + $previous_field_storage = $form_state->getFormObject()->getEntity()->getFieldStorageDefinition(); + assert($previous_field_storage instanceof FieldStorageConfigInterface); + + // Act on all sub-types of the entity_reference field type. + /** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */ + $field_type_manager = \Drupal::service('plugin.manager.field.field_type'); + $item_class = 'Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem'; + $class = $field_type_manager->getPluginClass($entity->getFieldStorageDefinition()->getType()); + if ($class !== $item_class && !is_subclass_of($class, $item_class)) { + return; + } + + // Update handler settings when target_type is changed. + if ($entity->getFieldStorageDefinition()->getSetting('target_type') !== $previous_field_storage->getSetting('target_type')) { + // @see field_field_storage_config_update(). + $entity->setSetting('handler_settings', []); + // @see field_field_config_presave(). + field_field_config_create($entity); + + // Store updated settings in form state so that the form state can be copied + // directly to the entity. + $form_state->setValue('settings', $entity->getSettings()); + + // Unset user input for the settings because they are not valid after the + // target type has changed. + $user_input = $form_state->getUserInput(); + unset($user_input['settings']); + $form_state->setUserInput($user_input); + } +} diff --git a/core/modules/field/src/Entity/FieldConfig.php b/core/modules/field/src/Entity/FieldConfig.php index 0c858c334a8b3c69503c6afad7955841648136b3..ca59414c33060a600bcda5156d2464348c29267e 100644 --- a/core/modules/field/src/Entity/FieldConfig.php +++ b/core/modules/field/src/Entity/FieldConfig.php @@ -263,7 +263,6 @@ protected function linkTemplates() { $link_templates = parent::linkTemplates(); if (\Drupal::moduleHandler()->moduleExists('field_ui')) { $link_templates["{$this->entity_type}-field-edit-form"] = 'entity.field_config.' . $this->entity_type . '_field_edit_form'; - $link_templates["{$this->entity_type}-storage-edit-form"] = 'entity.field_config.' . $this->entity_type . '_storage_edit_form'; $link_templates["{$this->entity_type}-field-delete-form"] = 'entity.field_config.' . $this->entity_type . '_field_delete_form'; if (isset($link_templates['config-translation-overview'])) { diff --git a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldType/TestItem.php b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldType/TestItem.php index ad2a135a2172fcf900815b1158794bc8ddc368ba..7f4d5f31e9ab97b0603fe1ab910976d1406ed93d 100644 --- a/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldType/TestItem.php +++ b/core/modules/field/tests/modules/field_test/src/Plugin/Field/FieldType/TestItem.php @@ -75,7 +75,8 @@ public static function schema(FieldStorageDefinitionInterface $field_definition) * {@inheritdoc} */ public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) { - $form['test_field_storage_setting'] = [ + $element = []; + $element['test_field_storage_setting'] = [ '#type' => 'textfield', '#title' => $this->t('Field test field storage setting'), '#default_value' => $this->getSetting('test_field_storage_setting'), @@ -83,14 +84,15 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state '#description' => $this->t('A dummy form element to simulate field storage setting.'), ]; - return $form; + return $element; } /** * {@inheritdoc} */ public function fieldSettingsForm(array $form, FormStateInterface $form_state) { - $form['test_field_setting'] = [ + $element = []; + $element['test_field_setting'] = [ '#type' => 'textfield', '#title' => $this->t('Field test field setting'), '#default_value' => $this->getSetting('test_field_setting'), @@ -98,7 +100,7 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) { '#description' => $this->t('A dummy form element to simulate field setting.'), ]; - return $form; + return $element; } /** diff --git a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAdminTest.php b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAdminTest.php index 12362a73ffb4284932227b30f4ae5afdeef2e044..9f3790b9995d0b9a1d8d4749f178ab2c649160d1 100644 --- a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAdminTest.php +++ b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAdminTest.php @@ -118,9 +118,9 @@ public function testFieldAdminHandler() { // Set to unlimited. $edit = [ - 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, + 'field_storage[subform][cardinality]' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, ]; - $this->submitForm($edit, 'Continue'); + $this->submitForm($edit, 'Update settings'); // Add the view to the test field. $edit = [ @@ -204,12 +204,11 @@ public function testFieldAdminHandler() { Vocabulary::create(['vid' => 'tags', 'name' => 'tags'])->save(); $taxonomy_term_field_name = $this->createEntityReferenceField('taxonomy_term', ['tags']); $field_path = 'node.' . $this->type . '.field_' . $taxonomy_term_field_name; - $this->drupalGet($bundle_path . '/fields/' . $field_path . '/storage'); + $this->drupalGet($bundle_path . '/fields/' . $field_path); $edit = [ - 'cardinality' => -1, + 'field_storage[subform][cardinality]' => -1, ]; - $this->submitForm($edit, 'Save'); - $this->drupalGet($bundle_path . '/fields/' . $field_path); + $this->submitForm($edit, 'Update settings'); $term_name = $this->randomString(); $result = \Drupal::entityQuery('taxonomy_term') ->condition('name', $term_name) @@ -382,7 +381,7 @@ protected function createEntityReferenceField($target_type, $bundles = []) { $field_name = $this->randomMachineName(); $storage_edit = $field_edit = []; - $storage_edit['settings[target_type]'] = $target_type; + $storage_edit['field_storage[subform][settings][target_type]'] = $target_type; foreach ($bundles as $bundle) { $field_edit['settings[handler_settings][target_bundles][' . $bundle . ']'] = TRUE; } diff --git a/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php b/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php index 84f20b3501be711bf33a69648982567d85712047..74f981d84a2d05b9e930718ab1b299113d2d4421 100644 --- a/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php +++ b/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php @@ -130,13 +130,10 @@ public function testFieldAdminHandler() { $this->fieldUIAddNewFieldJS(NULL, 'test', 'Test', 'entity_reference', FALSE); // Node should be selected by default. - $this->assertSession()->fieldValueEquals('settings[target_type]', 'node'); + $this->assertSession()->fieldValueEquals('field_storage[subform][settings][target_type]', 'node'); // Check that all entity types can be referenced. - $this->assertFieldSelectOptions('settings[target_type]', array_keys(\Drupal::entityTypeManager()->getDefinitions())); - - // Second step: 'Field settings' form. - $this->submitForm([], 'Continue'); + $this->assertFieldSelectOptions('field_storage[subform][settings][target_type]', array_keys(\Drupal::entityTypeManager()->getDefinitions())); // The base handler should be selected by default. $this->assertSession()->fieldValueEquals('settings[handler]', 'default:node'); @@ -264,23 +261,19 @@ public function testFieldAdminHandler() { // Switch the target type to 'taxonomy_term' and check that the settings // specific to its selection handler are displayed. $field_name = 'node.' . $this->type . '.field_test'; - $edit = [ - 'settings[target_type]' => 'taxonomy_term', - ]; - $this->drupalGet($bundle_path . '/fields/' . $field_name . '/storage'); - $this->submitForm($edit, 'Save'); $this->drupalGet($bundle_path . '/fields/' . $field_name); + $page->findField('field_storage[subform][settings][target_type]')->setValue('taxonomy_term'); + $this->assertSession()->assertWaitOnAjaxRequest(); $this->assertSession()->fieldExists('settings[handler_settings][auto_create]'); + $this->assertSession()->fieldValueEquals('settings[handler]', 'default:taxonomy_term'); // Switch the target type to 'user' and check that the settings specific to // its selection handler are displayed. $field_name = 'node.' . $this->type . '.field_test'; - $edit = [ - 'settings[target_type]' => 'user', - ]; - $this->drupalGet($bundle_path . '/fields/' . $field_name . '/storage'); - $this->submitForm($edit, 'Save'); $this->drupalGet($bundle_path . '/fields/' . $field_name); + $target_type_input = $assert_session->fieldExists('field_storage[subform][settings][target_type]'); + $target_type_input->setValue('user'); + $assert_session->assertWaitOnAjaxRequest(); $this->assertSession()->fieldValueEquals('settings[handler_settings][filter][type]', '_none'); $this->assertSession()->fieldValueEquals('settings[handler_settings][sort][field]', '_none'); $assert_session->optionNotExists('settings[handler_settings][sort][field]', 'nid'); @@ -295,11 +288,8 @@ public function testFieldAdminHandler() { // Switch the target type to 'node'. $field_name = 'node.' . $this->type . '.field_test'; - $edit = [ - 'settings[target_type]' => 'node', - ]; - $this->drupalGet($bundle_path . '/fields/' . $field_name . '/storage'); - $this->submitForm($edit, 'Save'); + $this->drupalGet($bundle_path . '/fields/' . $field_name); + $page->findField('field_storage[subform][settings][target_type]')->setValue('node'); // Try to select the views handler. $this->drupalGet($bundle_path . '/fields/' . $field_name); @@ -328,16 +318,13 @@ public function testFieldAdminHandler() { $assert_session->pageTextContains('Saved Test configuration.'); // Switch the target type to 'entity_test'. - $edit = [ - 'settings[target_type]' => 'entity_test', - ]; - $this->drupalGet($bundle_path . '/fields/' . $field_name . '/storage'); - $this->submitForm($edit, 'Save'); $this->drupalGet($bundle_path . '/fields/' . $field_name); + $page->findField('field_storage[subform][settings][target_type]')->setValue('entity_test'); $page->findField('settings[handler]')->setValue('views'); - $assert_session - ->waitForField('settings[handler_settings][view][view_and_display]') - ->setValue('test_entity_reference_entity_test:entity_reference_1'); + $assert_session->assertWaitOnAjaxRequest(); + $page + ->findField('settings[handler_settings][view][view_and_display]') + ->selectOption('test_entity_reference_entity_test:entity_reference_1'); $edit = [ 'required' => FALSE, ]; diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module index 46c983503a5d0db7e0dda851487ecbee216f566e..b8f4dc6f70be20e4987a112b9e92709b72a40638 100644 --- a/core/modules/field_ui/field_ui.module +++ b/core/modules/field_ui/field_ui.module @@ -7,12 +7,15 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Form\SubformState; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Entity\EntityViewModeInterface; use Drupal\Core\Entity\EntityFormModeInterface; use Drupal\Core\Url; +use Drupal\field\FieldConfigInterface; use Drupal\field_ui\FieldUI; use Drupal\field_ui\Form\FieldConfigEditForm; +use Drupal\field_ui\Form\FieldStorageConfigEditForm; use Drupal\field_ui\Plugin\Derivative\FieldUiLocalTask; /** @@ -85,6 +88,7 @@ function field_ui_entity_type_build(array &$entity_types) { $entity_types['field_config']->setListBuilderClass('Drupal\field_ui\FieldConfigListBuilder'); $entity_types['field_storage_config']->setFormClass('edit', 'Drupal\field_ui\Form\FieldStorageConfigEditForm'); + $entity_types['field_storage_config']->setFormClass('default', FieldStorageConfigEditForm::class); $entity_types['field_storage_config']->setListBuilderClass('Drupal\field_ui\FieldStorageConfigListBuilder'); $entity_types['field_storage_config']->setLinkTemplate('collection', '/admin/reports/fields'); @@ -155,7 +159,7 @@ function field_ui_entity_operation(EntityInterface $entity) { $operations['manage-display'] = [ 'title' => t('Manage display'), 'weight' => 25, - 'url' => Url::fromRoute("entity.entity_view_display.{$bundle_of}.default", [ + 'url' => Url::fromRoute("entity.entity_view_display.$bundle_of.default", [ $entity->getEntityTypeId() => $entity->id(), ]), ]; @@ -279,3 +283,21 @@ function field_ui_form_manage_field_form_submit($form, FormStateInterface $form_ $form_state->setRedirectUrl($route_info); } } + +/** + * Implements hook_form_FORM_ID_alter() for field_config_edit_form. + */ +function field_ui_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state) { + $field_config = $form_state->getFormObject()->getEntity(); + assert($field_config instanceof FieldConfigInterface); + + $form_id = 'field_storage_config_edit_form'; + $hook = 'form_' . $form_id; + + $field_storage_form = \Drupal::entityTypeManager()->getFormObject('field_storage_config', $form_state->getFormObject()->getOperation()); + $field_storage_form->setEntity($field_config->getFieldStorageDefinition()); + $subform_state = SubformState::createForSubform($form['field_storage']['subform'], $form, $form_state, $field_storage_form); + + \Drupal::moduleHandler()->alterDeprecated('Use hook_form_field_config_edit_form_alter() instead. See https://www.drupal.org/node/3386675.', $hook, $form['field_storage']['subform'], $subform_state, $form_id); + \Drupal::theme()->alter($hook, $form['field_storage']['subform'], $subform_state, $form_id); +} diff --git a/core/modules/field_ui/src/Controller/FieldConfigAddController.php b/core/modules/field_ui/src/Controller/FieldConfigAddController.php index 2a5a8b81de48cd0dbe9090e6fdc832229a49023d..8cb929608b0ebd024e3990a744f7c77972e077c3 100644 --- a/core/modules/field_ui/src/Controller/FieldConfigAddController.php +++ b/core/modules/field_ui/src/Controller/FieldConfigAddController.php @@ -5,8 +5,6 @@ namespace Drupal\field_ui\Controller; use Drupal\Core\Controller\ControllerBase; -use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface; -use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\TempStore\PrivateTempStore; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -23,15 +21,9 @@ final class FieldConfigAddController extends ControllerBase { * * @param \Drupal\Core\TempStore\PrivateTempStore $tempStore * The private tempstore. - * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $fieldTypeManager - * The field type plugin manager. - * @param \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface $selectionManager - * The entity reference selection plugin manager. */ public function __construct( protected readonly PrivateTempStore $tempStore, - protected readonly FieldTypePluginManagerInterface $fieldTypeManager, - protected readonly SelectionPluginManagerInterface $selectionManager, ) {} /** @@ -40,8 +32,6 @@ public function __construct( public static function create(ContainerInterface $container) { return new static( $container->get('tempstore.private')->get('field_ui'), - $container->get('plugin.manager.field.field_type'), - $container->get('plugin.manager.entity_reference_selection') ); } diff --git a/core/modules/field_ui/src/Controller/FieldStorageAddController.php b/core/modules/field_ui/src/Controller/FieldStorageAddController.php deleted file mode 100644 index c22ca0d94dd1010bef394bba13fa0abef692204d..0000000000000000000000000000000000000000 --- a/core/modules/field_ui/src/Controller/FieldStorageAddController.php +++ /dev/null @@ -1,64 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Drupal\field_ui\Controller; - -use Drupal\Core\Controller\ControllerBase; -use Drupal\Core\TempStore\PrivateTempStore; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; - -/** - * Controller for building the field storage form. - * - * @internal - * - * @todo remove in https://www.drupal.org/project/drupal/issues/3347291. - */ -final class FieldStorageAddController extends ControllerBase { - - /** - * FieldStorageAddController constructor. - * - * @param \Drupal\Core\TempStore\PrivateTempStore $tempStore - * The private tempstore. - */ - public function __construct(protected readonly PrivateTempStore $tempStore) {} - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('tempstore.private')->get('field_ui') - ); - } - - /** - * Builds the field storage form. - * - * @param string $entity_type - * The entity type. - * @param string $field_name - * The name of the field to create. - * @param string $bundle - * The bundle where the field is being created. - * - * @return array - * The field storage form. - */ - public function storageAddConfigureForm(string $entity_type, string $field_name, string $bundle): array { - // @see \Drupal\field_ui\Form\FieldStorageAddForm::submitForm - $temp_storage = $this->tempStore->get($entity_type . ':' . $field_name); - if (!$temp_storage) { - throw new NotFoundHttpException(); - } - - return $this->entityFormBuilder()->getForm($temp_storage['field_storage'], 'edit', [ - 'entity_type_id' => $entity_type, - 'bundle' => $bundle, - ]); - } - -} diff --git a/core/modules/field_ui/src/FieldConfigListBuilder.php b/core/modules/field_ui/src/FieldConfigListBuilder.php index 24cf2cdd91ac9e17848144800697754f7e39feef..0e4fbfeb489c071bb47a679cca5e8e7b01f96580 100644 --- a/core/modules/field_ui/src/FieldConfigListBuilder.php +++ b/core/modules/field_ui/src/FieldConfigListBuilder.php @@ -204,13 +204,6 @@ public function getDefaultOperations(EntityInterface $entity) { ]; } - $operations['storage-settings'] = [ - 'title' => $this->t('Storage settings'), - 'weight' => 20, - 'attributes' => ['title' => $this->t('Edit storage settings.')], - 'url' => $entity->toUrl("{$entity->getTargetEntityTypeId()}-storage-edit-form"), - ]; - return $operations; } diff --git a/core/modules/field_ui/src/Form/FieldConfigEditForm.php b/core/modules/field_ui/src/Form/FieldConfigEditForm.php index f18f550c670a209fa62f1b902c1cb5d9549371e3..79b0cc45396288548eaeef0b2cf10fe081e21308 100644 --- a/core/modules/field_ui/src/Form/FieldConfigEditForm.php +++ b/core/modules/field_ui/src/Form/FieldConfigEditForm.php @@ -4,13 +4,16 @@ use Drupal\Core\Entity\EntityDisplayRepositoryInterface; use Drupal\Core\Entity\EntityForm; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageException; 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\Form\SubformState; use Drupal\Core\Render\Element; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\TempStore\PrivateTempStore; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Core\TypedData\TypedDataManagerInterface; @@ -67,12 +70,16 @@ class FieldConfigEditForm extends EntityForm { * The entity display repository. * @param \Drupal\Core\TempStore\PrivateTempStore|null $tempStore * The private tempstore. + * @param \Drupal\Core\Render\ElementInfoManagerInterface|null $elementInfo + * The element info manager. */ public function __construct( EntityTypeBundleInfoInterface $entity_type_bundle_info, protected TypedDataManagerInterface $typedDataManager, protected ?EntityDisplayRepositoryInterface $entityDisplayRepository = NULL, - protected ?PrivateTempStore $tempStore = NULL) { + protected ?PrivateTempStore $tempStore = NULL, + protected ?ElementInfoManagerInterface $elementInfo = NULL, + ) { $this->entityTypeBundleInfo = $entity_type_bundle_info; if ($this->entityDisplayRepository === NULL) { @trigger_error('Calling FieldConfigEditForm::__construct() without the $entityDisplayRepository argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3383771', E_USER_DEPRECATED); @@ -82,6 +89,10 @@ public function __construct( @trigger_error('Calling FieldConfigEditForm::__construct() without the $tempStore argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3383771', E_USER_DEPRECATED); $this->tempStore = \Drupal::service('tempstore.private')->get('field_ui'); } + if ($this->elementInfo === NULL) { + @trigger_error('Calling FieldConfigEditForm::__construct() without the $elementInfo argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3383771', E_USER_DEPRECATED); + $this->elementInfo = \Drupal::service('plugin.manager.element_info'); + } } /** @@ -92,15 +103,27 @@ public static function create(ContainerInterface $container) { $container->get('entity_type.bundle.info'), $container->get('typed_data_manager'), $container->get('entity_display.repository'), - $container->get('tempstore.private')->get('field_ui') + $container->get('tempstore.private')->get('field_ui'), + $container->get('plugin.manager.element_info'), ); } + /** + * {@inheritdoc} + */ + public function getFormId() { + // Ensure that the form ID remains consistent between both 'default' and + // 'edit' operations. This is needed because historically it was only + // possible to edit the field configuration. + return 'field_config_edit_form'; + } + /** * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); + $form['#entity_builders'][] = 'field_form_field_config_edit_form_entity_builder'; $field_storage = $this->entity->getFieldStorageDefinition(); $bundles = $this->entityTypeBundleInfo->getBundleInfo($this->entity->getTargetEntityTypeId()); @@ -150,10 +173,43 @@ public function form(array $form, FormStateInterface $form_state) { 'bundle' => $this->entity->getTargetBundle(), 'entity_id' => NULL, ]; + $form['field_storage'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Field Storage'), + '#weight' => -15, + '#tree' => TRUE, + ]; + $form['field_storage']['subform'] = [ + '#parents' => ['field_storage', 'subform'], + ]; + $form['field_storage']['subform']['field_storage_submit'] = [ + '#type' => 'submit', + '#name' => 'field_storage_submit', + '#attributes' => [ + 'class' => ['js-hide'], + ], + '#value' => $this->t('Update settings'), + '#process' => ['::processFieldStorageSubmit'], + '#limit_validation_errors' => [$form['field_storage']['subform']['#parents']], + '#submit' => ['::fieldStorageSubmit'], + ]; + $field_storage_form = $this->entityTypeManager->getFormObject('field_storage_config', $this->operation); + $field_storage_form->setEntity($field_storage); + $subform_state = SubformState::createForSubform($form['field_storage']['subform'], $form, $form_state, $field_storage_form); + $form['field_storage']['subform'] = $field_storage_form->buildForm($form['field_storage']['subform'], $subform_state, $this->entity); + $form['#entity'] = _field_create_entity_from_ids($ids); - $items = $this->getTypedData($form['#entity']); + $items = $this->getTypedData($this->entity, $form['#entity']); $item = $items->first() ?: $items->appendItem(); + $this->addAjaxCallbacks($form['field_storage']['subform']); + + if (isset($form['field_storage']['subform']['cardinality_container'])) { + $form['field_storage']['subform']['cardinality_container']['#parents'] = [ + 'field_storage', + 'subform', + ]; + } // Add field settings for the field type and a container for third party // settings that modules can add to via hook_form_FORM_ID_alter(). $form['settings'] = [ @@ -168,7 +224,7 @@ public function form(array $form, FormStateInterface $form_state) { // 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']); + $items = $this->getTypedData($this->entity, $form['#entity']); // Add handling for default value. if ($element = $items->defaultValuesForm($form, $form_state)) { @@ -201,10 +257,50 @@ public function form(array $form, FormStateInterface $form_state) { $form['default_value'] = $element; } - + $form['#prefix'] = '<div id="field-combined">'; + $form['#suffix'] = '</div>'; return $form; } + /** + * {@inheritdoc} + */ + public function afterBuild(array $element, FormStateInterface $form_state) { + // Delegate ::afterBuild to the subform. + // @todo remove after https://www.drupal.org/i/3385205 has been addressed. + if (isset($element['field_storage_submit'])) { + $field_storage_form = $this->entityTypeManager->getFormObject('field_storage_config', $this->operation); + $field_storage_form->setEntity($this->entity->getFieldStorageDefinition()); + return $field_storage_form->afterBuild($element, SubformState::createForSubform($element, $form_state->getCompleteForm(), $form_state)); + } + + return parent::afterBuild($element, $form_state); + } + + /** + * {@inheritdoc} + */ + protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) { + parent::copyFormValuesToEntity($entity, $form, $form_state); + + // Update the current field storage instance based on subform state. + if (!empty($form['field_storage']['subform'])) { + $subform_state = SubformState::createForSubform($form['field_storage']['subform'], $form, $form_state); + $field_storage_form = $this->entityTypeManager->getFormObject('field_storage_config', $this->operation); + $field_storage_form->setEntity($entity->getFieldStorageDefinition()); + + $reflector = new \ReflectionObject($entity); + + // Update the field storage entity based on subform values. + $property = $reflector->getProperty('fieldStorage'); + $property->setValue($entity, $field_storage_form->buildEntity($form['field_storage']['subform'], $subform_state)); + + // Remove the item definition to make sure it's not storing stale data. + $property = $reflector->getProperty('itemDefinition'); + $property->setValue($entity, NULL); + } + } + /** * A function to check if element contains any required elements. * @@ -267,16 +363,23 @@ 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); + $field_storage_form = $this->entityTypeManager->getFormObject('field_storage_config', $this->operation); + $field_storage_form->setEntity($this->entity->getFieldStorageDefinition()); + $subform_state = SubformState::createForSubform($form['field_storage']['subform'], $form, $form_state, $field_storage_form); + $field_storage_form->validateForm($form['field_storage']['subform'], $subform_state); + // Make sure that the default value form is validated using the field + // configuration that was just submitted. + $field_config = $this->buildEntity($form, $form_state); if (isset($form['default_value']) && (!isset($form['set_default_value']) || $form_state->getValue('set_default_value'))) { - $items = $this->getTypedData($form['#entity']); + $items = $this->getTypedData($field_config, $form['#entity']); $items->defaultValuesFormValidate($form['default_value'], $form, $form_state); } + + // The form is rendered based on the entity property, meaning that it must + // be updated based on the latest form state even though it might be invalid + // at this point. + $this->entity = $this->buildEntity($form, $form_state); } /** @@ -285,10 +388,22 @@ public function validateForm(array &$form, FormStateInterface $form_state) { public function submitForm(array &$form, FormStateInterface $form_state) { parent::submitForm($form, $form_state); + $field_storage_form = $this->entityTypeManager->getFormObject('field_storage_config', $this->operation); + $field_storage_form->setEntity($this->entity->getFieldStorageDefinition()); + $subform_state = SubformState::createForSubform($form['field_storage']['subform'], $form, $form_state, $field_storage_form); + $field_storage_form->submitForm($form['field_storage']['subform'], $subform_state); + try { + $field_storage_form->save($form['field_storage']['subform'], $subform_state); + } + catch (EntityStorageException $exception) { + $this->handleEntityStorageException($form_state, $exception); + return; + } + // Handle the default value. $default_value = []; if (isset($form['default_value']) && (!isset($form['set_default_value']) || $form_state->getValue('set_default_value'))) { - $items = $this->getTypedData($form['#entity']); + $items = $this->getTypedData($this->entity, $form['#entity']); $default_value = $items->defaultValuesFormSubmit($form['default_value'], $form, $form_state); } $this->entity->setDefaultValue($default_value); @@ -298,44 +413,52 @@ public function submitForm(array &$form, FormStateInterface $form_state) { * {@inheritdoc} */ public function save(array $form, FormStateInterface $form_state) { - $temp_storage = $this->tempStore->get($this->entity->getTargetEntityTypeId() . ':' . $this->entity->getName()); - if ($this->entity->isNew()) { - // @todo remove in https://www.drupal.org/project/drupal/issues/3347291. - if ($temp_storage && $temp_storage['field_storage']->isNew()) { - // Save field storage. - try { - $temp_storage['field_storage']->save(); - } - catch (EntityStorageException $e) { - $this->tempStore->delete($this->entity->getTargetEntityTypeId() . ':' . $this->entity->getName()); - $form_state->setRedirectUrl(FieldUI::getOverviewRouteInfo($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle())); - $this->messenger()->addError($this->t('An error occurred while saving the field: @error', ['@error' => $e->getMessage()])); - return; - } - } - } // Save field config. - $this->entity->save(); - if (isset($form_state->getStorage()['default_options'])) { - $default_options = $form_state->getStorage()['default_options']; - // Configure the default display modes. - $this->entityTypeId = $temp_storage['field_config_values']['entity_type']; - $this->bundle = $temp_storage['field_config_values']['bundle']; - $this->configureEntityFormDisplay($temp_storage['field_config_values']['field_name'], $default_options['entity_form_display'] ?? []); - $this->configureEntityViewDisplay($temp_storage['field_config_values']['field_name'], $default_options['entity_view_display'] ?? []); - // Delete the temp store entry. - $this->tempStore->delete($this->entity->getTargetEntityTypeId() . ':' . $this->entity->getName()); - } + try { + try { + $this->entity->save(); + } + catch (EntityStorageException $exception) { + $this->handleEntityStorageException($form_state, $exception); + return; + } - $this->messenger()->addStatus($this->t('Saved %label configuration.', ['%label' => $this->entity->getLabel()])); + if (isset($form_state->getStorage()['default_options'])) { + $default_options = $form_state->getStorage()['default_options']; + // Configure the default display modes. + $this->entityTypeId = $this->entity->getTargetEntityTypeId(); + $this->bundle = $this->entity->getTargetBundle(); + $this->configureEntityFormDisplay($this->entity->getName(), $default_options['entity_form_display'] ?? []); + $this->configureEntityViewDisplay($this->entity->getName(), $default_options['entity_view_display'] ?? []); + } + + if ($this->entity->isNew()) { + // Delete the temp store entry. + $this->tempStore->delete($this->entity->getTargetEntityTypeId() . ':' . $this->entity->getName()); + } - $request = $this->getRequest(); - if (($destinations = $request->query->all('destinations')) && $next_destination = FieldUI::getNextDestination($destinations)) { - $request->query->remove('destinations'); - $form_state->setRedirectUrl($next_destination); + $this->messenger() + ->addStatus($this->t('Saved %label configuration.', ['%label' => $this->entity->getLabel()])); + + $request = $this->getRequest(); + if (($destinations = $request->query->all('destinations')) && $next_destination = FieldUI::getNextDestination($destinations)) { + $request->query->remove('destinations'); + $form_state->setRedirectUrl($next_destination); + } + else { + $form_state->setRedirectUrl(FieldUI::getOverviewRouteInfo($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle())); + } } - else { - $form_state->setRedirectUrl(FieldUI::getOverviewRouteInfo($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle())); + catch (\Exception $e) { + $this->messenger()->addStatus( + $this->t( + 'Attempt to update field %label failed: %message.', + [ + '%label' => $this->entity->getLabel(), + '%message' => $e->getMessage(), + ] + ) + ); } } @@ -355,14 +478,85 @@ public function getTitle(FieldConfigInterface $field_config) { /** * Gets typed data object for the field. * + * @param \Drupal\field\FieldConfigInterface $field_config + * The field configuration. * @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 { + private function getTypedData(FieldConfigInterface $field_config, FieldableEntityInterface $parent): TypedDataInterface { + // Make sure that typed data manager is re-generating the instance. This + // important because we want the returned instance to match the current + // state, which could be different from what has been stored in config. + $this->typedDataManager->clearCachedDefinitions(); + $entity_adapter = EntityAdapter::createFromEntity($parent); - return $this->typedDataManager->create($this->entity, $this->entity->getDefaultValue($parent), $this->entity->getName(), $entity_adapter); + return $this->typedDataManager->create($field_config, $field_config->getDefaultValue($parent), $field_config->getName(), $entity_adapter); + } + + /** + * Process handler for subform submit. + */ + public static function processFieldStorageSubmit(array $element, FormStateInterface $form_state, &$complete_form) { + // Limit validation errors to the field storage form while the field storage + // form is being edited. + $complete_form['#limit_validation_errors'] = [array_slice($element['#parents'], 0, -1)]; + return $element; + } + + /** + * Submit handler for subform submit. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + public function fieldStorageSubmit(&$form, FormStateInterface $form_state) { + // The default value widget needs to be regenerated. + $form_storage = &$form_state->getStorage(); + unset($form_storage['default_value_widget']); + $form_state->setRebuild(); + } + + /** + * Add Ajax callback for all inputs. + * + * @param array $form + * An associative array containing the structure of the form. + */ + private function addAjaxCallbacks(array &$form): void { + if (isset($form['#type']) && !isset($form['#ajax'])) { + if ($this->elementInfo->getInfoProperty($form['#type'], '#input') && !$this->elementInfo->getInfoProperty($form['#type'], '#is_button')) { + $form['#ajax'] = [ + 'trigger_as' => ['name' => 'field_storage_submit'], + 'wrapper' => 'field-combined', + 'event' => 'change', + ]; + } + } + + foreach (Element::children($form) as $child_key) { + $this->addAjaxCallbacks($form[$child_key]); + } + } + + /** + * Handles entity storage exceptions and redirects the form. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * @param \Drupal\Core\Entity\EntityStorageException $exception + * The exception. + */ + protected function handleEntityStorageException(FormStateInterface $form_state, EntityStorageException $exception): void { + $this->tempStore->delete($this->entity->getTargetEntityTypeId() . ':' . $this->entity->getName()); + $form_state->setRedirectUrl(FieldUI::getOverviewRouteInfo($this->entity->getTargetEntityTypeId(), + $this->entity->getTargetBundle())); + $this->messenger() + ->addError($this->t('An error occurred while saving the field: @error', + ['@error' => $exception->getMessage()])); } } diff --git a/core/modules/field_ui/src/Form/FieldStorageAddForm.php b/core/modules/field_ui/src/Form/FieldStorageAddForm.php index ea1f2f94436e4bfb087d1938b6e0b3c7e2f46e16..560e8d7c7268440d3f14419d27a340d1d937f11b 100644 --- a/core/modules/field_ui/src/Form/FieldStorageAddForm.php +++ b/core/modules/field_ui/src/Form/FieldStorageAddForm.php @@ -462,10 +462,6 @@ public function submitForm(array &$form, FormStateInterface $form_state) { 'entity_type' => $this->entityTypeId, 'field_name' => $field_name, ] + FieldUI::getRouteBundleParameter($entity_type, $this->bundle); - $destinations[] = [ - 'route_name' => "field_ui.field_storage_add_{$this->entityTypeId}", - 'route_parameters' => $route_parameters, - ]; $destinations[] = [ 'route_name' => "field_ui.field_add_{$this->entityTypeId}", 'route_parameters' => $route_parameters, diff --git a/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php b/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php index f83c7d3e9e75d1a1608a191b0486bed6843449b3..c6192738fa0273c4809dc697b8bb70e25bc61294 100644 --- a/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php +++ b/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php @@ -6,11 +6,11 @@ use Drupal\Core\Entity\Plugin\DataType\EntityAdapter; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Form\SubformStateInterface; use Drupal\Core\Routing\RouteMatchInterface; -use Drupal\Core\TempStore\PrivateTempStore; use Drupal\Core\TypedData\TypedDataManagerInterface; use Drupal\field\Entity\FieldConfig; -use Drupal\field_ui\FieldUI; +use Drupal\field\FieldConfigInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -33,27 +33,17 @@ class FieldStorageConfigEditForm extends EntityForm { * * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typedDataManager * The typed data manager. - * @param \Drupal\Core\TempStore\PrivateTempStore|null $tempStore - * The private tempstore. */ public function __construct( protected TypedDataManagerInterface $typedDataManager, - protected ?PrivateTempStore $tempStore = NULL, ) { - if ($this->tempStore === NULL) { - @trigger_error('Calling FieldStorageConfigEditForm::__construct() without the $tempStore argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3383720', E_USER_DEPRECATED); - $this->tempStore = \Drupal::service('tempstore.private')->get('field_ui'); - } } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { - return new static( - $container->get('typed_data_manager'), - $container->get('tempstore.private')->get('field_ui') - ); + return new static($container->get('typed_data_manager')); } /** @@ -77,12 +67,15 @@ public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entit * A nested array form elements comprising the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. - * @param string $field_config + * @param \Drupal\field\FieldConfigInterface|string $field_config * The ID of the field config whose field storage config is being edited. */ - public function buildForm(array $form, FormStateInterface $form_state, $field_config = NULL) { + public function buildForm(array $form, FormStateInterface $form_state, FieldConfigInterface|string $field_config = NULL) { if ($field_config) { - $field = FieldConfig::load($field_config); + $field = $field_config; + if (is_string($field)) { + $field = FieldConfig::load($field_config); + } $form_state->set('field_config', $field); $form_state->set('entity_type_id', $field->getTargetEntityTypeId()); @@ -96,11 +89,9 @@ public function buildForm(array $form, FormStateInterface $form_state, $field_co * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { - $temp_storage = $this->tempStore->get($this->entity->getTargetEntityTypeId() . ':' . $this->entity->getName()); $form = parent::form($form, $form_state); - $field_label = $this->entity->isNew() ? $temp_storage['field_config_values']['label'] : $form_state->get('field_config')->label(); - $form['#title'] = $field_label; + $field_label = $form_state->get('field_config')->label(); $form['#prefix'] = '<p>' . $this->t('These settings apply to the %field field everywhere it is used. Some also impact the way that data is stored and cannot be changed once data has been created.', ['%field' => $field_label]) . '</p>'; // Add settings provided by the field module. The field module is @@ -122,12 +113,7 @@ public function form(array $form, FormStateInterface $form_state) { $items = $entity->get($this->entity->getName()); } else { - // Create a temporary field config so that we can access the field - // definition. - $field_config = $this->entityTypeManager->getStorage('field_config')->create([ - ...$temp_storage['field_config_values'], - 'field_storage' => $temp_storage['field_storage'], - ]); + $field_config = $form_state->get('field_config'); $items = $this->typedDataManager->create($field_config, name: $this->entity->getName(), parent: EntityAdapter::createFromEntity($entity)); } $item = $items->first() ?: $items->appendItem(); @@ -170,7 +156,7 @@ protected function getCardinalityForm() { $form['cardinality'] = ['#markup' => $markup]; } else { - $form['#element_validate'][] = '::validateCardinality'; + $form['#element_validate'][] = [$this, 'validateCardinality']; $cardinality = $this->entity->getCardinality(); $form['cardinality'] = [ '#type' => 'select', @@ -191,10 +177,10 @@ protected function getCardinalityForm() { '#size' => 2, '#states' => [ 'visible' => [ - ':input[name="cardinality"]' => ['value' => 'number'], + ':input[name="field_storage[subform][cardinality]"]' => ['value' => 'number'], ], 'disabled' => [ - ':input[name="cardinality"]' => ['value' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED], + ':input[id="field_storage[subform][cardinality]"]' => ['value' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED], ], ], ]; @@ -207,9 +193,13 @@ protected function getCardinalityForm() { * {@inheritdoc} */ protected function actions(array $form, FormStateInterface $form_state) { + if ($form_state instanceof SubformStateInterface) { + return []; + } $elements = parent::actions($form, $form_state); $elements['submit']['#value'] = $this->entity->isNew() ? $this->t('Continue') : $this->t('Save'); + @trigger_error('Rendering ' . __CLASS__ . ' outside of a subform is deprecated in drupal:10.2.0 and is removed in drupal:11.0.0. See https://www.drupal.org/node/3391538', E_USER_DEPRECATED); return $elements; } @@ -224,24 +214,33 @@ protected function actions(array $form, FormStateInterface $form_state) { public function validateCardinality(array &$element, FormStateInterface $form_state) { $field_storage_definitions = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($this->entity->getTargetEntityTypeId()); + $cardinality = $form_state->getValue([ + ...$element['#parents'], + 'cardinality', + ]); + $cardinality_number = $form_state->getValue([ + ...$element['#parents'], + 'cardinality_number', + ]); + // Validate field cardinality. - if ($form_state->getValue('cardinality') === 'number' && !$form_state->getValue('cardinality_number')) { + if ($cardinality === 'number' && !$cardinality_number) { $form_state->setError($element['cardinality_number'], $this->t('Number of values is required.')); } // If a specific cardinality is used, validate that there are no entities // with a higher delta. - elseif (!$this->entity->isNew() && isset($field_storage_definitions[$this->entity->getName()]) && $form_state->getValue('cardinality') != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) { + elseif (!$this->entity->isNew() && isset($field_storage_definitions[$this->entity->getName()]) && $cardinality != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) { // Get a count of entities that have a value in a delta higher than the // one selected. Deltas start with 0, so the selected value does not // need to be incremented. $entities_with_higher_delta = \Drupal::entityQuery($this->entity->getTargetEntityTypeId()) ->accessCheck(FALSE) - ->condition($this->entity->getName() . '.%delta', $form_state->getValue('cardinality')) + ->condition($this->entity->getName() . '.%delta', $cardinality_number) ->count() ->execute(); if ($entities_with_higher_delta) { - $form_state->setError($element['cardinality_number'], $this->formatPlural($entities_with_higher_delta, 'There is @count entity with @delta or more values in this field, so the allowed number of values cannot be set to @allowed.', 'There are @count entities with @delta or more values in this field, so the allowed number of values cannot be set to @allowed.', ['@delta' => $form_state->getValue('cardinality') + 1, '@allowed' => $form_state->getValue('cardinality')])); + $form_state->setError($element['cardinality_number'], $this->formatPlural($entities_with_higher_delta, 'There is @count entity with @delta or more values in this field, so the allowed number of values cannot be set to @allowed.', 'There are @count entities with @delta or more values in this field, so the allowed number of values cannot be set to @allowed.', ['@delta' => $cardinality_number + 1, '@allowed' => $cardinality_number])); } } } @@ -252,7 +251,7 @@ public function validateCardinality(array &$element, FormStateInterface $form_st public function buildEntity(array $form, FormStateInterface $form_state) { // Save field cardinality. if (!$this->getEnforcedCardinality() && $form_state->getValue('cardinality') === 'number' && $form_state->getValue('cardinality_number')) { - $form_state->setValue('cardinality', $form_state->getValue('cardinality_number')); + $form_state->setValue('cardinality', (int) $form_state->getValue('cardinality_number')); } return parent::buildEntity($form, $form_state); @@ -262,31 +261,7 @@ public function buildEntity(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function save(array $form, FormStateInterface $form_state) { - // Save field storage entity values in tempstore. - if ($this->entity->isNew()) { - $temp_storage = $this->tempStore->get($this->entity->getTargetEntityTypeId() . ':' . $this->entity->getName()); - $field_label = $temp_storage['field_config_values']['label']; - $temp_storage['field_storage'] = $this->entity; - $this->tempStore->set($this->entity->getTargetEntityTypeId() . ':' . $this->entity->getName(), $temp_storage); - } - try { - if (!$this->entity->isNew()) { - $field_label = $form_state->get('field_config')->label(); - $this->entity->save(); - $this->messenger()->addMessage($this->t('Your settings have been saved.')); - } - $request = $this->getRequest(); - if (($destinations = $request->query->all('destinations')) && $next_destination = FieldUI::getNextDestination($destinations)) { - $request->query->remove('destinations'); - $form_state->setRedirectUrl($next_destination); - } - else { - $form_state->setRedirectUrl(FieldUI::getOverviewRouteInfo($form_state->get('entity_type_id'), $form_state->get('bundle'))); - } - } - catch (\Exception $e) { - $this->messenger()->addStatus($this->t('Attempt to update field %label failed: %message.', ['%label' => $field_label, '%message' => $e->getMessage()])); - } + $this->entity->save(); } /** diff --git a/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalTask.php b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalTask.php index deff201664774b5f6d40be4cb8b7b7a3022fe9cc..af4c5674d132058873934eac7c48b6b1db93ded1 100644 --- a/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalTask.php +++ b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalTask.php @@ -107,13 +107,6 @@ public function getDerivativeDefinitions($base_plugin_definition) { 'base_route' => "entity.field_config.{$entity_type_id}_field_edit_form", ]; - // Field settings tab. - $this->derivatives["field_storage_$entity_type_id"] = [ - 'route_name' => "entity.field_config.{$entity_type_id}_storage_edit_form", - 'title' => $this->t('Field settings'), - 'base_route' => "entity.field_config.{$entity_type_id}_field_edit_form", - ]; - // View and form modes secondary tabs. // The same base $path for the menu item (with a placeholder) can be // used for all bundles of a given entity type; but depending on diff --git a/core/modules/field_ui/src/Routing/RouteSubscriber.php b/core/modules/field_ui/src/Routing/RouteSubscriber.php index affe1ea2c732b0a6d8e7be83f107ce5839d99d8d..52968245e74ca76bc5265065fd4b908295f07c05 100644 --- a/core/modules/field_ui/src/Routing/RouteSubscriber.php +++ b/core/modules/field_ui/src/Routing/RouteSubscriber.php @@ -6,7 +6,6 @@ use Drupal\Core\Routing\RouteSubscriberBase; use Drupal\Core\Routing\RoutingEvents; use Drupal\field_ui\Controller\FieldConfigAddController; -use Drupal\field_ui\Controller\FieldStorageAddController; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -73,14 +72,6 @@ protected function alterRoutes(RouteCollection $collection) { ); $collection->add("entity.field_config.{$entity_type_id}_field_edit_form", $route); - $route = new Route( - "$path/fields/{field_config}/storage", - ['_entity_form' => 'field_storage_config.edit'] + $defaults, - ['_permission' => 'administer ' . $entity_type_id . ' fields'], - $options - ); - $collection->add("entity.field_config.{$entity_type_id}_storage_edit_form", $route); - $route = new Route( "$path/fields/{field_config}/delete", ['_entity_form' => 'field_config.delete'] + $defaults, @@ -122,18 +113,6 @@ protected function alterRoutes(RouteCollection $collection) { ); $collection->add("field_ui.field_add_$entity_type_id", $route); - // @todo remove in https://www.drupal.org/project/drupal/issues/3347291. - $route = new Route( - "$path/add-storage/{entity_type}/{field_name}", - [ - '_controller' => FieldStorageAddController::class . '::storageAddConfigureForm', - '_title' => 'Add storage', - ] + $defaults, - ['_permission' => 'administer ' . $entity_type_id . ' fields'], - $options - ); - $collection->add("field_ui.field_storage_add_$entity_type_id", $route); - $route = new Route( "$path/fields/reuse", [ diff --git a/core/modules/field_ui/tests/modules/field_ui_test_deprecated/field_ui_test_deprecated.info.yml b/core/modules/field_ui/tests/modules/field_ui_test_deprecated/field_ui_test_deprecated.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..09e77b8e5733cb9294662a9cb4823c6d0331f78c --- /dev/null +++ b/core/modules/field_ui/tests/modules/field_ui_test_deprecated/field_ui_test_deprecated.info.yml @@ -0,0 +1,5 @@ +name: 'Field UI test deprecated' +type: module +description: 'Support module for testing deprecated Field UI functionality.' +package: Testing +version: VERSION diff --git a/core/modules/field_ui/tests/modules/field_ui_test_deprecated/field_ui_test_deprecated.module b/core/modules/field_ui/tests/modules/field_ui_test_deprecated/field_ui_test_deprecated.module new file mode 100644 index 0000000000000000000000000000000000000000..81f4a774b9bb3b107aff28e7ecb6b09d5c4e3b51 --- /dev/null +++ b/core/modules/field_ui/tests/modules/field_ui_test_deprecated/field_ui_test_deprecated.module @@ -0,0 +1,29 @@ +<?php + +/** + * @file + * Field UI test module. + */ + +use Drupal\Core\Form\FormStateInterface; +use Drupal\field\FieldStorageConfigInterface; +use Drupal\field_ui\Form\FieldStorageConfigEditForm; + +/** + * Implements hook_form_FORM_ID_alter() for field_storage_config_edit_form. + */ +function field_ui_test_deprecated_form_field_storage_config_edit_form_alter(&$form, FormStateInterface $form_state) { + if (!($form_state->getFormObject() instanceof FieldStorageConfigEditForm)) { + throw new \LogicException('field_storage_config_edit_form() expects to get access to the field storage config entity edit form.'); + } + if (!($form_state->getFormObject()->getEntity() instanceof FieldStorageConfigInterface)) { + throw new \LogicException('field_storage_config_edit_form() expects to get access to the field storage config entity.'); + } + if (!isset($form['cardinality_container']['cardinality'])) { + throw new \LogicException('field_storage_config_edit_form() expects to that the cardinality container with the cardinality form element exists.'); + } + + $form['cardinality_container']['hello'] = [ + '#markup' => 'Greetings from the field_storage_config_edit_form() alter.', + ]; +} diff --git a/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php b/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php index dec8e17d64ad914e38f34a4a03ec756170eec711..349a6e5d118513ef16c5c877e59d7fe8be84a86c 100644 --- a/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php +++ b/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php @@ -257,10 +257,10 @@ public function testExternalDestinations() { $options = [ 'query' => ['destinations' => ['http://example.com']], ]; - $this->drupalGet('admin/structure/types/manage/article/fields/node.article.body/storage', $options); - $this->submitForm([], 'Save'); + $this->drupalGet('admin/structure/types/manage/article/fields/node.article.body', $options); + $this->submitForm([], 'Save settings'); // The external redirect should not fire. - $this->assertSession()->addressEquals('admin/structure/types/manage/article/fields/node.article.body/storage?destinations%5B0%5D=http%3A//example.com'); + $this->assertSession()->addressEquals('admin/structure/types/manage/article/fields/node.article.body?destinations%5B0%5D=http%3A//example.com'); $this->assertSession()->statusCodeEquals(200); $this->assertSession()->responseContains('Attempt to update field <em class="placeholder">Body</em> failed: <em class="placeholder">The internal path component 'http://example.com' is external. You are not allowed to specify an external URL together with internal:/.</em>.'); } @@ -369,9 +369,33 @@ public function testNonExistentFieldUrls() { $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id); $this->assertSession()->statusCodeEquals(404); + } - $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id . '/storage'); - $this->assertSession()->statusCodeEquals(404); + /** + * Tests that the 'field_prefix' setting works on Field UI. + */ + public function testFieldPrefix() { + // Change default field prefix. + $field_prefix = $this->randomMachineName(10); + $this->config('field_ui.settings')->set('field_prefix', $field_prefix)->save(); + + // Create a field input and label exceeding the new maxlength, which is 22. + $field_exceed_max_length_label = $this->randomString(23); + $field_exceed_max_length_input = $this->randomMachineName(23); + + // Try to create the field. + $edit = [ + 'label' => $field_exceed_max_length_label, + 'field_name' => $field_exceed_max_length_input, + ]; + $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/add-field'); + $this->submitForm($edit, 'Continue'); + $this->assertSession()->pageTextContains('Machine-readable name cannot be longer than 22 characters but is currently 23 characters long.'); + + // Create a valid field. + $this->fieldUIAddNewField('admin/structure/types/manage/' . $this->contentType, $this->fieldNameInput, $this->fieldLabel); + $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/node.' . $this->contentType . '.' . $field_prefix . $this->fieldNameInput); + $this->assertSession()->pageTextContains($this->fieldLabel . ' settings for ' . $this->contentType); } /** diff --git a/core/modules/field_ui/tests/src/Functional/ManageFieldsLifecycleTest.php b/core/modules/field_ui/tests/src/Functional/ManageFieldsLifecycleTest.php index f3d8ce5ea7e702b59daba593afded18c9850d6da..d18854a18b382ac98fdb462fc559117914bea16a 100644 --- a/core/modules/field_ui/tests/src/Functional/ManageFieldsLifecycleTest.php +++ b/core/modules/field_ui/tests/src/Functional/ManageFieldsLifecycleTest.php @@ -51,7 +51,7 @@ protected function manageFieldsPage($type = '') { $this->assertSession()->linkExists('Create a new field'); // Assert entity operations for all fields. - $number_of_links = 3; + $number_of_links = 2; $number_of_links_found = 0; $operation_links = $this->xpath('//ul[@class = "dropbutton"]/li/a'); $url = base_path() . "admin/structure/types/manage/$type/fields/node.$type.body"; @@ -63,11 +63,6 @@ protected function manageFieldsPage($type = '') { $number_of_links_found++; break; - case 'Edit storage settings.': - $this->assertSame("$url/storage", $link->getAttribute('href')); - $number_of_links_found++; - break; - case 'Delete field.': $this->assertSame("$url/delete", $link->getAttribute('href')); $number_of_links_found++; @@ -95,20 +90,14 @@ protected function createField() { protected function updateField() { $field_id = 'node.' . $this->contentType . '.' . $this->fieldName; // Go to the field edit page. - $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id . '/storage'); + $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id); $this->assertSession()->assertEscaped($this->fieldLabel); // Populate the field settings with new settings. $string = 'updated dummy test string'; - $edit = [ - 'settings[test_field_storage_setting]' => $string, - ]; - $this->submitForm($edit, 'Save'); - - // Go to the field edit page. - $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id); $edit = [ 'settings[test_field_setting]' => $string, + 'field_storage[subform][settings][test_field_storage_setting]' => $string, ]; $this->assertSession()->pageTextContains('Default value'); $this->submitForm($edit, 'Save settings'); @@ -145,34 +134,28 @@ protected function addExistingField() { * numeric value. That is tested already in FormTest::testNumber(). */ protected function cardinalitySettings() { - $field_edit_path = 'admin/structure/types/manage/article/fields/node.article.body/storage'; + $field_edit_path = 'admin/structure/types/manage/article/fields/node.article.body'; // Assert the cardinality other field cannot be empty when cardinality is // set to 'number'. $edit = [ - 'cardinality' => 'number', - 'cardinality_number' => '', + 'field_storage[subform][cardinality]' => 'number', + 'field_storage[subform][cardinality_number]' => '', ]; $this->drupalGet($field_edit_path); - $this->submitForm($edit, 'Save'); + $this->submitForm($edit, 'Update settings'); $this->assertSession()->pageTextContains('Number of values is required.'); // Submit a custom number. $edit = [ - 'cardinality' => 'number', - 'cardinality_number' => 6, + 'field_storage[subform][cardinality]' => 'number', + 'field_storage[subform][cardinality_number]' => 6, ]; + $this->submitForm($edit, 'Update settings'); + $this->submitForm([], 'Save settings'); $this->drupalGet($field_edit_path); - $this->submitForm($edit, 'Save'); - $this->drupalGet($field_edit_path); - $this->assertSession()->fieldValueEquals('cardinality', 'number'); - $this->assertSession()->fieldValueEquals('cardinality_number', 6); - - // Check that tabs displayed. - $this->assertSession()->linkExists('Edit'); - $this->assertSession()->linkByHrefExists('admin/structure/types/manage/article/fields/node.article.body'); - $this->assertSession()->linkExists('Field settings'); - $this->assertSession()->linkByHrefExists($field_edit_path); + $this->assertSession()->fieldValueEquals('field_storage[subform][cardinality]', 'number'); + $this->assertSession()->fieldValueEquals('field_storage[subform][cardinality_number]', 6); // Add two entries in the body. $edit = ['title[0][value]' => 'Cardinality', 'body[0][value]' => 'Body 1', 'body[1][value]' => 'Body 2']; @@ -182,11 +165,11 @@ protected function cardinalitySettings() { // Assert that you can't set the cardinality to a lower number than the // highest delta of this field. $edit = [ - 'cardinality' => 'number', - 'cardinality_number' => 1, + 'field_storage[subform][cardinality]' => 'number', + 'field_storage[subform][cardinality_number]' => 1, ]; $this->drupalGet($field_edit_path); - $this->submitForm($edit, 'Save'); + $this->submitForm($edit, 'Update settings'); $this->assertSession()->pageTextContains("There is 1 entity with 2 or more values in this field"); // Create a second entity with three values. @@ -196,47 +179,49 @@ protected function cardinalitySettings() { // Set to unlimited. $edit = [ - 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, + 'field_storage[subform][cardinality]' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, ]; $this->drupalGet($field_edit_path); - $this->submitForm($edit, 'Save'); + $this->submitForm($edit, 'Update settings'); + $this->submitForm([], 'Save settings'); $this->drupalGet($field_edit_path); - $this->assertSession()->fieldValueEquals('cardinality', FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); - $this->assertSession()->fieldValueEquals('cardinality_number', 1); + $this->assertSession()->fieldValueEquals('field_storage[subform][cardinality]', FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); + $this->assertSession()->fieldValueEquals('field_storage[subform][cardinality_number]', 1); // Assert that you can't set the cardinality to a lower number then the // highest delta of this field but can set it to the same. $edit = [ - 'cardinality' => 'number', - 'cardinality_number' => 1, + 'field_storage[subform][cardinality]' => 'number', + 'field_storage[subform][cardinality_number]' => 1, ]; $this->drupalGet($field_edit_path); - $this->submitForm($edit, 'Save'); + $this->submitForm($edit, 'Update settings'); + $this->submitForm([], 'Save settings'); $this->assertSession()->pageTextContains("There are 2 entities with 2 or more values in this field"); $edit = [ - 'cardinality' => 'number', - 'cardinality_number' => 2, + 'field_storage[subform][cardinality]' => 'number', + 'field_storage[subform][cardinality_number]' => 2, ]; $this->drupalGet($field_edit_path); - $this->submitForm($edit, 'Save'); + $this->submitForm($edit, 'Update settings'); $this->assertSession()->pageTextContains("There is 1 entity with 3 or more values in this field"); $edit = [ - 'cardinality' => 'number', - 'cardinality_number' => 3, + 'field_storage[subform][cardinality]' => 'number', + 'field_storage[subform][cardinality_number]' => 3, ]; $this->drupalGet($field_edit_path); - $this->submitForm($edit, 'Save'); + $this->submitForm($edit, 'Update settings'); // Test the cardinality validation is not access sensitive. // Remove the cardinality limit 4 so we can add a node the user doesn't have access to. $edit = [ - 'cardinality' => (string) FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, + 'field_storage[subform][cardinality]' => (string) FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, ]; $this->drupalGet($field_edit_path); - $this->submitForm($edit, 'Save'); + $this->submitForm($edit, 'Update settings'); $node = $this->drupalCreateNode([ 'private' => TRUE, 'uid' => 0, @@ -253,25 +238,26 @@ protected function cardinalitySettings() { // set it to the same. $this->drupalGet($field_edit_path); $edit = [ - 'cardinality' => 'number', - 'cardinality_number' => 2, + 'field_storage[subform][cardinality]' => 'number', + 'field_storage[subform][cardinality_number]' => 2, ]; $this->drupalGet($field_edit_path); - $this->submitForm($edit, 'Save'); + $this->submitForm($edit, 'Update settings'); $this->assertSession()->pageTextContains("There are 2 entities with 3 or more values in this field"); $edit = [ - 'cardinality' => 'number', - 'cardinality_number' => 3, + 'field_storage[subform][cardinality]' => 'number', + 'field_storage[subform][cardinality_number]' => 3, ]; $this->drupalGet($field_edit_path); - $this->submitForm($edit, 'Save'); + $this->submitForm($edit, 'Update settings'); $this->assertSession()->pageTextContains("There is 1 entity with 4 or more values in this field"); $edit = [ - 'cardinality' => 'number', - 'cardinality_number' => 4, + 'field_storage[subform][cardinality]' => 'number', + 'field_storage[subform][cardinality_number]' => 4, ]; $this->drupalGet($field_edit_path); - $this->submitForm($edit, 'Save'); + $this->submitForm($edit, 'Update settings'); + $this->submitForm([], 'Save settings'); } /** diff --git a/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php b/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php index 9a5c6de5487dc224c5247b7f6ac986a34f413264..dafe0df15daebc10d90a19c7749a797f0fc417b9 100644 --- a/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php +++ b/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php @@ -151,14 +151,12 @@ public function testAddField() { $this->assertSession()->statusMessageNotContains('Saved'); // Change the storage form values. - $edit = ['cardinality_number' => 5]; - $this->submitForm($edit, 'Continue'); + $edit = ['field_storage[subform][cardinality_number]' => 5]; + $this->submitForm($edit, 'Update settings'); $this->assertSession()->statusMessageNotContains('Saved'); - // Go back to the field storage form. - $this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/add-storage/node/field_test_field'); // Assert that the form values persist. - $this->assertEquals(5, $page->findField('cardinality_number')->getValue()); + $this->assertEquals(5, $page->findField('field_storage[subform][cardinality_number]')->getValue()); // Try creating a field with the same machine name. $this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/fields/add-field'); @@ -169,10 +167,10 @@ public function testAddField() { ]; $this->submitForm($edit, 'Continue'); // Assert that the values in the field storage form are reset. - $this->assertEquals(1, $page->findField('cardinality_number')->getValue()); + $this->assertEquals(1, $page->findField('field_storage[subform][cardinality_number]')->getValue()); // Assert that the field is created with the new settings. - $this->submitForm([], 'Continue'); + $this->submitForm([], 'Update settings'); $this->assertSession()->statusMessageNotContains('Saved'); $this->submitForm([], 'Save settings'); $this->assertSession()->statusMessageContains('Saved'); @@ -202,9 +200,9 @@ public function testAddFieldWithMultipleUsers() { ]; $this->submitForm($edit, 'Continue'); // Make changes to the storage form. - $edit = ['cardinality_number' => 5]; + $edit = ['field_storage[subform][cardinality_number]' => 5]; $storage_form_url = $this->getUrl(); - $this->submitForm($edit, 'Continue'); + $this->submitForm($edit, 'Update settings'); $this->drupalLogout(); // Actually add a field as user 2. @@ -216,11 +214,11 @@ public function testAddFieldWithMultipleUsers() { 'new_storage_type' => 'test_field', ]; $this->submitForm($edit, 'Continue'); - $allowed_no_of_values = $page->findField('cardinality_number')->getValue(); + $allowed_no_of_values = $page->findField('field_storage[subform][cardinality_number]')->getValue(); // Assert that the changes made by any user do not affect other users until // the field is saved. $this->assertEquals(1, $allowed_no_of_values); - $this->submitForm(['cardinality_number' => 2], 'Continue'); + $this->submitForm(['field_storage[subform][cardinality_number]' => 2], 'Update settings'); $this->submitForm([], 'Save settings'); $this->assertSession()->pageTextContains("Saved Test field configuration."); $this->drupalLogout(); @@ -228,7 +226,6 @@ public function testAddFieldWithMultipleUsers() { // Continue adding a field as user 1, using the URL saved previously. $this->drupalLogin($user1); $this->drupalGet($storage_form_url); - $this->submitForm([], 'Continue'); // Assert that the user can go on with configuring a field with a machine // that is already taken. $this->assertSession()->pageTextNotContains('error'); @@ -275,10 +272,6 @@ public function testEditFieldWithLeftOverFieldInTempStore() { ]) ->save(); - $this->drupalGet("$bundle_path/fields/node.{$node_type->id()}.test_field/storage"); - $this->submitForm([], 'Save'); - $this->assertSession()->statusMessageContains('Your settings have been saved.', 'status'); - $this->drupalGet("$bundle_path/fields/node.{$node_type->id()}.test_field"); $this->submitForm([], 'Save settings'); $this->assertSession()->statusMessageContains('Saved test_field configuration.', 'status'); @@ -304,4 +297,20 @@ public function testEntityReferenceToNonBundleableEntity() { $this->assertEquals([['target_id' => $this->adminUser->id()]], $field->getDefaultValue(User::create(['name' => '1337']))); } + /** + * Tests hook_form_field_storage_config_form_edit_alter(). + * + * @group legacy + */ + public function testFieldStorageFormAlter() { + $this->container->get('module_installer')->install(['field_ui_test_deprecated']); + $this->rebuildContainer(); + + $node_type = $this->drupalCreateContentType(); + $bundle = $node_type->id(); + $this->expectDeprecation('The deprecated alter hook hook_form_field_storage_config_edit_form_alter() is implemented in these functions: field_ui_test_deprecated_form_field_storage_config_edit_form_alter. Use hook_form_field_config_edit_form_alter() instead. See https://www.drupal.org/node/3386675.'); + $this->drupalGet("/admin/structure/types/manage/$bundle/fields/node.$bundle.body"); + $this->assertSession()->elementTextContains('css', '#edit-field-storage', 'Greetings from the field_storage_config_edit_form() alter.'); + } + } diff --git a/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php b/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php index 3db32a07236dae766271f13a03c91bcde86c6c32..4be9ffcf2faf1e04050ff3e1bfbe48af4be33828 100644 --- a/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php +++ b/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php @@ -212,9 +212,49 @@ public function testAddField() { $text_plain->click(); $this->assertTrue($assert_session->elementExists('css', '[name="group_field_options_wrapper"][value="string"]')->isSelected()); $page->pressButton('Continue'); - $this->assertMatchesRegularExpression('/.*article\/add-storage\/node\/field_test_field_1.*/', $this->getUrl()); - $page->pressButton('Continue'); + $this->assertMatchesRegularExpression('/.*article\/add-field\/node\/field_test_field_1.*/', $this->getUrl()); + + // Ensure the default value is reloaded when the field storage settings + // are changed. + $default_input_1_name = 'default_value_input[field_test_field_1][0][value]'; + $default_input_1 = $assert_session->fieldExists($default_input_1_name); + $this->assertFalse($default_input_1->isVisible()); + + $default_value = $assert_session->fieldExists('set_default_value'); + $default_value->check(); + $assert_session->waitForElementVisible('xpath', $default_value->getXpath()); + $default_input_1->setValue('There can be only one!'); + $default_input_2_name = 'default_value_input[field_test_field_1][1][value]'; + $assert_session->fieldNotExists($default_input_2_name); + $cardinality = $assert_session->fieldExists('field_storage[subform][cardinality_number]'); + $cardinality->setValue(2); + $default_input_2 = $assert_session->waitForField($default_input_2_name); + // Ensure the default value for first input is retained. + $assert_session->fieldValueEquals($default_input_1_name, 'There can be only one!'); + $page->findField($default_input_2_name)->setValue('But maybe also two?'); + $cardinality->setValue('1'); + $assert_session->assertWaitOnAjaxRequest(); + $assert_session->waitForElementRemoved('xpath', $default_input_2->getXpath()); + // Ensure the first input retains its value. + $assert_session->fieldValueEquals($default_input_1_name, 'There can be only one!'); + $cardinality->setValue(2); + $assert_session->waitForField($default_input_2_name); + // Ensure when the second input is added again it does not retain its value. + $assert_session->fieldValueEquals($default_input_2_name, ''); + + // Ensure changing the max length input will also reload the form. + $max_length_input = $assert_session->fieldExists('field_storage[subform][settings][max_length]'); + $this->assertSame('255', $max_length_input->getValue()); + $this->assertSame('255', $default_input_1->getAttribute('maxlength')); + $max_length_input->setValue('5'); + $page->waitFor(5, function () use ($default_input_1) { + return $default_input_1->getAttribute('maxlength') === '5'; + }); + $this->assertSame('5', $default_input_1->getAttribute('maxlength')); + // Set a default value that is under the new limit. + $default_input_1->setValue('Five!'); + $page->pressButton('Save settings'); $assert_session->pageTextContains('Saved ' . $field_name . ' configuration.'); $this->assertNotNull($field_storage = FieldStorageConfig::loadByName('node', "field_$field_name")); @@ -240,8 +280,6 @@ public function testAddField() { $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="test_field"]')->isSelected()); $assert_session->pageTextNotContains('Choose an option below'); - $page->pressButton('Continue'); - $this->assertMatchesRegularExpression('/.*article\/add-storage\/node\/field_test_field_2.*/', $this->getUrl()); $page->pressButton('Continue'); $this->assertMatchesRegularExpression('/.*article\/add-field\/node\/field_test_field_2.*/', $this->getUrl()); $page->pressButton('Save settings'); diff --git a/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php b/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php index 1a4625095d6affdc2b8a9cb3cf4877bfe082474f..2630b36815b76c3876f328781542852b4a38f47e 100644 --- a/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php +++ b/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php @@ -64,10 +64,7 @@ public function fieldUIAddNewFieldJS(?string $bundle_path, string $field_name, ? $page->findButton('Continue')->click(); $assert_session->waitForText("These settings apply to the $label field everywhere it is used."); if ($save_settings) { - // Second step: 'Storage settings' form. - $page->findButton('Continue')->click(); - - // Third step: 'Field settings' form. + // Second step: Save field settings. $page->findButton('Save settings')->click(); $assert_session->pageTextContains("Saved $label configuration."); diff --git a/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php b/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php index 9a01349a30770de684ca8c83516e86652ca735dc..c73388645f7ff9ce79267d48e13e16892f9466a9 100644 --- a/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php +++ b/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php @@ -85,9 +85,7 @@ public function fieldUIAddNewField($bundle_path, $field_name, $label = NULL, $fi $this->getSession()->getPage()->findLink($label); // Second step: 'Storage settings' form. - $this->submitForm($storage_edit, 'Continue'); - // Assert that the field is not created. - $this->assertFieldDoesNotExist($bundle_path, $label); + $this->submitForm($storage_edit, 'Update settings'); // Third step: 'Field settings' form. $this->submitForm($field_edit, 'Save settings'); diff --git a/core/modules/field_ui/tests/src/Unit/FieldConfigEditFormTest.php b/core/modules/field_ui/tests/src/Unit/FieldConfigEditFormTest.php index 650baff4c69395365865d9f1f81241179a18c086..d680842acc1d7953edc56e6f388cb6e34b7aca79 100644 --- a/core/modules/field_ui/tests/src/Unit/FieldConfigEditFormTest.php +++ b/core/modules/field_ui/tests/src/Unit/FieldConfigEditFormTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\field_ui\Unit; use Drupal\Core\Entity\EntityDisplayRepositoryInterface; +use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\Core\TempStore\PrivateTempStore; use Drupal\field_ui\Form\FieldConfigEditForm; use Drupal\Tests\UnitTestCase; @@ -30,8 +31,9 @@ protected function setUp(): void { $entity_type_bundle_info = $this->createMock('\Drupal\Core\Entity\EntityTypeBundleInfoInterface'); $typed_data = $this->createMock('\Drupal\Core\TypedData\TypedDataManagerInterface'); $temp_store = $this->createMock(PrivateTempStore::class); + $element_info_manager = $this->createMock(ElementInfoManagerInterface::class); $entity_display_repository = $this->createMock(EntityDisplayRepositoryInterface::class); - $this->fieldConfigEditForm = new FieldConfigEditForm($entity_type_bundle_info, $typed_data, $entity_display_repository, $temp_store); + $this->fieldConfigEditForm = new FieldConfigEditForm($entity_type_bundle_info, $typed_data, $entity_display_repository, $temp_store, $element_info_manager); } /** diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php index b5947c9e5b7098163e6e0bade049e7706bf82835..d61822ceb04d1520ad7e6c85344f17a8cce5746f 100644 --- a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php +++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php @@ -139,7 +139,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state '#description' => $this->t('This setting only has an effect if the display option is enabled.'), '#states' => [ 'visible' => [ - ':input[name="settings[display_field]"]' => ['checked' => TRUE], + ':input[name="field_storage[subform][settings][display_field]"]' => ['checked' => TRUE], ], ], ]; diff --git a/core/modules/file/tests/src/Functional/FileFieldWidgetTest.php b/core/modules/file/tests/src/Functional/FileFieldWidgetTest.php index 42511f153a3113a42b6247640a24d30e03052bdf..212c5fb6d239d62af15d520d0935c2d0f7ad8bfa 100644 --- a/core/modules/file/tests/src/Functional/FileFieldWidgetTest.php +++ b/core/modules/file/tests/src/Functional/FileFieldWidgetTest.php @@ -259,8 +259,8 @@ public function testPrivateFileSetting() { $test_file = $this->getTestFile('text'); // Change the field setting to make its files private, and upload a file. - $edit = ['settings[uri_scheme]' => 'private']; - $this->drupalGet("admin/structure/types/manage/{$type_name}/fields/{$field_id}/storage"); + $edit = ['field_storage[subform][settings][uri_scheme]' => 'private']; + $this->drupalGet("admin/structure/types/manage/{$type_name}/fields/{$field_id}"); $this->submitForm($edit, 'Save'); $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); $node = $node_storage->loadUnchanged($nid); @@ -273,13 +273,13 @@ public function testPrivateFileSetting() { // Ensure we can't change 'uri_scheme' field settings while there are some // entities with uploaded files. - $this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_id/storage"); - $this->assertSession()->fieldDisabled("edit-settings-uri-scheme-public"); + $this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_id"); + $this->assertSession()->fieldDisabled("edit-field-storage-subform-settings-uri-scheme-public"); // Delete node and confirm that setting could be changed. $node->delete(); - $this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_id/storage"); - $this->assertSession()->fieldEnabled("edit-settings-uri-scheme-public"); + $this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_id"); + $this->assertSession()->fieldEnabled("edit-field-storage-subform-settings-uri-scheme-public"); } /** @@ -302,7 +302,7 @@ public function testPrivateFileComment() { $name = $this->randomMachineName(); $label = $this->randomMachineName(); - $storage_edit = ['settings[uri_scheme]' => 'private']; + $storage_edit = ['field_storage[subform][settings][uri_scheme]' => 'private']; $this->fieldUIAddNewField('admin/structure/comment/manage/comment', $name, $label, 'file', $storage_edit); // Manually clear cache on the tester side. diff --git a/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php b/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php index 9ac7dd18f3e2ec766c69b2a0dad4efaa76f7c0db..b3b1dd9d248b6e37ece66ef9bbe9c883bbd3619a 100644 --- a/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php +++ b/core/modules/image/tests/src/Functional/ImageFieldDefaultImagesTest.php @@ -126,17 +126,17 @@ public function testDefaultImages() { ->save(); // Confirm the defaults are present on the article field storage settings - // form. + // sub-form. $field_id = $field->id(); - $this->drupalGet("admin/structure/types/manage/article/fields/$field_id/storage"); - $this->assertSession()->hiddenFieldValueEquals('settings[default_image][uuid][fids]', $default_images['field_storage']->id()); + $this->drupalGet("admin/structure/types/manage/article/fields/$field_id"); + $this->assertSession()->hiddenFieldValueEquals('field_storage[subform][settings][default_image][uuid][fids]', $default_images['field_storage']->id()); // Confirm the defaults are present on the article field edit form. $this->drupalGet("admin/structure/types/manage/article/fields/$field_id"); $this->assertSession()->hiddenFieldValueEquals('settings[default_image][uuid][fids]', $default_images['field']->id()); // Confirm the defaults are present on the page field storage settings form. - $this->drupalGet("admin/structure/types/manage/page/fields/$field_id/storage"); - $this->assertSession()->hiddenFieldValueEquals('settings[default_image][uuid][fids]', $default_images['field_storage']->id()); + $this->drupalGet("admin/structure/types/manage/page/fields/$field_id"); + $this->assertSession()->hiddenFieldValueEquals('field_storage[subform][settings][default_image][uuid][fids]', $default_images['field_storage']->id()); // Confirm the defaults are present on the page field edit form. $field2_id = $field2->id(); $this->drupalGet("admin/structure/types/manage/page/fields/$field2_id"); @@ -167,8 +167,8 @@ public function testDefaultImages() { // Confirm that the new default is used on the article field storage // settings form. - $this->drupalGet("admin/structure/types/manage/article/fields/$field_id/storage"); - $this->assertSession()->hiddenFieldValueEquals('settings[default_image][uuid][fids]', $default_images['field_storage_new']->id()); + $this->drupalGet("admin/structure/types/manage/article/fields/$field_id"); + $this->assertSession()->hiddenFieldValueEquals('field_storage[subform][settings][default_image][uuid][fids]', $default_images['field_storage_new']->id()); // Reload the nodes and confirm the field defaults are used. $node_storage->resetCache([$article->id(), $page->id()]); @@ -240,9 +240,9 @@ public function testDefaultImages() { $field_storage->save(); // Confirm that the new default is used on the article field storage - // settings form. - $this->drupalGet("admin/structure/types/manage/article/fields/$field_id/storage"); - $this->assertSession()->hiddenFieldValueEquals('settings[default_image][uuid][fids]', $default_images['field_storage_private']->id()); + // settings sub-form. + $this->drupalGet("admin/structure/types/manage/article/fields/$field_id"); + $this->assertSession()->hiddenFieldValueEquals('field_storage[subform][settings][default_image][uuid][fids]', $default_images['field_storage_private']->id()); // Upload a new default for the article's field after setting the field // storage upload destination to 'private'. diff --git a/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php index bb270feda8f12ecdcb0f788c4312479e27591441..3ce49168a630f3b46da2f5fdc945cda9c4880ade 100644 --- a/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php +++ b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php @@ -334,9 +334,9 @@ public function testImageFieldSettings() { // 1, so we need to make sure the file widget prevents these notices by // providing all settings, even if they are not used. // @see FileWidget::formMultipleElements(). - $this->drupalGet('admin/structure/types/manage/article/fields/node.article.' . $field_name . '/storage'); + $this->drupalGet('admin/structure/types/manage/article/fields/node.article.' . $field_name); $this->submitForm([ - 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, + 'field_storage[subform][cardinality]' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, ], 'Save'); $edit = [ 'files[' . $field_name . '_1][]' => \Drupal::service('file_system')->realpath($test_image->uri), @@ -499,11 +499,11 @@ public function testImageFieldDefaultImage() { $title = $this->randomString(1024); $edit = [ // Get the path of the 'image-test.png' file. - 'files[settings_default_image_uuid]' => \Drupal::service('file_system')->realpath($images[0]->uri), - 'settings[default_image][alt]' => $alt, - 'settings[default_image][title]' => $title, + 'files[field_storage_subform_settings_default_image_uuid]' => \Drupal::service('file_system')->realpath($images[0]->uri), + 'field_storage[subform][settings][default_image][alt]' => $alt, + 'field_storage[subform][settings][default_image][title]' => $title, ]; - $this->drupalGet("admin/structure/types/manage/article/fields/node.article.{$field_name}/storage"); + $this->drupalGet("admin/structure/types/manage/article/fields/node.article.{$field_name}"); $this->submitForm($edit, 'Save'); // Clear field definition cache so the new default image is detected. \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); @@ -558,8 +558,8 @@ public function testImageFieldDefaultImage() { // Remove default image from the field and make sure it is no longer used. // Can't use fillField cause Mink can't fill hidden fields. - $this->drupalGet("admin/structure/types/manage/article/fields/node.article.$field_name/storage"); - $this->getSession()->getPage()->find('css', 'input[name="settings[default_image][uuid][fids]"]')->setValue(0); + $this->drupalGet("admin/structure/types/manage/article/fields/node.article.$field_name"); + $this->getSession()->getPage()->find('css', 'input[name="field_storage[subform][settings][default_image][uuid][fids]"]')->setValue(0); $this->getSession()->getPage()->pressButton('Save'); // Clear field definition cache so the new default image is detected. @@ -574,11 +574,11 @@ public function testImageFieldDefaultImage() { // Add a default image to the new field. $edit = [ // Get the path of the 'image-test.gif' file. - 'files[settings_default_image_uuid]' => \Drupal::service('file_system')->realpath($images[2]->uri), - 'settings[default_image][alt]' => $alt, - 'settings[default_image][title]' => $title, + 'files[field_storage_subform_settings_default_image_uuid]' => \Drupal::service('file_system')->realpath($images[2]->uri), + 'field_storage[subform][settings][default_image][alt]' => $alt, + 'field_storage[subform][settings][default_image][title]' => $title, ]; - $this->drupalGet('admin/structure/types/manage/article/fields/node.article.' . $private_field_name . '/storage'); + $this->drupalGet('admin/structure/types/manage/article/fields/node.article.' . $private_field_name); $this->submitForm($edit, 'Save'); // Clear field definition cache so the new default image is detected. \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); diff --git a/core/modules/link/tests/src/Functional/LinkFieldUITest.php b/core/modules/link/tests/src/Functional/LinkFieldUITest.php index 2c40adac32ca8544105dea4b0bbf3fbf25516026..b435e2d3e9d844e3d657522c9133a241f0120dc5 100644 --- a/core/modules/link/tests/src/Functional/LinkFieldUITest.php +++ b/core/modules/link/tests/src/Functional/LinkFieldUITest.php @@ -165,7 +165,7 @@ public function runFieldUIItem($cardinality, $link_type, $title, $label, $field_ $field_edit['default_value_input[field_' . $field_name . '][0][title]'] = 'Default title'; } $storage_edit = [ - 'cardinality_number' => $cardinality, + 'field_storage[subform][cardinality_number]' => $cardinality, ]; $this->fieldUIAddNewField($type_path, $field_name, $label, 'link', $storage_edit, $field_edit); diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php index c87def8210b807dde1d17579e363e8cef13f7725..4e26cd1739d3e6d7424c53677dedfe1ae18fdeb8 100644 --- a/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php +++ b/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php @@ -71,8 +71,6 @@ public function testFieldUiIntegration() { $page->fillField('label', 'Shatner'); $this->waitForText('field_shatner'); $page->pressButton('Continue'); - $this->assertMatchesRegularExpression('/.*article\/add-storage\/node\/field_shatner.*/', $this->getUrl()); - $page->pressButton('Continue'); $this->assertMatchesRegularExpression('/.*article\/add-field\/node\/field_shatner.*/', $this->getUrl()); $assert_session->pageTextNotContains('Undefined index: target_bundles'); $this->waitForFieldExists('Type One')->check(); @@ -91,7 +89,7 @@ public function testFieldUiIntegration() { ->pressButton('Add media'); $this->waitForText('Add or select media'); $this->selectMediaItem(0); - $this->pressInsertSelected(); + $this->pressInsertSelected('Added one media item.'); $page->pressButton('Save settings'); $assert_session->pageTextContains('Saved Shatner configuration.'); diff --git a/core/modules/node/tests/src/Functional/NodeTranslationUITest.php b/core/modules/node/tests/src/Functional/NodeTranslationUITest.php index 202fcbceae2d341cd52cc0e72f2127612e1e9f3b..3d17ab6cf1a089cc8f8cdf366176aa516008aa03 100644 --- a/core/modules/node/tests/src/Functional/NodeTranslationUITest.php +++ b/core/modules/node/tests/src/Functional/NodeTranslationUITest.php @@ -559,8 +559,8 @@ public function testDetailsTitleIsNotEscaped() { $this->drupalLogin($this->administrator); // Make the image field a multi-value field in order to display a // details form element. - $edit = ['cardinality_number' => 2]; - $this->drupalGet('admin/structure/types/manage/article/fields/node.article.field_image/storage'); + $edit = ['field_storage[subform][cardinality_number]' => 2]; + $this->drupalGet('admin/structure/types/manage/article/fields/node.article.field_image'); $this->submitForm($edit, 'Save'); // Make the image field non-translatable. diff --git a/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php b/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php index 4bfbbe48de5dea1c0ee4fa12d02135eb42b32805..043cdb80b4d5fed775a1199846de33930e49b88d 100644 --- a/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php +++ b/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php @@ -173,7 +173,7 @@ public function testNodeTypeTitleLabelTranslation() { 'label' => 'Email', 'field_name' => 'email', ], 'Continue'); - $this->submitForm([], 'Continue'); + $this->submitForm([], 'Update settings'); $this->submitForm([], 'Save settings'); $type = $this->randomMachineName(16); diff --git a/core/modules/options/options.module b/core/modules/options/options.module index 608f4bcc6586553976e12ab1fdbf416a931f0315..30aefdd33cadbdaea59dec819eb4afd74f51f122 100644 --- a/core/modules/options/options.module +++ b/core/modules/options/options.module @@ -5,12 +5,10 @@ * Defines selection, check box and radio button widgets for text and numeric fields. */ -use Drupal\Component\Utility\NestedArray; use Drupal\Core\Url; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException; use Drupal\Core\Field\FieldStorageDefinitionInterface; -use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\field\FieldStorageConfigInterface; @@ -138,20 +136,3 @@ function _options_values_in_use($entity_type, $field_name, $values) { return FALSE; } - -/** - * Implements hook_form_FORM_ID_alter(). - * - * Add additional classes to enable styling for list field types. - * - * @see \Drupal\options\Plugin\Field\FieldType\ListItemBase::storageSettingsForm - */ -function options_form_field_storage_config_edit_form_alter(&$form, FormStateInterface $form_state) { - $table = &NestedArray::getValue($form, ['settings', 'allowed_values', 'table']); - if (!$table) { - return; - } - - $form['#attached']['library'][] = 'field_ui/drupal.field_ui'; - $table['#attributes']['class'][] = 'allowed-values-table'; -} diff --git a/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php b/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php index 3bdf31578a8d71a9902a33afce6e5000ba71548c..fbd26da53aaecad7c12413b830fc29f160a45b13 100644 --- a/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php +++ b/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php @@ -93,6 +93,8 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state if (!array_key_exists('allowed_values', $form_state->getStorage())) { $form_state->set('allowed_values', $this->getFieldDefinition()->getSetting('allowed_values')); } + $form['field_storage_submit']['#submit'][] = [static::class, 'submitFieldStorageUpdate']; + $form['field_storage_submit']['#limit_validation_errors'] = []; $allowed_values = $form_state->getStorage()['allowed_values']; $allowed_values_function = $this->getSetting('allowed_values_function'); @@ -122,6 +124,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state '#attributes' => [ 'id' => 'allowed-values-order', 'data-field-list-table' => TRUE, + 'class' => ['allowed-values-table'], ], '#tabledrag' => [ [ @@ -131,7 +134,10 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state ], ], '#attached' => [ - 'library' => ['core/drupal.fieldListKeyboardNavigation'], + 'library' => [ + 'core/drupal.fieldListKeyboardNavigation', + 'field_ui/drupal.field_ui', + ], ], ]; @@ -293,12 +299,17 @@ public static function deleteSubmit(array $form, FormStateInterface $form_state) $remaining_allowed_values = array_diff($allowed_values, [$item_to_be_removed]); $form_state->set('allowed_values', $remaining_allowed_values); - $delta = $button['#delta']; - $user_input = $form_state->getUserInput(); // The user input is directly modified to preserve the rest of the data on // the page as it cannot be rebuilt from a fresh form state. - unset($user_input['settings']['allowed_values']['table'][$delta]); - $user_input['settings']['allowed_values']['table'] = array_values($user_input['settings']['allowed_values']['table']); + $user_input = $form_state->getUserInput(); + NestedArray::unsetValue($user_input, $element['#parents']); + + // Reset the keys in the array. + $table_parents = $element['#parents']; + array_pop($table_parents); + $new_values = array_values(NestedArray::getValue($user_input, $table_parents)); + NestedArray::setValue($user_input, $table_parents, $new_values); + $form_state->setUserInput($user_input); $form_state->set('items_count', $form_state->get('items_count') - 1); @@ -350,7 +361,7 @@ public static function validateAllowedValues($element, FormStateInterface $form_ }, Element::children($element['table'])), function ($item) { return $item; }); - if ($reordered_items = $form_state->getValue(['settings', 'allowed_values', 'table'])) { + if ($reordered_items = $form_state->getValue([...$element['#parents'], 'table'])) { uksort($items, function ($a, $b) use ($reordered_items) { $a_weight = $reordered_items[$a]['weight'] ?? 0; $b_weight = $reordered_items[$b]['weight'] ?? 0; @@ -552,4 +563,11 @@ protected static function castAllowedValue($value) { return $value; } + /** + * Resets the static variable on field storage update. + */ + public static function submitFieldStorageUpdate() { + drupal_static_reset('options_allowed_values'); + } + } diff --git a/core/modules/options/tests/src/Functional/OptionsFieldUITest.php b/core/modules/options/tests/src/Functional/OptionsFieldUITest.php index c89e3e8ea3170ee2d5bcdb2a7ad25c3d3b0360ab..c56f2f37d14b9f5c40319d7e8243f5651c08bfb7 100644 --- a/core/modules/options/tests/src/Functional/OptionsFieldUITest.php +++ b/core/modules/options/tests/src/Functional/OptionsFieldUITest.php @@ -96,31 +96,31 @@ public function testOptionsAllowedValuesInteger() { // Explicit integer keys. $input = [ - 'settings[allowed_values][table][0][item][key]' => 0, - 'settings[allowed_values][table][0][item][label]' => 'Zero', - 'settings[allowed_values][table][1][item][key]' => 2, - 'settings[allowed_values][table][1][item][label]' => 'Two', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 0, + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero', + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => 2, + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Two', ]; $array = [0 => 'Zero', 2 => 'Two']; $this->assertAllowedValuesInput($input, $array, 'Integer keys are accepted.'); // Non-integer keys. $input = [ - 'settings[allowed_values][table][0][item][key]' => 1.1, - 'settings[allowed_values][table][0][item][label]' => 'One', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 1.1, + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'One', ]; $this->assertAllowedValuesInput($input, 'keys must be integers', 'Non integer keys are rejected.'); $input = [ - 'settings[allowed_values][table][0][item][key]' => 'abc', - 'settings[allowed_values][table][0][item][label]' => 'abc', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 'abc', + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'abc', ]; $this->assertAllowedValuesInput($input, 'keys must be integers', 'Non integer keys are rejected.'); $input = [ - 'settings[allowed_values][table][0][item][key]' => 0, - 'settings[allowed_values][table][0][item][label]' => 'Zero', - 'settings[allowed_values][table][1][item][key]' => 1, - 'settings[allowed_values][table][1][item][label]' => 'One', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 0, + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero', + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => 1, + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'One', ]; $array = [0 => 'Zero', 1 => 'One']; $this->assertAllowedValuesInput($input, $array, ''); @@ -148,10 +148,10 @@ public function testOptionsAllowedValuesInteger() { // Check that the same key can only be used once. $input = [ - 'settings[allowed_values][table][0][item][key]' => 0, - 'settings[allowed_values][table][0][item][label]' => 'Zero', - 'settings[allowed_values][table][1][item][key]' => 0, - 'settings[allowed_values][table][1][item][label]' => 'One', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 0, + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero', + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => 0, + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'One', ]; $array = ['0' => 'One']; $this->assertAllowedValuesInput($input, $array, 'Same value cannot be used multiple times.'); @@ -168,39 +168,39 @@ public function testOptionsAllowedValuesFloat() { // Explicit numeric keys. $input = [ - 'settings[allowed_values][table][0][item][key]' => 0, - 'settings[allowed_values][table][0][item][label]' => 'Zero', - 'settings[allowed_values][table][1][item][key]' => .5, - 'settings[allowed_values][table][1][item][label]' => 'Point five', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 0, + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero', + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => .5, + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Point five', ]; $array = ['0' => 'Zero', '0.5' => 'Point five']; $this->assertAllowedValuesInput($input, $array, 'Integer keys are accepted.'); // Check that values can be added. $input = [ - 'settings[allowed_values][table][0][item][key]' => 0, - 'settings[allowed_values][table][0][item][label]' => 'Zero', - 'settings[allowed_values][table][1][item][key]' => .5, - 'settings[allowed_values][table][1][item][label]' => 'Point five', - 'settings[allowed_values][table][2][item][key]' => 1, - 'settings[allowed_values][table][2][item][label]' => 'One', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 0, + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero', + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => .5, + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Point five', + 'field_storage[subform][settings][allowed_values][table][2][item][key]' => 1, + 'field_storage[subform][settings][allowed_values][table][2][item][label]' => 'One', ]; $array = ['0' => 'Zero', '0.5' => 'Point five', '1' => 'One']; $this->assertAllowedValuesInput($input, $array, 'Values can be added.'); // Non-numeric keys. $input = [ - 'settings[allowed_values][table][0][item][key]' => 'abc', - 'settings[allowed_values][table][0][item][label]' => 'abc', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 'abc', + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'abc', ]; $this->assertAllowedValuesInput($input, 'each key must be a valid integer or decimal', 'Non numeric keys are rejected.'); $input = [ - 'settings[allowed_values][table][0][item][key]' => 0, - 'settings[allowed_values][table][0][item][label]' => 'Zero', - 'settings[allowed_values][table][1][item][key]' => .5, - 'settings[allowed_values][table][1][item][label]' => 'Point five', - 'settings[allowed_values][table][2][item][key]' => 2, - 'settings[allowed_values][table][2][item][label]' => 'Two', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 0, + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero', + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => .5, + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Point five', + 'field_storage[subform][settings][allowed_values][table][2][item][key]' => 2, + 'field_storage[subform][settings][allowed_values][table][2][item][label]' => 'Two', ]; $array = ['0' => 'Zero', '0.5' => 'Point five', '2' => 'Two']; $this->assertAllowedValuesInput($input, $array, ''); @@ -227,20 +227,20 @@ public function testOptionsAllowedValuesFloat() { $this->assertSame($field_storage->getSetting('allowed_values'), [0 => 'Zero', 2 => 'Two']); $input = [ - 'settings[allowed_values][table][0][item][key]' => .5, - 'settings[allowed_values][table][0][item][label]' => 'Point five', - 'settings[allowed_values][table][1][item][key]' => .5, - 'settings[allowed_values][table][1][item][label]' => 'Half', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => .5, + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Point five', + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => .5, + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Half', ]; $array = ['0.5' => 'Half']; $this->assertAllowedValuesInput($input, $array, 'Same value cannot be used multiple times.'); // Check that different forms of the same float value cannot be used. $input = [ - 'settings[allowed_values][table][0][item][key]' => .5, - 'settings[allowed_values][table][0][item][label]' => 'Point five', - 'settings[allowed_values][table][1][item][key]' => 0.5, - 'settings[allowed_values][table][1][item][label]' => 'Half', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => .5, + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Point five', + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => 0.5, + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Half', ]; $array = ['0.5' => 'Half']; $this->assertAllowedValuesInput($input, $array, 'Different forms of the same value cannot be used.'); @@ -257,28 +257,28 @@ public function testOptionsAllowedValuesText() { // Explicit keys. $input = [ - 'settings[allowed_values][table][0][item][key]' => '_zero', - 'settings[allowed_values][table][0][item][label]' => 'Zero', - 'settings[allowed_values][table][1][item][key]' => '_one', - 'settings[allowed_values][table][1][item][label]' => 'One', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => '_zero', + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero', + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => '_one', + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'One', ]; $array = ['_zero' => 'Zero', '_one' => 'One']; $this->assertAllowedValuesInput($input, $array, 'Explicit keys are accepted.'); // Overly long keys. $input = [ - 'settings[allowed_values][table][0][item][key]' => 'zero', - 'settings[allowed_values][table][0][item][label]' => 'Zero', - 'settings[allowed_values][table][1][item][key]' => $this->randomMachineName(256), - 'settings[allowed_values][table][1][item][label]' => 'One', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 'zero', + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero', + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => $this->randomMachineName(256), + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'One', ]; $this->assertAllowedValuesInput($input, 'each key must be a string at most 255 characters long', 'Overly long keys are rejected.'); $input = [ - 'settings[allowed_values][table][0][item][key]' => 'zero', - 'settings[allowed_values][table][0][item][label]' => 'Zero', - 'settings[allowed_values][table][1][item][key]' => 'one', - 'settings[allowed_values][table][1][item][label]' => 'One', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 'zero', + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero', + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => 'one', + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'One', ]; $array = ['zero' => 'Zero', 'one' => 'One']; $this->assertAllowedValuesInput($input, $array, ''); @@ -293,7 +293,7 @@ public function testOptionsAllowedValuesText() { $this->drupalGet($this->adminPath); $assert_session->elementExists('css', '#remove_row_button__1'); $delete_button_1 = $page->findById('remove_row_button__1'); - $value_field_1 = $page->findField('settings[allowed_values][table][1][item][key]'); + $value_field_1 = $page->findField('field_storage[subform][settings][allowed_values][table][1][item][key]'); $this->assertTrue($delete_button_1->hasAttribute('disabled'), 'Button is disabled'); $this->assertTrue($value_field_1->hasAttribute('disabled'), 'Button is disabled'); @@ -308,19 +308,19 @@ public function testOptionsAllowedValuesText() { // Check that string values with dots can not be used. $input = [ - 'settings[allowed_values][table][0][item][key]' => 'zero', - 'settings[allowed_values][table][0][item][label]' => 'Zero', - 'settings[allowed_values][table][1][item][key]' => 'example.com', - 'settings[allowed_values][table][1][item][label]' => 'Example', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 'zero', + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero', + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => 'example.com', + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Example', ]; $this->assertAllowedValuesInput($input, 'The machine-readable name must contain only lowercase letters, numbers, and underscores.', 'String value with dot is not supported.'); // Check that the same key can only be used once. $input = [ - 'settings[allowed_values][table][0][item][key]' => 'zero', - 'settings[allowed_values][table][0][item][label]' => 'Zero', - 'settings[allowed_values][table][1][item][key]' => 'zero', - 'settings[allowed_values][table][1][item][label]' => 'One', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 'zero', + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero', + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => 'zero', + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'One', ]; $array = ['zero' => 'One']; $this->assertAllowedValuesInput($input, $array, 'Same value cannot be used multiple times.'); @@ -350,7 +350,7 @@ protected function createOptionsField($type) { ->setComponent($this->fieldName) ->save(); - $this->adminPath = 'admin/structure/types/manage/' . $this->type . '/fields/node.' . $this->type . '.' . $this->fieldName . '/storage'; + $this->adminPath = 'admin/structure/types/manage/' . $this->type . '/fields/node.' . $this->type . '.' . $this->fieldName; } /** @@ -397,10 +397,10 @@ public function testNodeDisplay() { $on = $this->randomMachineName(); $off = $this->randomMachineName(); $edit = [ - 'settings[allowed_values][table][0][item][key]' => 1, - 'settings[allowed_values][table][0][item][label]' => $on, - 'settings[allowed_values][table][1][item][key]' => 0, - 'settings[allowed_values][table][1][item][label]' => $off, + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 1, + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => $on, + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => 0, + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => $off, ]; $this->drupalGet($this->adminPath); diff --git a/core/modules/options/tests/src/Functional/OptionsFloatFieldImportTest.php b/core/modules/options/tests/src/Functional/OptionsFloatFieldImportTest.php index 2610530d2a4b7f8cc530571260d84cfd448ce24d..18cb151f5426809b20aa822f4f2a06e637c87c23 100644 --- a/core/modules/options/tests/src/Functional/OptionsFloatFieldImportTest.php +++ b/core/modules/options/tests/src/Functional/OptionsFloatFieldImportTest.php @@ -65,17 +65,17 @@ public function testImport() { $field_storage = FieldStorageConfig::loadByName('node', $field_name); $this->assertSame($array = ['0' => 'Zero', '0.5' => 'Point five'], $field_storage->getSetting('allowed_values')); - $admin_path = 'admin/structure/types/manage/' . $type . '/fields/node.' . $type . '.' . $field_name . '/storage'; + $admin_path = 'admin/structure/types/manage/' . $type . '/fields/node.' . $type . '.' . $field_name; // Export active config to sync. $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync')); // Set the active to not use dots in the allowed values key names. $edit = [ - 'settings[allowed_values][table][0][item][key]' => 0, - 'settings[allowed_values][table][0][item][label]' => 'Zero', - 'settings[allowed_values][table][1][item][key]' => 1, - 'settings[allowed_values][table][1][item][label]' => 'One', + 'field_storage[subform][settings][allowed_values][table][0][item][key]' => 0, + 'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero', + 'field_storage[subform][settings][allowed_values][table][1][item][key]' => 1, + 'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'One', ]; $this->drupalGet($admin_path); $this->submitForm($edit, 'Save'); diff --git a/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php b/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php index 593fd85ef041dd3ce49635776df84995333f3748..e74463f2cd7748d895afa42a6bec2162aab391db 100644 --- a/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php +++ b/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php @@ -5,6 +5,7 @@ use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use Drupal\Tests\field_ui\Traits\FieldUiJSTestTrait; /** * Tests the Options field UI functionality. @@ -13,6 +14,8 @@ */ class OptionsFieldUITest extends WebDriverTestBase { + use FieldUiJSTestTrait; + /** * {@inheritdoc} */ @@ -91,9 +94,10 @@ public function testOptionsAllowedValues($option_type, $options, $is_string_opti $expected_rows = 1; $this->assertAllowValuesRowCount(1); foreach ($options as $option_key => $option_label) { - $enter_element_name = $label_element_name = "settings[allowed_values][table][$i][item][label]"; + $enter_element_name = $label_element_name = "field_storage[subform][settings][allowed_values][table][$i][item][label]"; $page->fillField($label_element_name, $option_label); - $key_element_name = "settings[allowed_values][table][$i][item][key]"; + $this->assertSession()->assertWaitOnAjaxRequest(); + $key_element_name = "field_storage[subform][settings][allowed_values][table][$i][item][key]"; // Add keys if not string option list. if (!$is_string_option) { @@ -104,6 +108,7 @@ public function testOptionsAllowedValues($option_type, $options, $is_string_opti $enter_element_name = $key_element_name; $this->assertHasFocusByAttribute('name', $key_element_name); $page->fillField($key_element_name, $option_key); + $this->assertSession()->assertWaitOnAjaxRequest(); } else { $this->assertFalse($assert->fieldExists($key_element_name)->isVisible()); @@ -146,8 +151,8 @@ public function testOptionsAllowedValues($option_type, $options, $is_string_opti $i++; $expected_rows++; - $this->assertSession()->waitForElementVisible('css', "[name='settings[allowed_values][table][$i][item][label]']"); - $this->assertHasFocusByAttribute('name', "settings[allowed_values][table][$i][item][label]"); + $this->assertSession()->waitForElementVisible('css', "[name='field_storage[subform][settings][allowed_values][table][$i][item][label]']"); + $this->assertHasFocusByAttribute('name', "field_storage[subform][settings][allowed_values][table][$i][item][label]"); $this->assertAllowValuesRowCount($expected_rows); if ($is_string_option) { @@ -171,8 +176,8 @@ public function testOptionsAllowedValues($option_type, $options, $is_string_opti // Test the order of the option list on admin path. $this->drupalGet($this->adminPath); $this->assertOrder(['First', 'Second', 'Third', ''], $is_string_option); - $drag_handle = $page->find('css', '[data-drupal-selector="edit-settings-allowed-values-table-0"] .tabledrag-handle'); - $target = $page->find('css', '[data-drupal-selector="edit-settings-allowed-values-table-2"]'); + $drag_handle = $page->find('css', '[data-drupal-selector="edit-field-storage-subform-settings-allowed-values-table-0"] .tabledrag-handle'); + $target = $page->find('css', '[data-drupal-selector="edit-field-storage-subform-settings-allowed-values-table-2"]'); // Change the order the items appear. $drag_handle->dragTo($target); @@ -202,6 +207,47 @@ public function testOptionsAllowedValues($option_type, $options, $is_string_opti $this->assertOrder(['Second', 'First', ''], $is_string_option); } + /** + * Tests that the allowed options are available to the default value widget. + */ + public function testDefaultValueOptions() { + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + $bundle_path = 'admin/structure/types/manage/' . $this->type; + // Create a field of type list:string. + $this->fieldUIAddNewFieldJS($bundle_path, 'test_string_list', 'Test string list', 'list_string', FALSE); + $page->findField('field_storage[subform][settings][allowed_values][table][0][item][label]')->setValue('first'); + $assert_session->assertWaitOnAjaxRequest(); + $page->findField('set_default_value')->setValue(TRUE); + // Assert that the option added in the subform is available to the default + // value field. + $this->assertSession()->optionExists('default_value_input[field_test_string_list]', 'first'); + $page->pressButton('Add another item'); + $this->assertNotNull($assert_session->waitForElement('css', "[name='field_storage[subform][settings][allowed_values][table][1][item][label]']")); + $page->findField('field_storage[subform][settings][allowed_values][table][1][item][label]')->setValue('second'); + $assert_session->assertWaitOnAjaxRequest(); + $assert_session->optionExists('default_value_input[field_test_string_list]', 'second'); + $page->selectFieldOption('default_value_input[field_test_string_list]', 'second'); + $page->pressButton('Save settings'); + $assert_session->pageTextContains('Saved Test string list configuration.'); + + // Create a field of type list:integer. + $this->fieldUIAddNewFieldJS($bundle_path, 'test_int_list', 'Test int list', 'list_integer', FALSE); + $page->findField('field_storage[subform][settings][allowed_values][table][0][item][label]')->setValue('first'); + $assert_session->assertWaitOnAjaxRequest(); + // Assert that no validation is performed. + $assert_session->statusMessageNotContains('Value field is required.'); + $page->findField('field_storage[subform][settings][allowed_values][table][0][item][key]')->setValue(1); + $assert_session->assertWaitOnAjaxRequest(); + $page->findField('set_default_value')->setValue(TRUE); + // Assert that the option added in the subform is available to the default + // value field. + $this->assertSession()->optionExists('default_value_input[field_test_int_list]', 'first'); + $page->selectFieldOption('default_value_input[field_test_int_list]', 'first'); + $page->pressButton('Save settings'); + $assert_session->pageTextContains('Saved Test int list configuration.'); + } + /** * Asserts the order of provided option list on admin path. * @@ -262,7 +308,7 @@ protected function createOptionsField($type) { ->setComponent($this->fieldName) ->save(); - $this->adminPath = 'admin/structure/types/manage/' . $this->type . '/fields/node.' . $this->type . '.' . $this->fieldName . '/storage'; + $this->adminPath = 'admin/structure/types/manage/' . $this->type . '/fields/node.' . $this->type . '.' . $this->fieldName; } /** @@ -364,7 +410,7 @@ private function exposeOptionMachineName(int $row): void { $index = $row - 1; $rows = $this->getSession()->getPage()->findAll('css', '#allowed-values-order tr.draggable'); $this->assertSession()->buttonExists('Edit', $rows[$index])->click(); - $this->assertSession()->waitForElementVisible('css', "[name='settings[allowed_values][table][$index][item][key]']"); + $this->assertSession()->waitForElementVisible('css', "[name='field_storage[subform][settings][allowed_values][table][$index][item][key]']"); } } diff --git a/core/modules/system/tests/src/Functional/Entity/EntityReferenceFieldCreationTest.php b/core/modules/system/tests/src/Functional/Entity/EntityReferenceFieldCreationTest.php index 7d001f8df336c551a89564278eb12c74a5374035..89a185704257354b3ea1d036c4a3787b8434d583 100644 --- a/core/modules/system/tests/src/Functional/Entity/EntityReferenceFieldCreationTest.php +++ b/core/modules/system/tests/src/Functional/Entity/EntityReferenceFieldCreationTest.php @@ -36,7 +36,7 @@ public function testAddReferenceFieldTargetingEntityTypeWithoutId() { // Entity types without an ID key should not be presented as options when // creating an entity reference field in the UI. $this->fieldUIAddNewField("/admin/structure/types/manage/$node_type", 'test_reference_field', 'Test Field', 'entity_reference', [], [], FALSE); - $this->assertSession()->optionNotExists('settings[target_type]', 'entity_test_no_id'); + $this->assertSession()->optionNotExists('field_storage[subform][settings][target_type]', 'entity_test_no_id'); // Trying to do it programmatically should raise an exception. $this->expectException('\Drupal\Core\Field\FieldException'); diff --git a/core/modules/system/tests/src/Functional/System/DateTimeTest.php b/core/modules/system/tests/src/Functional/System/DateTimeTest.php index d6f62b7878c24c8a56e2d32eade745a8699f8b56..c3eaf3ecca14d11c335f62dd2c1b230a310172b2 100644 --- a/core/modules/system/tests/src/Functional/System/DateTimeTest.php +++ b/core/modules/system/tests/src/Functional/System/DateTimeTest.php @@ -215,9 +215,9 @@ public function testEnteringDateTimeViaSelectors() { $this->assertSession()->statusCodeEquals(200); $storage_edit = [ - 'settings[datetime_type]' => 'datetime', - 'cardinality' => 'number', - 'cardinality_number' => '1', + 'field_storage[subform][settings][datetime_type]' => 'datetime', + 'field_storage[subform][cardinality]' => 'number', + 'field_storage[subform][cardinality_number]' => '1', ]; $this->fieldUIAddNewField('admin/structure/types/manage/page_with_date', 'dt', 'dt', 'datetime', $storage_edit); diff --git a/core/tests/Drupal/Tests/Core/Form/SubformStateTest.php b/core/tests/Drupal/Tests/Core/Form/SubformStateTest.php index 6d5318402d924b9b79a5aeaac286659ca42ce120..01d57e46a3eae77906b83c4bdd1deb576303be35 100644 --- a/core/tests/Drupal/Tests/Core/Form/SubformStateTest.php +++ b/core/tests/Drupal/Tests/Core/Form/SubformStateTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\Core\Form; use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Form\FormInterface; use Drupal\Core\Form\FormState; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\SubformState; @@ -311,4 +312,18 @@ public function testSetErrorByName() { $this->assertSame($subform_state, $subform_state->setErrorByName($subform_error_name, $message)); } + /** + * @covers ::getFormObject + */ + public function testFormObject() { + $parent_form_state = $this->prophesize(FormStateInterface::class); + $parent_form_object = $this->prophesize(FormInterface::class)->reveal(); + $parent_form_state->getFormObject()->willReturn($parent_form_object)->shouldBeCalledOnce(); + + $subform_form_object = $this->prophesize(FormInterface::class)->reveal(); + $subform_state = SubformState::createForSubform($this->parentForm['dog'], $this->parentForm, $parent_form_state->reveal(), $subform_form_object); + $this->assertSame($subform_form_object, $subform_state->getFormObject()); + $this->assertSame($parent_form_object, $subform_state->getCompleteFormState()->getFormObject()); + } + }