Issue #3358049 by narendraR, lauriii, srishtiiee, Utkarsh_33, Wim Leers, tim.plunkett, tedbow, bnjmnm, smustgrave, Berdir, amateescu, larowlan: Save FieldStorageConfig at the same time as FieldConfig

merge-requests/5382/head
effulgentsia 2023-08-31 14:52:48 -07:00
parent 2b9d2a95ac
commit d2cdfb1940
29 changed files with 698 additions and 143 deletions

View File

@ -6,6 +6,7 @@ use Drupal\Core\Config\Entity\ConfigEntityBase;
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.
@ -468,10 +469,17 @@ abstract class FieldConfigBase extends ConfigEntityBase implements FieldConfigIn
* @todo Investigate in https://www.drupal.org/node/1977206.
*/
public function __sleep() {
$properties = get_object_vars($this);
// Only serialize necessary properties, excluding those that can be
// recalculated.
$properties = get_object_vars($this);
unset($properties['fieldStorage'], $properties['itemDefinition'], $properties['original']);
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);
}

View File

@ -370,7 +370,17 @@ class FieldItemList extends ItemList implements FieldItemListInterface {
]);
}
else {
$widget = $field_widget_plugin_manager->getInstance(['field_definition' => $definition]);
$options = [
'field_definition' => $this->getFieldDefinition(),
];
// If the field does not have a widget configured in the 'default' form
// mode, check if there are default entity form display options defined
// for the 'default' form mode in the form state.
// @see \Drupal\field_ui\Controller\FieldConfigAddController::fieldConfigAddConfigureForm
if (($default_options = $form_state->get('default_options')) && isset($default_options['entity_form_display']['default'])) {
$options['configuration'] = $default_options['entity_form_display']['default'];
}
$widget = $field_widget_plugin_manager->getInstance($options);
}
$form_state->set('default_value_widget', $widget);

View File

@ -158,12 +158,12 @@ class CommentFieldsTest extends CommentTestBase {
'field_name' => 'user_comment',
];
$this->drupalGet('admin/config/people/accounts/fields/add-field');
$this->submitForm($edit, 'Save and continue');
$this->submitForm($edit, 'Continue');
// Try to save the comment field without selecting a comment type.
$edit = [];
$this->drupalGet('admin/config/people/accounts/fields/user.user.field_user_comment/storage');
$this->submitForm($edit, 'Save field settings');
$this->drupalGet('admin/config/people/accounts/add-storage/user/field_user_comment');
$this->submitForm($edit, 'Continue');
// We should get an error message.
$this->assertSession()->pageTextContains('The submitted value in the Comment type element is not allowed.');
@ -180,8 +180,8 @@ class CommentFieldsTest extends CommentTestBase {
$edit = [
'settings[comment_type]' => 'user_comment_type',
];
$this->drupalGet('admin/config/people/accounts/fields/user.user.field_user_comment/storage');
$this->submitForm($edit, 'Save field settings');
$this->drupalGet('admin/config/people/accounts/add-storage/user/field_user_comment');
$this->submitForm($edit, 'Continue');
// We shouldn't get an error message.
$this->assertSession()->pageTextNotContains('The submitted value in the Comment type element is not allowed.');
@ -190,7 +190,7 @@ class CommentFieldsTest extends CommentTestBase {
$edit = [
'settings[per_page]' => 0,
];
$this->drupalGet('admin/config/people/accounts/fields/user.user.field_user_comment');
$this->drupalGet('admin/config/people/accounts/add-field/user/field_user_comment');
$this->submitForm($edit, 'Save settings');
$this->assertSession()->statusMessageContains('Saved User comment configuration.', 'status');
}

View File

@ -6,6 +6,8 @@ use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\FieldableEntityStorageInterface;
use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldException;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\OptionsProviderInterface;
@ -680,7 +682,19 @@ class FieldStorageConfig extends ConfigEntityBase implements FieldStorageConfigI
// runtime item object, so that it can be used as the options provider
// without modifying the entity being worked on.
if (is_subclass_of($this->getFieldItemClass(), OptionsProviderInterface::class)) {
$items = $entity->get($this->getName());
try {
$items = $entity->get($this->getName());
}
catch (\InvalidArgumentException $e) {
// When a field doesn't exist, create a new field item list using a
// temporary base field definition. This step is necessary since there
// may not be a field configuration for the storage when creating a new
// field.
// @todo Simplify in https://www.drupal.org/project/drupal/issues/3347291.
$field_storage = BaseFieldDefinition::createFromFieldStorageDefinition($this);
$entity_adapter = EntityAdapter::createFromEntity($entity);
$items = \Drupal::typedDataManager()->create($field_storage, name: $field_storage->getName(), parent: $entity_adapter);
}
return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($items, 0);
}
// @todo: Allow setting custom options provider, see
@ -724,7 +738,7 @@ class FieldStorageConfig extends ConfigEntityBase implements FieldStorageConfigI
* TRUE if the field has data for any entity; FALSE otherwise.
*/
public function hasData() {
return \Drupal::entityTypeManager()->getStorage($this->entity_type)->countFieldData($this, TRUE);
return !$this->isNew() && \Drupal::entityTypeManager()->getStorage($this->entity_type)->countFieldData($this, TRUE);
}
/**

View File

@ -4,6 +4,7 @@ namespace Drupal\Tests\field\Functional\EntityReference;
use Behat\Mink\Element\NodeElement;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\node\Entity\Node;
use Drupal\taxonomy\Entity\Vocabulary;
@ -119,7 +120,7 @@ class EntityReferenceAdminTest extends BrowserTestBase {
$edit = [
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
];
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Continue');
// Add the view to the test field.
$edit = [
@ -131,6 +132,8 @@ class EntityReferenceAdminTest extends BrowserTestBase {
'settings[handler_settings][view][view_and_display]' => 'node_test_view:entity_reference_1',
];
$this->submitForm($edit, 'Save settings');
$this->assertSession()->statusMessageContains("Saved Test Entity Reference Field configuration.", MessengerInterface::TYPE_STATUS);
$this->assertFieldExistsOnOverview('Test Entity Reference Field');
// Create nodes.
$node1 = Node::create([
@ -205,7 +208,7 @@ class EntityReferenceAdminTest extends BrowserTestBase {
$edit = [
'cardinality' => -1,
];
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
$this->drupalGet($bundle_path . '/fields/' . $field_path);
$term_name = $this->randomString();
$result = \Drupal::entityQuery('taxonomy_term')
@ -219,6 +222,7 @@ class EntityReferenceAdminTest extends BrowserTestBase {
'settings[handler_settings][auto_create]' => 1,
];
$this->submitForm($edit, 'Save settings');
$this->assertFieldExistsOnOverview($taxonomy_term_field_name);
$this->drupalGet($bundle_path . '/fields/' . $field_path);
$edit = [
'set_default_value' => '1',
@ -226,6 +230,7 @@ class EntityReferenceAdminTest extends BrowserTestBase {
'default_value_input[field_' . $taxonomy_term_field_name . '][0][target_id]' => $term_name,
];
$this->submitForm($edit, 'Save settings');
$this->assertFieldExistsOnOverview($taxonomy_term_field_name);
// The term should now exist.
$result = \Drupal::entityQuery('taxonomy_term')
->condition('name', $term_name)
@ -382,7 +387,7 @@ class EntityReferenceAdminTest extends BrowserTestBase {
$field_edit['settings[handler_settings][target_bundles][' . $bundle . ']'] = TRUE;
}
$this->fieldUIAddNewField($bundle_path, $field_name, NULL, 'entity_reference', $storage_edit, $field_edit);
$this->fieldUIAddNewField($bundle_path, $field_name, $field_name, 'entity_reference', $storage_edit, $field_edit);
// Returns the generated field name.
return $field_name;

View File

@ -136,7 +136,7 @@ class EntityReferenceAdminTest extends WebDriverTestBase {
$this->assertFieldSelectOptions('settings[target_type]', array_keys(\Drupal::entityTypeManager()->getDefinitions()));
// Second step: 'Field settings' form.
$this->submitForm([], 'Save field settings');
$this->submitForm([], 'Continue');
// The base handler should be selected by default.
$this->assertSession()->fieldValueEquals('settings[handler]', 'default:node');
@ -268,7 +268,7 @@ class EntityReferenceAdminTest extends WebDriverTestBase {
'settings[target_type]' => 'taxonomy_term',
];
$this->drupalGet($bundle_path . '/fields/' . $field_name . '/storage');
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
$this->drupalGet($bundle_path . '/fields/' . $field_name);
$this->assertSession()->fieldExists('settings[handler_settings][auto_create]');
@ -279,7 +279,7 @@ class EntityReferenceAdminTest extends WebDriverTestBase {
'settings[target_type]' => 'user',
];
$this->drupalGet($bundle_path . '/fields/' . $field_name . '/storage');
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
$this->drupalGet($bundle_path . '/fields/' . $field_name);
$this->assertSession()->fieldValueEquals('settings[handler_settings][filter][type]', '_none');
$this->assertSession()->fieldValueEquals('settings[handler_settings][sort][field]', '_none');
@ -299,7 +299,7 @@ class EntityReferenceAdminTest extends WebDriverTestBase {
'settings[target_type]' => 'node',
];
$this->drupalGet($bundle_path . '/fields/' . $field_name . '/storage');
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
// Try to select the views handler.
$this->drupalGet($bundle_path . '/fields/' . $field_name);
@ -332,7 +332,7 @@ class EntityReferenceAdminTest extends WebDriverTestBase {
'settings[target_type]' => 'entity_test',
];
$this->drupalGet($bundle_path . '/fields/' . $field_name . '/storage');
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
$this->drupalGet($bundle_path . '/fields/' . $field_name);
$page->findField('settings[handler]')->setValue('views');
$assert_session

View File

@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityViewModeInterface;
use Drupal\Core\Entity\EntityFormModeInterface;
use Drupal\Core\Url;
use Drupal\field_ui\FieldUI;
use Drupal\field_ui\Form\FieldConfigEditForm;
use Drupal\field_ui\Plugin\Derivative\FieldUiLocalTask;
/**
@ -79,6 +80,7 @@ function field_ui_theme() {
function field_ui_entity_type_build(array &$entity_types) {
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
$entity_types['field_config']->setFormClass('edit', 'Drupal\field_ui\Form\FieldConfigEditForm');
$entity_types['field_config']->setFormClass('default', FieldConfigEditForm::class);
$entity_types['field_config']->setFormClass('delete', 'Drupal\field_ui\Form\FieldConfigDeleteForm');
$entity_types['field_config']->setListBuilderClass('Drupal\field_ui\FieldConfigListBuilder');

View File

@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
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;
/**
* Controller for building the field instance form.
*
* @internal
*/
final class FieldConfigAddController extends ControllerBase {
/**
* FieldConfigAddController constructor.
*
* @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,
) {}
/**
* {@inheritdoc}
*/
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')
);
}
/**
* Builds the field config instance form.
*
* @param string $entity_type
* The entity type.
* @param string $field_name
* The name of the field to create.
*
* @return array
* The field instance edit form.
*/
public function fieldConfigAddConfigureForm(string $entity_type, string $field_name): array {
// @see \Drupal\field_ui\Form\FieldStorageAddForm::submitForm
$temp_storage = $this->tempStore->get($entity_type . ':' . $field_name);
if (!$temp_storage) {
throw new NotFoundHttpException();
}
/** @var \Drupal\Core\Field\FieldConfigInterface $entity */
$entity = $this->entityTypeManager()->getStorage('field_config')->create([
...$temp_storage['field_config_values'],
'field_storage' => $temp_storage['field_storage'],
]);
return $this->entityFormBuilder()->getForm($entity, 'default', [
'default_options' => $temp_storage['default_options'],
]);
}
}

View File

@ -0,0 +1,64 @@
<?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,
]);
}
}

View File

@ -2,13 +2,16 @@
namespace Drupal\field_ui\Form;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Entity\EntityForm;
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\Render\Element;
use Drupal\Core\TempStore\PrivateTempStore;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\TypedData\TypedDataManagerInterface;
use Drupal\Core\Url;
@ -23,6 +26,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
class FieldConfigEditForm extends EntityForm {
use FieldStorageCreationTrait;
/**
* The entity being used by this form.
*
@ -37,6 +42,20 @@ class FieldConfigEditForm extends EntityForm {
*/
protected $entityTypeBundleInfo;
/**
* The name of the entity type.
*
* @var string
*/
protected string $entityTypeId;
/**
* The entity bundle.
*
* @var string
*/
protected string $bundle;
/**
* Constructs a new FieldConfigDeleteForm object.
*
@ -44,9 +63,25 @@ class FieldConfigEditForm extends EntityForm {
* The entity type bundle info service.
* @param \Drupal\Core\TypedData\TypedDataManagerInterface $typedDataManager
* The type data manger.
* @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface|null $entityDisplayRepository
* The entity display repository.
* @param \Drupal\Core\TempStore\PrivateTempStore|null $tempStore
* The private tempstore.
*/
public function __construct(EntityTypeBundleInfoInterface $entity_type_bundle_info, protected TypedDataManagerInterface $typedDataManager) {
public function __construct(
EntityTypeBundleInfoInterface $entity_type_bundle_info,
protected TypedDataManagerInterface $typedDataManager,
protected ?EntityDisplayRepositoryInterface $entityDisplayRepository = NULL,
protected ?PrivateTempStore $tempStore = 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);
$this->entityDisplayRepository = \Drupal::service('entity_display.repository');
}
if ($this->tempStore === NULL) {
@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');
}
}
/**
@ -55,7 +90,9 @@ class FieldConfigEditForm extends EntityForm {
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.bundle.info'),
$container->get('typed_data_manager')
$container->get('typed_data_manager'),
$container->get('entity_display.repository'),
$container->get('tempstore.private')->get('field_ui')
);
}
@ -261,7 +298,34 @@ class FieldConfigEditForm extends EntityForm {
* {@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());
}
$this->messenger()->addStatus($this->t('Saved %label configuration.', ['%label' => $this->entity->getLabel()]));

View File

@ -5,7 +5,6 @@ namespace Drupal\field_ui\Form;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SortArray;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FallbackFieldTypeCategory;
@ -13,6 +12,7 @@ use Drupal\Core\Field\FieldTypeCategoryManagerInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\TempStore\PrivateTempStore;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field_ui\FieldUI;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -24,8 +24,6 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
class FieldStorageAddForm extends FormBase {
use FieldStorageCreationTrait;
/**
* The name of the entity type.
*
@ -54,13 +52,6 @@ class FieldStorageAddForm extends FormBase {
*/
protected $entityFieldManager;
/**
* The entity display repository.
*
* @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
*/
protected $entityDisplayRepository;
/**
* The field type plugin manager.
*
@ -86,17 +77,20 @@ class FieldStorageAddForm extends FormBase {
* The configuration factory.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* (optional) The entity field manager.
* @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
* (optional) The entity display repository.
* @param \Drupal\Core\TempStore\PrivateTempStore|null $tempStore
* The private tempstore.
* @param \Drupal\Core\Field\FieldTypeCategoryManagerInterface|null $fieldTypeCategoryManager
* The field type category plugin manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, FieldTypePluginManagerInterface $field_type_plugin_manager, ConfigFactoryInterface $config_factory, EntityFieldManagerInterface $entity_field_manager, EntityDisplayRepositoryInterface $entity_display_repository, protected ?FieldTypeCategoryManagerInterface $fieldTypeCategoryManager = NULL) {
public function __construct(EntityTypeManagerInterface $entity_type_manager, FieldTypePluginManagerInterface $field_type_plugin_manager, ConfigFactoryInterface $config_factory, EntityFieldManagerInterface $entity_field_manager, protected ?PrivateTempStore $tempStore = NULL, protected ?FieldTypeCategoryManagerInterface $fieldTypeCategoryManager = NULL) {
$this->entityTypeManager = $entity_type_manager;
$this->fieldTypePluginManager = $field_type_plugin_manager;
$this->configFactory = $config_factory;
$this->entityFieldManager = $entity_field_manager;
$this->entityDisplayRepository = $entity_display_repository;
if ($this->tempStore === NULL) {
@trigger_error('Calling FieldStorageAddForm::__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/3383719', E_USER_DEPRECATED);
$this->tempStore = \Drupal::service('tempstore.private')->get('field_ui');
}
if ($this->fieldTypeCategoryManager === NULL) {
@trigger_error('Calling FieldStorageAddForm::__construct() without the $fieldTypeCategoryManager argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3375740', E_USER_DEPRECATED);
$this->fieldTypeCategoryManager = \Drupal::service('plugin.manager.field.field_type_category');
@ -119,7 +113,7 @@ class FieldStorageAddForm extends FormBase {
$container->get('plugin.manager.field.field_type'),
$container->get('config.factory'),
$container->get('entity_field.manager'),
$container->get('entity_display.repository'),
$container->get('tempstore.private')->get('field_ui'),
$container->get('plugin.manager.field.field_type_category'),
);
}
@ -349,7 +343,7 @@ class FieldStorageAddForm extends FormBase {
$form['actions'] = ['#type' => 'actions'];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save and continue'),
'#value' => $this->t('Continue'),
'#button_type' => 'primary',
];
@ -421,6 +415,8 @@ class FieldStorageAddForm extends FormBase {
'entity_type' => $this->entityTypeId,
'bundle' => $this->bundle,
];
$default_options = [];
// Check if we're dealing with a preconfigured field.
if (strpos($field_storage_type, 'field_ui:') === 0) {
[, $field_type, $preset_key] = explode(':', $field_storage_type, 3);
@ -446,40 +442,32 @@ class FieldStorageAddForm extends FormBase {
];
try {
// Create the field storage.
$this->entityTypeManager->getStorage('field_storage_config')
->create($field_storage_values)->save();
// Create the field.
$field = $this->entityTypeManager->getStorage('field_config')
->create($field_values);
$field->save();
// Configure the display modes.
$this->configureEntityFormDisplay($field_name, $default_options['entity_form_display'] ?? []);
$this->configureEntityViewDisplay($field_name, $default_options['entity_view_display'] ?? []);
$field_storage_entity = $this->entityTypeManager->getStorage('field_storage_config')->create($field_storage_values);
}
catch (\Exception $e) {
$this->messenger()->addError($this->t(
'There was a problem creating field %label: @message',
['%label' => $values['label'], '@message' => $e->getMessage()]));
$this->messenger()->addError($this->t('There was a problem creating field %label: @message', ['%label' => $values['label'], '@message' => $e->getMessage()]));
return;
}
// Save field and field storage values in tempstore.
$this->tempStore->set($this->entityTypeId . ':' . $field_name, [
'field_storage' => $field_storage_entity,
'field_config_values' => $field_values,
'default_options' => $default_options,
]);
// Configure next steps in the multi-part form.
$destinations = [];
$route_parameters = [
'field_config' => $field->id(),
'entity_type' => $this->entityTypeId,
'field_name' => $field_name,
] + FieldUI::getRouteBundleParameter($entity_type, $this->bundle);
// Always show the field settings step, as the cardinality needs to be
// configured for new fields.
$destinations[] = [
'route_name' => "entity.field_config.{$this->entityTypeId}_storage_edit_form",
'route_name' => "field_ui.field_storage_add_{$this->entityTypeId}",
'route_parameters' => $route_parameters,
];
$destinations[] = [
'route_name' => "entity.field_config.{$this->entityTypeId}_field_edit_form",
'route_name' => "field_ui.field_add_{$this->entityTypeId}",
'route_parameters' => $route_parameters,
];
$destinations[] = [
@ -494,8 +482,6 @@ class FieldStorageAddForm extends FormBase {
// Store new field information for any additional submit handlers.
$form_state->set(['fields_added', '_add_new_field'], $field_name);
$this->messenger()->addMessage($this->t('Your settings have been saved.'));
}
/**

View File

@ -3,11 +3,15 @@
namespace Drupal\field_ui\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
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 Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
@ -24,6 +28,34 @@ class FieldStorageConfigEditForm extends EntityForm {
*/
protected $entity;
/**
* FieldStorageConfigEditForm constructor.
*
* @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')
);
}
/**
* {@inheritdoc}
*/
@ -64,9 +96,10 @@ class FieldStorageConfigEditForm extends EntityForm {
* {@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 = $form_state->get('field_config')->label();
$field_label = $this->entity->isNew() ? $temp_storage['field_config_values']['label'] : $form_state->get('field_config')->label();
$form['#title'] = $field_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>';
@ -85,7 +118,18 @@ class FieldStorageConfigEditForm extends EntityForm {
'entity_id' => NULL,
];
$entity = _field_create_entity_from_ids($ids);
$items = $entity->get($this->entity->getName());
if (!$this->entity->isNew()) {
$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'],
]);
$items = $this->typedDataManager->create($field_config, name: $this->entity->getName(), parent: EntityAdapter::createFromEntity($entity));
}
$item = $items->first() ?: $items->appendItem();
$form['settings'] += $item->storageSettingsForm($form, $form_state, $this->entity->hasData());
@ -164,7 +208,7 @@ class FieldStorageConfigEditForm extends EntityForm {
*/
protected function actions(array $form, FormStateInterface $form_state) {
$elements = parent::actions($form, $form_state);
$elements['submit']['#value'] = $this->t('Save field settings');
$elements['submit']['#value'] = $this->entity->isNew() ? $this->t('Continue') : $this->t('Save');
return $elements;
}
@ -218,10 +262,19 @@ class FieldStorageConfigEditForm extends EntityForm {
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$field_label = $form_state->get('field_config')->label();
// 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 {
$this->entity->save();
$this->messenger()->addStatus($this->t('Updated field %label field settings.', ['%label' => $field_label]));
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');

View File

@ -5,6 +5,8 @@ namespace Drupal\field_ui\Routing;
use Drupal\Core\Entity\EntityTypeManagerInterface;
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;
@ -109,6 +111,29 @@ class RouteSubscriber extends RouteSubscriberBase {
);
$collection->add("field_ui.field_storage_config_add_$entity_type_id", $route);
$route = new Route(
"$path/add-field/{entity_type}/{field_name}",
[
'_controller' => FieldConfigAddController::class . '::fieldConfigAddConfigureForm',
'_title' => 'Add field',
] + $defaults,
['_permission' => 'administer ' . $entity_type_id . ' fields'],
$options
);
$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",
[

View File

@ -240,7 +240,7 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
$edit = [
'settings[test_field_storage_setting]' => $string,
];
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
// Go to the field edit page.
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id);
@ -291,7 +291,7 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
'cardinality_number' => '',
];
$this->drupalGet($field_edit_path);
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Number of values is required.');
// Submit a custom number.
@ -300,8 +300,7 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
'cardinality_number' => 6,
];
$this->drupalGet($field_edit_path);
$this->submitForm($edit, 'Save field settings');
$this->assertSession()->pageTextContains('Updated field Body field settings.');
$this->submitForm($edit, 'Save');
$this->drupalGet($field_edit_path);
$this->assertSession()->fieldValueEquals('cardinality', 'number');
$this->assertSession()->fieldValueEquals('cardinality_number', 6);
@ -324,7 +323,7 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
'cardinality_number' => 1,
];
$this->drupalGet($field_edit_path);
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains("There is 1 entity with 2 or more values in this field");
// Create a second entity with three values.
@ -337,8 +336,7 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
];
$this->drupalGet($field_edit_path);
$this->submitForm($edit, 'Save field settings');
$this->assertSession()->pageTextContains('Updated field Body field settings.');
$this->submitForm($edit, 'Save');
$this->drupalGet($field_edit_path);
$this->assertSession()->fieldValueEquals('cardinality', FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$this->assertSession()->fieldValueEquals('cardinality_number', 1);
@ -350,7 +348,7 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
'cardinality_number' => 1,
];
$this->drupalGet($field_edit_path);
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains("There are 2 entities with 2 or more values in this field");
$edit = [
@ -358,7 +356,7 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
'cardinality_number' => 2,
];
$this->drupalGet($field_edit_path);
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains("There is 1 entity with 3 or more values in this field");
$edit = [
@ -366,7 +364,7 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
'cardinality_number' => 3,
];
$this->drupalGet($field_edit_path);
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
// Test the cardinality validation is not access sensitive.
@ -375,7 +373,7 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
'cardinality' => (string) FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
];
$this->drupalGet($field_edit_path);
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
$node = $this->drupalCreateNode([
'private' => TRUE,
'uid' => 0,
@ -396,21 +394,21 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
'cardinality_number' => 2,
];
$this->drupalGet($field_edit_path);
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains("There are 2 entities with 3 or more values in this field");
$edit = [
'cardinality' => 'number',
'cardinality_number' => 3,
];
$this->drupalGet($field_edit_path);
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains("There is 1 entity with 4 or more values in this field");
$edit = [
'cardinality' => 'number',
'cardinality_number' => 4,
];
$this->drupalGet($field_edit_path);
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
}
/**
@ -490,7 +488,7 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
'field_name' => $field_exceed_max_length_input,
];
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/add-field');
$this->submitForm($edit, 'Save and continue');
$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.
@ -642,14 +640,14 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
$edit['field_name'] = 'title';
$bundle_path = 'admin/structure/types/manage/' . $this->contentType;
$this->drupalGet("{$bundle_path}/fields/add-field");
$this->submitForm($edit, 'Save and continue');
$this->submitForm($edit, 'Continue');
$this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
// Try with a base field.
$edit['field_name'] = 'sticky';
$bundle_path = 'admin/structure/types/manage/' . $this->contentType;
$this->drupalGet("{$bundle_path}/fields/add-field");
$this->submitForm($edit, 'Save and continue');
$this->submitForm($edit, 'Continue');
$this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
}
@ -755,11 +753,17 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
public function testDuplicateFieldName() {
// field_tags already exists, so we're expecting an error when trying to
// create a new field with the same name.
$url = 'admin/structure/types/manage/' . $this->contentType;
$this->fieldUIAddNewField($url, 'tags', $this->randomMachineName(), 'entity_reference', [], [], FALSE);
$url = 'admin/structure/types/manage/' . $this->contentType . '/fields/add-field';
$this->drupalGet($url);
$edit = [
'label' => $this->randomMachineName(),
'field_name' => 'tags',
'new_storage_type' => 'boolean',
];
$this->submitForm($edit, 'Continue');
$this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
$this->assertSession()->addressEquals($url . '/fields/add-field');
$this->assertSession()->addressEquals($url);
}
/**
@ -770,7 +774,7 @@ class ManageFieldsFunctionalTest extends BrowserTestBase {
'query' => ['destinations' => ['http://example.com']],
];
$this->drupalGet('admin/structure/types/manage/article/fields/node.article.body/storage', $options);
$this->submitForm([], 'Save field settings');
$this->submitForm([], 'Save');
// 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()->statusCodeEquals(200);

View File

@ -2,7 +2,11 @@
namespace Drupal\Tests\field_ui\Functional;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
use Drupal\user\Entity\User;
// cSpell:ignore downlander
@ -13,10 +17,12 @@ use Drupal\Tests\BrowserTestBase;
*/
class ManageFieldsTest extends BrowserTestBase {
use FieldUiTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'field_test',
'field_ui',
'field_ui_test',
'node',
@ -28,13 +34,20 @@ class ManageFieldsTest extends BrowserTestBase {
*/
protected $defaultTheme = 'stark';
/**
* A user with permission to administer node fields, etc.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$account = $this->drupalCreateUser(['administer node fields']);
$this->drupalLogin($account);
$this->adminUser = $this->drupalCreateUser(['administer node fields']);
$this->drupalLogin($this->adminUser);
$this->config('system.logging')
->set('error_level', ERROR_REPORTING_DISPLAY_ALL)
->save();
@ -112,4 +125,182 @@ class ManageFieldsTest extends BrowserTestBase {
$this->assertStringContainsString($allowed_bundles_text, $element->getText());
}
/**
* Tests adding a field.
*/
public function testAddField() {
$page = $this->getSession()->getPage();
$type = $this->drupalCreateContentType([
'name' => 'Article',
'type' => 'article',
]);
// Create a new field without actually saving it.
$this->fieldUIAddNewField('admin/structure/types/manage/' . $type->id(), 'test_field', 'Test field', 'test_field', [], [], FALSE);
// Assert that the field was not created.
$this->assertNull(FieldStorageConfig::loadByName('node', "field_test_field"));
$this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/fields/add-field');
$edit = [
'label' => 'Test field',
'field_name' => 'test_field',
'new_storage_type' => 'test_field',
];
$this->submitForm($edit, 'Continue');
$this->assertSession()->statusMessageNotContains('Saved');
// Change the storage form values.
$edit = ['cardinality_number' => 5];
$this->submitForm($edit, 'Continue');
$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());
// Try creating a field with the same machine name.
$this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/fields/add-field');
$edit = [
'label' => 'Test field',
'field_name' => 'test_field',
'new_storage_type' => 'test_field',
];
$this->submitForm($edit, 'Continue');
// Assert that the values in the field storage form are reset.
$this->assertEquals(1, $page->findField('cardinality_number')->getValue());
// Assert that the field is created with the new settings.
$this->submitForm([], 'Continue');
$this->assertSession()->statusMessageNotContains('Saved');
$this->submitForm([], 'Save settings');
$this->assertSession()->statusMessageContains('Saved');
$this->assertEquals(1, FieldStorageConfig::loadByName('node', 'field_test_field')->getCardinality());
}
/**
* Tests multiple users adding a field with the same name.
*/
public function testAddFieldWithMultipleUsers() {
$page = $this->getSession()->getPage();
// Create two users.
$user1 = $this->drupalCreateUser(['administer node fields']);
$user2 = $this->drupalCreateUser(['administer node fields']);
$node_type = $this->drupalCreateContentType();
$bundle_path = '/admin/structure/types/manage/' . $node_type->id();
// Start adding a field as user 1, stop prior to saving, but keep the URL.
$this->drupalLogin($user1);
$this->drupalGet($bundle_path . '/fields/add-field');
$edit = [
'label' => 'Test field',
'field_name' => 'test_field',
'new_storage_type' => 'test_field',
];
$this->submitForm($edit, 'Continue');
// Make changes to the storage form.
$edit = ['cardinality_number' => 5];
$storage_form_url = $this->getUrl();
$this->submitForm($edit, 'Continue');
$this->drupalLogout();
// Actually add a field as user 2.
$this->drupalLogin($user2);
$this->drupalGet($bundle_path . '/fields/add-field');
$edit = [
'label' => 'Test field',
'field_name' => 'test_field',
'new_storage_type' => 'test_field',
];
$this->submitForm($edit, 'Continue');
$allowed_no_of_values = $page->findField('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([], 'Save settings');
$this->assertSession()->pageTextContains("Saved Test field configuration.");
$this->drupalLogout();
// 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');
$this->submitForm([], 'Save settings');
// An error is thrown only after the final 'Save'.
$this->assertSession()->statusMessageContains("An error occurred while saving the field: 'field_storage_config' entity with ID 'node.field_test_field' already exists.");
}
/**
* Tests editing field when the field exists in temp store.
*/
public function testEditFieldWithLeftOverFieldInTempStore() {
$user = $this->drupalCreateUser(['administer node fields']);
$node_type = $this->drupalCreateContentType();
$bundle_path = '/admin/structure/types/manage/' . $node_type->id();
// Start adding a field but stop prior to saving.
$this->drupalLogin($user);
$this->drupalGet($bundle_path . '/fields/add-field');
$edit = [
'label' => 'Test field',
'field_name' => 'test_field',
'new_storage_type' => 'test_field',
];
$this->submitForm($edit, 'Continue');
/** @var \Drupal\field\FieldStorageConfigInterface $storage */
$storage = $this->container->get('entity_type.manager')
->getStorage('field_storage_config')
->create([
'type' => 'test_field',
'field_name' => 'test_field',
'entity_type' => 'node',
]);
$storage->save();
$this->container->get('entity_type.manager')
->getStorage('field_config')
->create([
'field_storage' => $storage,
'bundle' => $node_type->id(),
'entity_type' => 'node',
])
->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');
}
/**
* Tests creating entity reference field to non-bundleable entity type.
*/
public function testEntityReferenceToNonBundleableEntity() {
$type = $this->drupalCreateContentType([
'name' => 'kittens',
'type' => 'kittens',
]);
$bundle_path = 'admin/structure/types/manage/' . $type->id();
$field_name = 'field_user_reference';
$field_edit = [
'set_default_value' => '1',
"default_value_input[$field_name][0][target_id]" => $this->adminUser->label() . ' (' . $this->adminUser->id() . ')',
];
$this->fieldUIAddNewField($bundle_path, 'user_reference', NULL, 'field_ui:entity_reference:user', [], $field_edit);
$field = FieldConfig::loadByName('node', 'kittens', $field_name);
$this->assertEquals([['target_id' => $this->adminUser->id()]], $field->getDefaultValue(User::create(['name' => '1337'])));
}
}

View File

@ -180,7 +180,7 @@ class ManageFieldsTest extends WebDriverTestBase {
$page->fillField('label', $field_name);
// Test validation.
$page->pressButton('Save and continue');
$page->pressButton('Continue');
$assert_session->pageTextContains('You need to select a field type.');
$assert_session->elementExists('css', '[name="new_storage_type"].error');
$assert_session->pageTextNotContains('Choose an option below');
@ -190,7 +190,7 @@ class ManageFieldsTest extends WebDriverTestBase {
$assert_session->assertWaitOnAjaxRequest();
$this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="number"]')->isSelected());
$assert_session->pageTextContains('Choose an option below');
$page->pressButton('Save and continue');
$page->pressButton('Continue');
$assert_session->pageTextContains('You need to select a field type.');
$assert_session->elementNotExists('css', '[name="new_storage_type"].error');
$assert_session->elementExists('css', '[name="group_field_options_wrapper"].error');
@ -211,9 +211,12 @@ class ManageFieldsTest extends WebDriverTestBase {
$this->assertNotEmpty($text_plain = $page->find('xpath', '//*[text() = "Text (plain)"]')->getParent());
$text_plain->click();
$this->assertTrue($assert_session->elementExists('css', '[name="group_field_options_wrapper"][value="string"]')->isSelected());
$page->pressButton('Save and continue');
$assert_session->pageTextContains('Your settings have been saved.');
$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());
$page->pressButton('Save settings');
$assert_session->pageTextContains('Saved ' . $field_name . ' configuration.');
$this->assertNotNull($field_storage = FieldStorageConfig::loadByName('node', "field_$field_name"));
$this->assertEquals('string', $field_storage->getType());
@ -237,8 +240,12 @@ class ManageFieldsTest extends WebDriverTestBase {
$this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="test_field"]')->isSelected());
$assert_session->pageTextNotContains('Choose an option below');
$page->pressButton('Save and continue');
$assert_session->pageTextContains('Your settings have been saved.');
$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');
$assert_session->pageTextContains('Saved ' . $field_name . ' configuration.');
$this->assertNotNull($field_storage = FieldStorageConfig::loadByName('node', "field_$field_name"));
$this->assertEquals('test_field', $field_storage->getType());
}

View File

@ -61,17 +61,11 @@ trait FieldUiJSTestTrait {
$this->assertTrue($field_field_name->isVisible());
$field_field_name->setValue($field_name);
$page->findButton('Save and continue')->click();
$page->findButton('Continue')->click();
$assert_session->waitForText("These settings apply to the $label field everywhere it is used.");
if ($save_settings) {
$breadcrumb_link = $page->findLink($label);
// Test breadcrumb.
$this->assertTrue($breadcrumb_link->isVisible());
// Second step: 'Storage settings' form.
$page->findButton('Save field settings')->click();
$assert_session->pageTextContains("Updated field $label field settings.");
$page->findButton('Continue')->click();
// Third step: 'Field settings' form.
$page->findButton('Save settings')->click();

View File

@ -42,12 +42,12 @@ trait FieldUiTestTrait {
// page before calling this method.
if ($bundle_path !== NULL) {
$bundle_path = "$bundle_path/fields/add-field";
}
// First step: 'Add field' page.
if ($bundle_path !== NULL) {
// First step: 'Add field' page.
$this->drupalGet($bundle_path);
}
else {
$bundle_path = $this->getUrl();
}
try {
// First check if the passed in field type is not part of a group.
@ -76,27 +76,25 @@ trait FieldUiTestTrait {
];
}
}
$this->submitForm($initial_edit, 'Save and continue');
$this->submitForm($initial_edit, 'Continue');
// Assert that the field is not created.
$this->assertFieldDoesNotExist($bundle_path, $label);
if ($save_settings) {
$this->assertSession()->pageTextContains("These settings apply to the $label field everywhere it is used.");
// Test Breadcrumbs.
$this->getSession()->getPage()->findLink($label);
// Second step: 'Storage settings' form.
$this->submitForm($storage_edit, 'Save field settings');
$this->assertSession()
->pageTextContains("Updated field $label field settings.");
$this->submitForm($storage_edit, 'Continue');
// Assert that the field is not created.
$this->assertFieldDoesNotExist($bundle_path, $label);
// Third step: 'Field settings' form.
$this->submitForm($field_edit, 'Save settings');
$this->assertSession()->pageTextContains("Saved $label configuration.");
// Check that the field appears in the overview form.
$xpath = $this->assertSession()
->buildXPathQuery("//table[@id=\"field-overview\"]//tr/td[1 and text() = :label]", [
':label' => $label,
]);
$this->assertSession()->elementExists('xpath', $xpath);
$this->assertFieldExistsOnOverview($label);
}
}
@ -205,4 +203,53 @@ trait FieldUiTestTrait {
return NULL;
}
/**
* Asserts that the field doesn't exist in the overview form.
*
* @param string $bundle_path
* The bundle path.
* @param string $label
* The field label.
*/
protected function assertFieldDoesNotExist(string $bundle_path, string $label) {
$original_url = $this->getUrl();
$this->drupalGet(explode('/fields', $bundle_path)[0] . '/fields');
$this->assertFieldDoesNotExistOnOverview($label);
$this->drupalGet($original_url);
}
/**
* Asserts that the field appears on the overview form.
*
* @param string $label
* The field label.
*
* @throws \Behat\Mink\Exception\ElementNotFoundException
*/
protected function assertFieldExistsOnOverview(string $label) {
$xpath = $this->assertSession()
->buildXPathQuery("//table[@id=\"field-overview\"]//tr/td[1 and text() = :label]", [
':label' => $label,
]);
$element = $this->getSession()->getPage()->find('xpath', $xpath);
if ($element === NULL) {
throw new ElementNotFoundException($this->getSession()->getDriver(), 'form field', 'label', $label);
}
}
/**
* Asserts that the field does not appear on the overview form.
*
* @param string $label
* The field label.
*/
protected function assertFieldDoesNotExistOnOverview(string $label) {
$xpath = $this->assertSession()
->buildXPathQuery("//table[@id=\"field-overview\"]//tr/td[1 and text() = :label]", [
':label' => $label,
]);
$element = $this->getSession()->getPage()->find('xpath', $xpath);
$this->assertSession()->assert($element === NULL, sprintf('A field "%s" appears on this page, but it should not.', $label));
}
}

View File

@ -2,6 +2,8 @@
namespace Drupal\Tests\field_ui\Unit;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\TempStore\PrivateTempStore;
use Drupal\field_ui\Form\FieldConfigEditForm;
use Drupal\Tests\UnitTestCase;
@ -27,7 +29,9 @@ class FieldConfigEditFormTest extends UnitTestCase {
$entity_type_bundle_info = $this->createMock('\Drupal\Core\Entity\EntityTypeBundleInfoInterface');
$typed_data = $this->createMock('\Drupal\Core\TypedData\TypedDataManagerInterface');
$this->fieldConfigEditForm = new FieldConfigEditForm($entity_type_bundle_info, $typed_data);
$temp_store = $this->createMock(PrivateTempStore::class);
$entity_display_repository = $this->createMock(EntityDisplayRepositoryInterface::class);
$this->fieldConfigEditForm = new FieldConfigEditForm($entity_type_bundle_info, $typed_data, $entity_display_repository, $temp_store);
}
/**

View File

@ -260,7 +260,7 @@ class FileFieldWidgetTest extends FileFieldTestBase {
// 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");
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
$node = $node_storage->loadUnchanged($nid);
$node_file = File::load($node->{$field_name}->target_id);

View File

@ -337,7 +337,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
$this->drupalGet('admin/structure/types/manage/article/fields/node.article.' . $field_name . '/storage');
$this->submitForm([
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
], 'Save field settings');
], 'Save');
$edit = [
'files[' . $field_name . '_1][]' => \Drupal::service('file_system')->realpath($test_image->uri),
];
@ -504,7 +504,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
'settings[default_image][title]' => $title,
];
$this->drupalGet("admin/structure/types/manage/article/fields/node.article.{$field_name}/storage");
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
// Clear field definition cache so the new default image is detected.
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
@ -560,7 +560,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
// 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->getSession()->getPage()->pressButton('Save field settings');
$this->getSession()->getPage()->pressButton('Save');
// Clear field definition cache so the new default image is detected.
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
@ -579,7 +579,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
'settings[default_image][title]' => $title,
];
$this->drupalGet('admin/structure/types/manage/article/fields/node.article.' . $private_field_name . '/storage');
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
// Clear field definition cache so the new default image is detected.
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();

View File

@ -196,7 +196,8 @@ class MediaUiFunctionalTest extends MediaFunctionalTestBase {
$assert_session = $this->assertSession();
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Page']);
$this->fieldUIAddNewField('/admin/structure/types/manage/page', 'foo_field', 'Foo field', 'field_ui:entity_reference:media', [], [], FALSE);
$this->createMediaType('image', ['id' => 'image', 'new_revision' => TRUE]);
$this->fieldUIAddNewField('/admin/structure/types/manage/page', 'foo_field', 'Foo field', 'field_ui:entity_reference:media', [], ['settings[handler_settings][target_bundles][image]' => TRUE]);
$this->drupalGet('/admin/structure/types/manage/page/display');
$assert_session->fieldValueEquals('fields[field_foo_field][type]', 'entity_reference_entity_view');
}
@ -340,13 +341,11 @@ class MediaUiFunctionalTest extends MediaFunctionalTestBase {
// settings form.
// Using submitForm() to avoid dealing with JavaScript on the previous
// page in the field creation.
$this->fieldUIAddNewField("admin/structure/types/manage/{$content_type->id()}", 'media_reference', "Media (cardinality $cardinality)", 'field_ui:entity_reference:media', [], [], FALSE);
$edit = [];
$field_edit = [];
foreach ($media_types as $type) {
$edit["settings[handler_settings][target_bundles][$type]"] = TRUE;
$field_edit["settings[handler_settings][target_bundles][$type]"] = TRUE;
}
$this->drupalGet("admin/structure/types/manage/{$content_type->id()}/fields/node.{$content_type->id()}.field_media_reference");
$this->submitForm($edit, "Save settings");
$this->fieldUIAddNewField("admin/structure/types/manage/{$content_type->id()}", 'media_reference', "Media (cardinality $cardinality)", 'field_ui:entity_reference:media', [], $field_edit);
\Drupal::entityTypeManager()
->getStorage('entity_form_display')
->load('node.' . $content_type->id() . '.default')

View File

@ -70,8 +70,10 @@ class FieldUiIntegrationTest extends MediaLibraryTestBase {
$this->assertNotNull($assert_session->waitForField('label'));
$page->fillField('label', 'Shatner');
$this->waitForText('field_shatner');
$page->pressButton('Save and continue');
$page->pressButton('Save field settings');
$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();
$this->assertElementExistsAfterWait('css', '[name="settings[handler_settings][target_bundles][type_one]"][checked="checked"]');

View File

@ -560,7 +560,7 @@ class NodeTranslationUITest extends ContentTranslationUITestBase {
// details form element.
$edit = ['cardinality_number' => 2];
$this->drupalGet('admin/structure/types/manage/article/fields/node.article.field_image/storage');
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
// Make the image field non-translatable.
$edit = ['settings[node][article][fields][field_image]' => FALSE];

View File

@ -172,8 +172,8 @@ class NodeTypeTranslationTest extends BrowserTestBase {
'new_storage_type' => 'email',
'label' => 'Email',
'field_name' => 'email',
], 'Save and continue');
$this->submitForm([], 'Save field settings');
], 'Continue');
$this->submitForm([], 'Continue');
$this->submitForm([], 'Save settings');
$type = $this->randomMachineName(16);

View File

@ -373,7 +373,7 @@ class OptionsFieldUITest extends FieldTestBase {
$add_button->click();
$add_button->click();
$this->submitForm($input, 'Save field settings');
$this->submitForm($input, 'Save');
// Verify that the page does not have double escaped HTML tags.
$this->assertSession()->responseNotContains('&amp;lt;');
@ -406,8 +406,7 @@ class OptionsFieldUITest extends FieldTestBase {
$this->drupalGet($this->adminPath);
$page = $this->getSession()->getPage();
$page->findButton('Add another item')->click();
$this->submitForm($edit, 'Save field settings');
$this->assertSession()->pageTextContains('Updated field ' . $this->fieldName . ' field settings.');
$this->submitForm($edit, 'Save');
// Select a default value.
$edit = [
@ -469,7 +468,7 @@ class OptionsFieldUITest extends FieldTestBase {
// Assert that the button is disabled again.
$this->assertTrue($delete_button_0->hasAttribute('disabled'), 'Button is disabled');
// Try to proceed without entering any value.
$page->findButton('Save field settings')->click();
$page->findButton('Save')->click();
if ($field_type == 'list_string') {
// Asserting only name field as there is no value field for list_string.

View File

@ -78,7 +78,7 @@ class OptionsFloatFieldImportTest extends FieldTestBase {
'settings[allowed_values][table][1][item][label]' => 'One',
];
$this->drupalGet($admin_path);
$this->submitForm($edit, 'Save field settings');
$this->submitForm($edit, 'Save');
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
$this->assertSame($array = ['0' => 'Zero', '1' => 'One'], $field_storage->getSetting('allowed_values'));

View File

@ -162,7 +162,7 @@ class OptionsFieldUITest extends WebDriverTestBase {
$this->assertHasFocusByAttribute('name', $key_element_name);
$this->assertAllowValuesRowCount($expected_rows);
}
$page->pressButton('Save field settings');
$page->pressButton('Save');
// Test the order of the option list on node form.
$this->drupalGet($this->nodeFormPath);
@ -177,7 +177,7 @@ class OptionsFieldUITest extends WebDriverTestBase {
// Change the order the items appear.
$drag_handle->dragTo($target);
$this->assertOrder(['Second', 'Third', 'First', ''], $is_string_option);
$page->pressButton('Save field settings');
$page->pressButton('Save');
$this->drupalGet($this->nodeFormPath);
$this->assertNodeFormOrder(['- None -', 'Second', 'Third', 'First']);
@ -191,7 +191,7 @@ class OptionsFieldUITest extends WebDriverTestBase {
$page->pressButton('remove_row_button__1');
$this->assertSession()->assertWaitOnAjaxRequest();
$this->assertOrder(['Second', 'First', ''], $is_string_option);
$page->pressButton('Save field settings');
$page->pressButton('Save');
$this->drupalGet($this->nodeFormPath);
$this->assertNodeFormOrder(['- None -', 'Second', 'First']);

View File

@ -157,7 +157,7 @@ class SearchPageCacheTagsTest extends BrowserTestBase {
]);
$this->drupalLogin($admin_user);
$this->fieldUIAddNewField($bundle_path, 'test__ref', 'Test label', 'entity_reference', [], [], FALSE);
$this->fieldUIAddNewField($bundle_path, 'test__ref', 'Test label', 'entity_reference', [], ['settings[handler_settings][target_bundles][page]' => TRUE]);
// Create a new node of our newly created node type and fill in the entity
// reference field.
$edit = [