Skip to content
Snippets Groups Projects
Commit e8787136 authored by Alex Bronstein's avatar Alex Bronstein
Browse files

Issue #2327883 by phenaproxima, lauriii, Wim Leers, yched, amateescu,...

Issue #2327883 by phenaproxima, lauriii, Wim Leers, yched, amateescu, borisson_, Berdir, larowlan, jibran: Field [storage] config have incomplete settings until they are saved
parent 27aba04e
No related branches found
No related tags found
40 merge requests!54479.5.x SF update,!5014Issue #3071143: Table Render Array Example Is Incorrect,!4868Issue #1428520: Improve menu parent link selection,!3878Removed unused condition head title for views,!38582585169-10.1.x,!3818Issue #2140179: $entity->original gets stale between updates,!3742Issue #3328429: Create item list field formatter for displaying ordered and unordered lists,!3731Claro: role=button on status report items,!3668Resolve #3347842 "Deprecate the trusted",!3651Issue #3347736: Create new SDC component for Olivero (header-search),!3546refactored dialog.pcss file,!3531Issue #3336994: StringFormatter always displays links to entity even if the user in context does not have access,!3502Issue #3335308: Confusing behavior with FormState::setFormState and FormState::setMethod,!3452Issue #3332701: Refactor Claro's tablesort-indicator stylesheet,!3451Issue #2410579: Allows setting the current language programmatically.,!3355Issue #3209129: Scrolling problems when adding a block via layout builder,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key,!3154Fixes #2987987 - CSRF token validation broken on routes with optional parameters.,!3147Issue #3328457: Replace most substr($a, $i) where $i is negative with str_ends_with(),!3146Issue #3328456: Replace substr($a, 0, $i) with str_starts_with(),!3133core/modules/system/css/components/hidden.module.css,!31312878513-10.1.x,!2812Issue #3312049: [Followup] Fix Drupal.Commenting.FunctionComment.MissingReturnType returns for NULL,!2614Issue #2981326: Replace non-test usages of \Drupal::logger() with IoC injection,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2334Issue #3228209: Add hasRole() method to AccountInterface,!2062Issue #3246454: Add weekly granularity to views date sort,!1591Issue #3199697: Add JSON:API Translation experimental module,!1255Issue #3238922: Refactor (if feasible) uses of the jQuery serialize function to use vanillaJS,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!877Issue #2708101: Default value for link text is not saved,!844Resolve #3036010 "Updaters",!673Issue #3214208: FinishResponseSubscriber could create duplicate headers,!617Issue #3043725: Provide a Entity Handler for user cancelation,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493,!485Sets the autocomplete attribute for username/password input field on login form.,!30Issue #3182188: Updates composer usage to point at ./vendor/bin/composer
......@@ -279,6 +279,13 @@ public function postCreate(EntityStorageInterface $storage) {
if (empty($this->field_type)) {
$this->field_type = $this->getFieldStorageDefinition()->getType();
}
// Make sure all expected runtime settings are present.
$default_settings = \Drupal::service('plugin.manager.field.field_type')
->getDefaultFieldSettings($this->getType());
// Filter out any unknown (unsupported) settings.
$supported_settings = array_intersect_key($this->getSettings(), $default_settings);
$this->set('settings', $supported_settings + $default_settings);
}
/**
......
......@@ -386,6 +386,34 @@ function field_field_storage_config_update(FieldStorageConfigInterface $field_st
}
}
/**
* Implements hook_ENTITY_TYPE_create() for 'field_config'.
*
* Determine the selection handler plugin ID for an entity reference field.
*/
function field_field_config_create(FieldConfigInterface $field) {
// 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($field->getType());
if ($class !== $item_class && !is_subclass_of($class, $item_class)) {
return;
}
// If we don't know the target type yet, there's nothing else we can do.
$target_type = $field->getFieldStorageDefinition()->getSetting('target_type');
if (empty($target_type)) {
return;
}
// Make sure the selection handler plugin is the correct derivative for the
// target entity type.
$selection_manager = \Drupal::service('plugin.manager.entity_reference_selection');
[$current_handler] = explode(':', $field->getSetting('handler'), 2);
$field->setSetting('handler', $selection_manager->getPluginId($target_type, $current_handler));
}
/**
* Implements hook_ENTITY_TYPE_presave() for 'field_config'.
*
......@@ -396,6 +424,7 @@ function field_field_config_presave(FieldConfigInterface $field) {
if ($field->isSyncing()) {
return;
}
field_field_config_create($field);
// Act on all sub-types of the entity_reference field type.
/** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
......@@ -406,13 +435,6 @@ function field_field_config_presave(FieldConfigInterface $field) {
return;
}
// Make sure the selection handler plugin is the correct derivative for the
// target entity type.
$target_type = $field->getFieldStorageDefinition()->getSetting('target_type');
$selection_manager = \Drupal::service('plugin.manager.entity_reference_selection');
[$current_handler] = explode(':', $field->getSetting('handler'), 2);
$field->setSetting('handler', $selection_manager->getPluginId($target_type, $current_handler));
// In case we removed all the target bundles allowed by the field in
// EntityReferenceItem::onDependencyRemoval() or field_entity_bundle_delete()
// we have to log a critical message because the field will not function
......
......@@ -279,6 +279,28 @@ public function id() {
return $this->getTargetEntityTypeId() . '.' . $this->getName();
}
/**
* {@inheritdoc}
*/
public function postCreate(EntityStorageInterface $storage) {
parent::postCreate($storage);
// Check that the field type is known.
$field_type = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->type, FALSE);
if (!$field_type) {
throw new FieldException("Attempt to create a field storage of unknown type {$this->type}.");
}
$this->module = $field_type['provider'];
// Make sure all expected runtime settings are present.
$default_settings = \Drupal::service('plugin.manager.field.field_type')
->getDefaultStorageSettings($this->getType());
// Filter out any unknown (unsupported) settings.
$supported_settings = array_intersect_key($this->getSettings(), $default_settings);
$this->set('settings', $supported_settings + $default_settings);
}
/**
* Overrides \Drupal\Core\Entity\Entity::preSave().
*
......@@ -319,7 +341,6 @@ public function preSave(EntityStorageInterface $storage) {
*/
protected function preSaveNew(EntityStorageInterface $storage) {
$entity_field_manager = \Drupal::service('entity_field.manager');
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
// Assign the ID.
$this->id = $this->id();
......@@ -337,13 +358,6 @@ protected function preSaveNew(EntityStorageInterface $storage) {
throw new FieldException("Attempt to create field storage {$this->getName()} which is reserved by entity type {$this->getTargetEntityTypeId()}.");
}
// Check that the field type is known.
$field_type = $field_type_manager->getDefinition($this->getType(), FALSE);
if (!$field_type) {
throw new FieldException("Attempt to create a field storage of unknown type {$this->getType()}.");
}
$this->module = $field_type['provider'];
// Notify the field storage definition listener.
\Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionCreate($this);
}
......
<?php
declare(strict_types=1);
namespace Drupal\Tests\field\Kernel\Entity;
use Drupal\entity_test\Entity\EntityTestBundle;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the ways that field entities handle their settings.
*
* @group field
*/
class FieldEntitySettingsTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['entity_test', 'field'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
EntityTestBundle::create(['id' => 'test', 'label' => 'Test'])->save();
}
/**
* @group legacy
*/
public function testFieldEntitiesCarryDefaultSettings(): void {
/** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
$field_storage = FieldStorageConfig::create([
'type' => 'integer',
'entity_type' => 'entity_test',
'field_name' => 'test',
]);
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'test',
]);
/** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $plugin_manager */
$plugin_manager = $this->container->get('plugin.manager.field.field_type');
$default_storage_settings = $plugin_manager->getDefaultStorageSettings('integer');
$default_field_settings = $plugin_manager->getDefaultFieldSettings('integer');
// Both entities should have the complete, default settings for their
// field type.
$this->assertSame($default_storage_settings, $field_storage->get('settings'));
$this->assertSame($default_field_settings, $field->get('settings'));
// If we try to set incomplete settings, the existing values should be
// retained.
$storage_settings = $field_storage->setSettings(['size' => 'big'])
->get('settings');
// There should be no missing settings.
$missing_storage_settings = array_diff_key($default_storage_settings, $storage_settings);
$this->assertEmpty($missing_storage_settings);
// The value we set should be remembered.
$this->assertSame('big', $storage_settings['size']);
$field_settings = $field->setSetting('min', 10)->getSettings();
$missing_field_settings = array_diff_key($default_field_settings, $field_settings);
$this->assertEmpty($missing_field_settings);
$this->assertSame(10, $field_settings['min']);
$field_settings = $field->setSettings(['max' => 39])->get('settings');
$missing_field_settings = array_diff_key($default_field_settings, $field_settings);
$this->assertEmpty($missing_field_settings);
$this->assertSame(39, $field_settings['max']);
// Test that saving settings with incomplete settings is not triggering
// error, and values are retained.
$field_storage->save();
$missing_storage_settings = array_diff_key($default_storage_settings, $storage_settings);
$this->assertEmpty($missing_storage_settings);
// The value we set should be remembered.
$this->assertSame('big', $storage_settings['size']);
$field->save();
$missing_field_settings = array_diff_key($default_field_settings, $field_settings);
$this->assertEmpty($missing_field_settings);
$this->assertSame(39, $field_settings['max']);
}
/**
* Tests entity reference settings are normalized on field creation and save.
*/
public function testEntityReferenceSettingsNormalized(): void {
$field_storage = FieldStorageConfig::create([
'field_name' => 'test_reference',
'type' => 'entity_reference',
'entity_type' => 'entity_test',
'cardinality' => 1,
'settings' => [
'target_type' => 'entity_test',
],
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'test',
'label' => 'Test Reference',
'settings' => [
'handler' => 'default',
],
]);
$this->assertSame('default:entity_test', $field->getSetting('handler'));
// If the handler is changed, it should be normalized again on pre-save.
$field->setSetting('handler', 'default')->save();
$this->assertSame('default:entity_test', $field->getSetting('handler'));
}
}
......@@ -113,14 +113,19 @@ public function testConfigurableFieldSettings() {
'type' => 'test_field',
]);
$field_storage->save();
$expected_settings = [
'test_field_storage_setting' => 'dummy test string',
'changeable' => 'a changeable field storage setting',
'unchangeable' => 'an unchangeable field storage setting',
'translatable_storage_setting' => 'a translatable field storage setting',
'storage_setting_from_config_data' => 'TRUE',
];
$this->assertEquals($expected_settings, $field_storage->getSettings());
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'entity_test',
]);
// Note: FieldConfig does not populate default settings until the config
// is saved.
// @todo Remove once https://www.drupal.org/node/2327883 is fixed.
$field->save();
// Check that the default settings have been populated. Note: getSettings()
// returns both storage and field settings.
......@@ -131,7 +136,6 @@ public function testConfigurableFieldSettings() {
'translatable_storage_setting' => 'a translatable field storage setting',
'test_field_setting' => 'dummy test string',
'translatable_field_setting' => 'a translatable field setting',
'field_setting_from_config_data' => 'TRUE',
'storage_setting_from_config_data' => 'TRUE',
];
$this->assertEquals($expected_settings, $field->getSettings());
......@@ -141,6 +145,20 @@ public function testConfigurableFieldSettings() {
$expected_settings['test_field_setting'] = 'another test string';
$field->setSettings(['test_field_setting' => $expected_settings['test_field_setting']]);
$this->assertEquals($expected_settings, $field->getSettings());
// Save the field and check the expected settings.
$field->save();
$expected_settings['field_setting_from_config_data'] = 'TRUE';
$this->assertEquals($expected_settings, $field->getSettings());
$field = FieldConfig::loadByName('entity_test', 'entity_test', 'test_field');
$this->assertEquals($expected_settings, $field->getSettings());
$expected_settings['test_field_setting'] = 'yet another test string';
$field->setSettings(['test_field_setting' => $expected_settings['test_field_setting']]);
$this->assertEquals($expected_settings, $field->getSettings());
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment