diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php index 2238a31..e99397e 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstaller.php +++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php @@ -225,16 +225,12 @@ protected function createConfiguration($collection, array $config_to_install) { if ($this->getActiveStorage($collection)->exists($name)) { $id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix()); $entity = $entity_storage->load($id); - foreach ($new_config->get() as $property => $value) { - $entity->set($property, $value); - } - $entity->save(); + $entity = $entity_storage->updateFromStorageRecord($entity, $new_config->get()); } else { - $entity_storage - ->create($new_config->get()) - ->save(); + $entity = $entity_storage->createFromStorageRecord($new_config->get()); } + $entity->save(); } else { $new_config->save(); diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php index 0e071b8..4cad074 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php @@ -347,7 +347,7 @@ public function getQueryServicename() { * {@inheritdoc} */ public function importCreate($name, Config $new_config, Config $old_config) { - $entity = $this->create($new_config->get()); + $entity = $this->createFromStorageRecord($new_config->get()); $entity->setSyncing(TRUE); $entity->save(); return TRUE; @@ -363,16 +363,7 @@ public function importUpdate($name, Config $new_config, Config $old_config) { throw new ConfigImporterException(String::format('Attempt to update non-existing entity "@id".', array('@id' => $id))); } $entity->setSyncing(TRUE); - $entity->original = clone $entity; - - foreach ($old_config->get() as $property => $value) { - $entity->original->set($property, $value); - } - - foreach ($new_config->get() as $property => $value) { - $entity->set($property, $value); - } - + $entity = $this->updateFromStorageRecord($entity, $new_config->get()); $entity->save(); return TRUE; } @@ -392,15 +383,44 @@ public function importDelete($name, Config $new_config, Config $old_config) { * {@inheritdoc} */ public function importRename($old_name, Config $new_config, Config $old_config) { - $id = static::getIDFromConfigName($old_name, $this->entityType->getConfigPrefix()); - $entity = $this->load($id); - $entity->setSyncing(TRUE); - $data = $new_config->get(); - foreach ($data as $key => $value) { - $entity->set($key, $value); + return $this->importUpdate($old_name, $new_config, $old_config); + } + + /** + * {@inheritdoc} + */ + public function createFromStorageRecord(array $values) { + // Assign a new UUID if there is none yet. + if ($this->uuidKey && $this->uuidService && !isset($values[$this->uuidKey])) { + $values[$this->uuidKey] = $this->uuidService->generate(); } - $entity->save(); - return TRUE; + $data = $this->mapFromStorageRecords(array($values)); + $entity = current($data); + $entity->original = clone $entity; + $entity->enforceIsNew(); + $entity->postCreate($this); + + // Modules might need to add or change the data initially held by the new + // entity object, for instance to fill-in default values. + $this->invokeHook('create', $entity); + return $entity; + } + + /** + * {@inheritdoc} + */ + public function updateFromStorageRecord(ConfigEntityInterface $entity, array $values) { + $entity->original = clone $entity; + + $data = $this->mapFromStorageRecords(array($values)); + $updated_entity = current($data); + + foreach (array_keys($values) as $property) { + $value = $updated_entity->get($property); + $entity->set($property, $value); + } + + return $entity; } } diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorageInterface.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorageInterface.php index c034101..62c36f9 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorageInterface.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorageInterface.php @@ -29,4 +29,40 @@ */ public static function getIDFromConfigName($config_name, $config_prefix); + /** + * Creates a configuration entity from storage values. + * + * Allows the configuration entity storage to massage storage values before + * creating an entity. + * + * @param array $values + * The array of values from the configuration storage. + * + * @return ConfigEntityInterface + * The configuration entity. + * + * @see \Drupal\Core\Entity\EntityStorageBase::mapFromStorageRecords() + * @see \Drupal\field\FieldStorageConfigStorage::mapFromStorageRecords() + */ + public function createFromStorageRecord(array $values); + + /** + * Updates a configuration entity from storage values. + * + * Allows the configuration entity storage to massage storage values before + * updating an entity. + * + * @param ConfigEntityInterface $entity + * The configuration entity to update. + * @param array $values + * The array of values from the configuration storage. + * + * @return ConfigEntityInterface + * The configuration entity. + * + * @see \Drupal\Core\Entity\EntityStorageBase::mapFromStorageRecords() + * @see \Drupal\field\FieldStorageConfigStorage::mapFromStorageRecords() + */ + public function updateFromStorageRecord(ConfigEntityInterface $entity, array $values); + } diff --git a/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php b/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php index 0836eaf..4d2bd11 100644 --- a/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php +++ b/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php @@ -94,7 +94,8 @@ protected function checkValue($key, $value) { if ($element instanceof PrimitiveInterface) { $success = ($type == 'integer' && $element instanceof IntegerInterface) || - ($type == 'double' && $element instanceof FloatInterface) || + // Allow integer values in a float field. + (($type == 'double' || $type == 'integer') && $element instanceof FloatInterface) || ($type == 'boolean' && $element instanceof BooleanInterface) || ($type == 'string' && $element instanceof StringInterface) || // Null values are allowed for all types. diff --git a/core/modules/config/src/Form/ConfigSingleImportForm.php b/core/modules/config/src/Form/ConfigSingleImportForm.php index 5d7a541..debd1cb 100644 --- a/core/modules/config/src/Form/ConfigSingleImportForm.php +++ b/core/modules/config/src/Form/ConfigSingleImportForm.php @@ -246,12 +246,16 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $this->config($this->data['config_name'])->setData($this->data['import'])->save(); drupal_set_message($this->t('The %name configuration was imported.', array('%name' => $this->data['config_name']))); } - // For a config entity, create a new entity and save it. + // For a config entity, create an entity and save it. else { try { - $entity = $this->entityManager - ->getStorage($this->data['config_type']) - ->create($this->data['import']); + $entity_storage = $this->entityManager->getStorage($this->data['config_type']); + if ($this->configExists) { + $entity = $entity_storage->updateFromStorageRecord($this->configExists, $this->data['import']); + } + else { + $entity = $entity_storage->createFromStorageRecord($this->data['import']); + } $entity->save(); drupal_set_message($this->t('The @entity_type %label was imported.', array('@entity_type' => $entity->getEntityTypeId(), '%label' => $entity->label()))); } diff --git a/core/modules/config/src/Tests/ConfigCRUDTest.php b/core/modules/config/src/Tests/ConfigCRUDTest.php index c38d3f6..39f790e 100644 --- a/core/modules/config/src/Tests/ConfigCRUDTest.php +++ b/core/modules/config/src/Tests/ConfigCRUDTest.php @@ -226,6 +226,7 @@ public function testDataTypes() { 'boolean' => TRUE, 'exp' => 1.2e+34, 'float' => 3.14159, + 'float_as_integer' => (float) 1, 'hex' => 0xC, 'int' => 99, 'octal' => 0775, diff --git a/core/modules/config/src/Tests/ConfigSingleImportExportTest.php b/core/modules/config/src/Tests/ConfigSingleImportExportTest.php index 5609f8f..7221be4 100644 --- a/core/modules/config/src/Tests/ConfigSingleImportExportTest.php +++ b/core/modules/config/src/Tests/ConfigSingleImportExportTest.php @@ -98,6 +98,26 @@ public function testImport() { $this->assertIdentical($entity->id(), 'second'); $this->assertFalse($entity->status()); $this->assertIdentical($entity->uuid(), $second_uuid); + + // Perform an update. + $import = << 'config_test', + 'import' => $import, + ); + $this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import')); + $this->assertRaw(t('Are you sure you want to update the %name @type?', array('%name' => 'second', '@type' => 'test configuration'))); + $this->drupalPostForm(NULL, array(), t('Confirm')); + $entity = $storage->load('second'); + $this->assertRaw(t('The @entity_type %label was imported.', array('@entity_type' => 'config_test', '%label' => $entity->label()))); + $this->assertIdentical($entity->label(), 'Second updated'); } /** diff --git a/core/modules/config/tests/config_test/config/install/config_test.types.yml b/core/modules/config/tests/config_test/config/install/config_test.types.yml index 4ab2cdc..060a2b0 100644 --- a/core/modules/config/tests/config_test/config/install/config_test.types.yml +++ b/core/modules/config/tests/config_test/config/install/config_test.types.yml @@ -2,6 +2,7 @@ array: [] boolean: true exp: 1.2e+34 float: 3.14159 +float_as_integer: 1 hex: 0xC int: 99 octal: 0775 diff --git a/core/modules/config/tests/config_test/config/schema/config_test.schema.yml b/core/modules/config/tests/config_test/config/schema/config_test.schema.yml index 80dc9fb..a557789 100644 --- a/core/modules/config/tests/config_test/config/schema/config_test.schema.yml +++ b/core/modules/config/tests/config_test/config/schema/config_test.schema.yml @@ -68,6 +68,9 @@ config_test.types: float: type: float label: 'Float' + float_as_integer: + type: float + label: 'Float' exp: type: float label: 'Exponential' diff --git a/core/modules/field/src/Tests/FieldImportDeleteUninstallUiTest.php b/core/modules/field/src/Tests/FieldImportDeleteUninstallUiTest.php index d7a7b6c..939cfc4 100644 --- a/core/modules/field/src/Tests/FieldImportDeleteUninstallUiTest.php +++ b/core/modules/field/src/Tests/FieldImportDeleteUninstallUiTest.php @@ -102,6 +102,16 @@ public function testImportDeleteUninstall() { $staging->write('core.extension', $core_extension); $this->drupalGet('admin/config/development/configuration'); $this->assertText('This synchronization will delete data from the fields: entity_test.field_tel, entity_test.field_text.'); + // Delete all the text fields in staging, entity_test_install() adds quite + // a few. + foreach (\Drupal::entityManager()->getFieldMap() as $entity_type => $fields) { + foreach ($fields as $field_name => $info) { + if ($info['type'] == 'text') { + $staging->delete("field.storage.$entity_type.$field_name"); + $staging->delete("field.field.$entity_type.$entity_type.$field_name"); + } + } + } // This will purge all the data, delete the field and uninstall the // Telephone and Text modules. diff --git a/core/modules/options/src/Tests/OptionsFloatFieldImportTest.php b/core/modules/options/src/Tests/OptionsFloatFieldImportTest.php new file mode 100644 index 0000000..0f8414f --- /dev/null +++ b/core/modules/options/src/Tests/OptionsFloatFieldImportTest.php @@ -0,0 +1,77 @@ +drupalCreateUser(array('synchronize configuration', 'access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'bypass node access', 'administer node fields', 'administer node display')); + $this->drupalLogin($admin_user); + } + + /** + * Tests that importing list_float fields works. + */ + public function testImport() { + $field_name = 'field_options_float'; + $type = 'options_install_test'; + + // Test the results on installing options_config_install_test. All the + // necessary configuration for this test is created by installing that + // module. + $field_storage = FieldStorageConfig::loadByName('node', $field_name); + $this->assertIdentical($field_storage->getSetting('allowed_values'), $array = array('0' => 'Zero', '0.5' => 'Point five')); + + $admin_path = 'admin/structure/types/manage/' . $type . '/fields/node.' . $type . '.' . $field_name . '/storage'; + + // Export active config to staging + $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging')); + + // Set the active to not use dots in the allowed values key names. + $edit = array('field_storage[settings][allowed_values]' => "0|Zero\n1|One"); + $this->drupalPostForm($admin_path, $edit, t('Save field settings')); + $field_storage = FieldStorageConfig::loadByName('node', $field_name); + $this->assertIdentical($field_storage->getSetting('allowed_values'), $array = array('0' => 'Zero', '1' => 'One')); + + // Import configuration with dots in the allowed values key names. This + // tests \Drupal\Core\Config\Entity\ConfigEntityStorage::importUpdate(). + $this->drupalGet('admin/config/development/configuration'); + $this->drupalPostForm(NULL, array(), t('Import all')); + $field_storage = FieldStorageConfig::loadByName('node', $field_name); + $this->assertIdentical($field_storage->getSetting('allowed_values'), $array = array('0' => 'Zero', '0.5' => 'Point five')); + + // Delete field to test creation. This tests + // \Drupal\Core\Config\Entity\ConfigEntityStorage::importCreate(). + FieldConfig::loadByName('node', $type, $field_name)->delete(); + + $this->drupalGet('admin/config/development/configuration'); + $this->drupalPostForm(NULL, array(), t('Import all')); + $field_storage = FieldStorageConfig::loadByName('node', $field_name); + $this->assertIdentical($field_storage->getSetting('allowed_values'), $array = array('0' => 'Zero', '0.5' => 'Point five')); + } + +} diff --git a/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml b/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml new file mode 100644 index 0000000..3b96a57 --- /dev/null +++ b/core/modules/options/tests/options_config_install_test/config/install/core.entity_form_display.node.options_install_test.default.yml @@ -0,0 +1,57 @@ +langcode: en +status: true +dependencies: + entity: + - field.field.node.options_install_test.body + - node.type.options_install_test + module: + - entity_reference + - text +id: node.options_install_test.default +targetEntityType: node +bundle: options_install_test +mode: default +content: + title: + type: string_textfield + weight: -5 + settings: + size: 60 + placeholder: '' + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 5 + settings: + match_operator: CONTAINS + size: 60 + autocomplete_type: tags + placeholder: '' + third_party_settings: { } + created: + type: datetime_timestamp + weight: 10 + settings: { } + third_party_settings: { } + promote: + type: boolean_checkbox + settings: + display_label: '1' + weight: 15 + third_party_settings: { } + sticky: + type: boolean_checkbox + settings: + display_label: '1' + weight: 16 + third_party_settings: { } + body: + type: text_textarea_with_summary + weight: 26 + settings: + rows: 9 + summary_rows: 3 + placeholder: '' + third_party_settings: { } +hidden: { } +third_party_settings: { } diff --git a/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml new file mode 100644 index 0000000..506ee9d --- /dev/null +++ b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.default.yml @@ -0,0 +1,28 @@ +langcode: en +status: true +dependencies: + entity: + - field.field.node.options_install_test.body + - node.type.options_install_test + module: + - text + - user +id: node.options_install_test.default +label: null +targetEntityType: node +bundle: options_install_test +mode: default +content: + links: + weight: 100 + body: + label: hidden + type: text_default + weight: 101 + settings: { } + third_party_settings: { } +hidden: + langcode: true +third_party_settings: + entity_test: + foo: bar diff --git a/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml new file mode 100644 index 0000000..356a6a2 --- /dev/null +++ b/core/modules/options/tests/options_config_install_test/config/install/core.entity_view_display.node.options_install_test.teaser.yml @@ -0,0 +1,30 @@ +langcode: en +status: true +dependencies: + entity: + - core.entity_view_mode.node.teaser + - field.field.node.options_install_test.body + - node.type.options_install_test + module: + - text + - user +id: node.options_install_test.teaser +label: null +targetEntityType: node +bundle: options_install_test +mode: teaser +content: + links: + weight: 100 + body: + label: hidden + type: text_summary_or_trimmed + weight: 101 + settings: + trim_length: 600 + third_party_settings: { } +hidden: + langcode: true +third_party_settings: + entity_test: + foo: bar diff --git a/core/modules/options/tests/options_config_install_test/config/install/field.field.node.options_install_test.body.yml b/core/modules/options/tests/options_config_install_test/config/install/field.field.node.options_install_test.body.yml new file mode 100644 index 0000000..20fd4d3 --- /dev/null +++ b/core/modules/options/tests/options_config_install_test/config/install/field.field.node.options_install_test.body.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + entity: + - field.storage.node.body + - node.type.options_install_test +id: node.options_install_test.body +field_name: body +entity_type: node +bundle: options_install_test +label: Body +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + display_summary: true +third_party_settings: { } +field_type: text_with_summary diff --git a/core/modules/options/tests/options_config_install_test/config/install/field.field.node.options_install_test.field_options_float.yml b/core/modules/options/tests/options_config_install_test/config/install/field.field.node.options_install_test.field_options_float.yml new file mode 100644 index 0000000..581f2b7 --- /dev/null +++ b/core/modules/options/tests/options_config_install_test/config/install/field.field.node.options_install_test.field_options_float.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + entity: + - field.storage.node.field_options_float + - node.type.options_install_test +id: node.options_install_test.field_options_float +field_name: field_options_float +entity_type: node +bundle: options_install_test +label: field_options_float +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: { } +third_party_settings: { } +field_type: list_float diff --git a/core/modules/options/tests/options_config_install_test/config/install/field.storage.node.field_options_float.yml b/core/modules/options/tests/options_config_install_test/config/install/field.storage.node.field_options_float.yml new file mode 100644 index 0000000..daf5c75 --- /dev/null +++ b/core/modules/options/tests/options_config_install_test/config/install/field.storage.node.field_options_float.yml @@ -0,0 +1,24 @@ +langcode: en +status: true +dependencies: + module: + - node + - options +id: node.field_options_float +field_name: field_options_float +entity_type: node +type: list_float +settings: + allowed_values: + - + value: 0 + label: Zero + - + value: 0.5 + label: 'Point five' + allowed_values_function: '' +module: options +locked: false +cardinality: 1 +translatable: true +indexes: { } diff --git a/core/modules/options/tests/options_config_install_test/config/install/node.type.options_install_test.yml b/core/modules/options/tests/options_config_install_test/config/install/node.type.options_install_test.yml new file mode 100644 index 0000000..5d843bc --- /dev/null +++ b/core/modules/options/tests/options_config_install_test/config/install/node.type.options_install_test.yml @@ -0,0 +1,11 @@ +langcode: en +status: true +dependencies: { } +name: options_install_test +type: options_install_test +description: null +help: null +new_revision: false +preview_mode: 1 +display_submitted: true +third_party_settings: { } diff --git a/core/modules/options/tests/options_test.info.yml b/core/modules/options/tests/options_config_install_test/options_config_install_test.info.yml similarity index 62% copy from core/modules/options/tests/options_test.info.yml copy to core/modules/options/tests/options_config_install_test/options_config_install_test.info.yml index add43a4..d9f4086 100644 --- a/core/modules/options/tests/options_test.info.yml +++ b/core/modules/options/tests/options_config_install_test/options_config_install_test.info.yml @@ -1,6 +1,9 @@ -name: 'Options test' +name: 'Options config install test' type: module description: 'Support module for the Options module tests.' core: 8.x package: Testing version: VERSION +dependencies: + - node + - options diff --git a/core/modules/options/tests/options_test.info.yml b/core/modules/options/tests/options_test/options_test.info.yml similarity index 100% rename from core/modules/options/tests/options_test.info.yml rename to core/modules/options/tests/options_test/options_test.info.yml diff --git a/core/modules/options/tests/options_test.module b/core/modules/options/tests/options_test/options_test.module similarity index 100% rename from core/modules/options/tests/options_test.module rename to core/modules/options/tests/options_test/options_test.module