diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index 90bb702379..1efa344782 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -4,7 +4,6 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\Cache; -use Drupal\Core\Config\Schema\SchemaIncompleteException; use Drupal\Core\Entity\Entity; use Drupal\Core\Config\ConfigDuplicateUUIDException; use Drupal\Core\Entity\EntityStorageInterface; @@ -267,18 +266,8 @@ public function toArray() { /** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */ $entity_type = $this->getEntityType(); - $properties_to_export = $entity_type->getPropertiesToExport(); - if (empty($properties_to_export)) { - $config_name = $entity_type->getConfigPrefix() . '.' . $this->id(); - $definition = $this->getTypedConfig()->getDefinition($config_name); - if (!isset($definition['mapping'])) { - throw new SchemaIncompleteException("Incomplete or missing schema for $config_name"); - } - $properties_to_export = array_combine(array_keys($definition['mapping']), array_keys($definition['mapping'])); - } - $id_key = $entity_type->getKey('id'); - foreach ($properties_to_export as $property_name => $export_name) { + foreach ($entity_type->getPropertiesToExport($this->id()) as $property_name => $export_name) { // Special handling for IDs so that computed compound IDs work. // @see \Drupal\Core\Entity\EntityDisplayBase::id() if ($property_name == $id_key) { diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php index 3b90899bac..f02de926bb 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php @@ -450,9 +450,16 @@ public function updateFromStorageRecord(ConfigEntityInterface $entity, array $va $data = $this->mapFromStorageRecords([$values]); $updated_entity = current($data); - foreach (array_keys($values) as $property) { - $value = $updated_entity->get($property); - $entity->set($property, $value); + /** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $entity_type */ + $entity_type = $this->getEntityType(); + $uuid_key = $entity_type->getKey('uuid'); + $id_key = $entity_type->getKey('id'); + foreach ($entity_type->getPropertiesToExport($updated_entity->get($id_key)) as $property) { + if ($property === $uuid_key) { + // The UUID field should not be copied. + continue; + } + $entity->set($property, $updated_entity->get($property)); } return $entity; diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php index 11c9ff1bf0..d462adb08d 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php @@ -3,6 +3,7 @@ namespace Drupal\Core\Config\Entity; use Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException; +use Drupal\Core\Config\Schema\SchemaIncompleteException; use Drupal\Core\Entity\EntityType; use Drupal\Core\Config\ConfigPrefixLengthException; @@ -142,30 +143,40 @@ protected function checkStorageClass($class) { /** * {@inheritdoc} */ - public function getPropertiesToExport() { + public function getPropertiesToExport($id = NULL) { + if (!empty($this->mergedConfigExport)) { + return $this->mergedConfigExport; + } if (!empty($this->config_export)) { - if (empty($this->mergedConfigExport)) { - // Always add default properties to be exported. - $this->mergedConfigExport = [ - 'uuid' => 'uuid', - 'langcode' => 'langcode', - 'status' => 'status', - 'dependencies' => 'dependencies', - 'third_party_settings' => 'third_party_settings', - '_core' => '_core', - ]; - foreach ($this->config_export as $property => $name) { - if (is_numeric($property)) { - $this->mergedConfigExport[$name] = $name; - } - else { - $this->mergedConfigExport[$property] = $name; - } + // Always add default properties to be exported. + $this->mergedConfigExport = [ + 'uuid' => 'uuid', + 'langcode' => 'langcode', + 'status' => 'status', + 'dependencies' => 'dependencies', + 'third_party_settings' => 'third_party_settings', + '_core' => '_core', + ]; + foreach ($this->config_export as $property => $name) { + if (is_numeric($property)) { + $this->mergedConfigExport[$name] = $name; + } + else { + $this->mergedConfigExport[$property] = $name; } } - return $this->mergedConfigExport; } - return NULL; + else { + // @todo https://www.drupal.org/project/drupal/issues/2949021 Deprecate + // fallback to schema. + $config_name = $this->getConfigPrefix() . '.' . $id; + $definition = \Drupal::service('config.typed')->getDefinition($config_name); + if (!isset($definition['mapping'])) { + throw new SchemaIncompleteException("Incomplete or missing schema for $config_name"); + } + $this->mergedConfigExport = array_combine(array_keys($definition['mapping']), array_keys($definition['mapping'])); + } + return $this->mergedConfigExport; } /** diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php index 589a63d2c1..4e257e000e 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php @@ -65,11 +65,22 @@ public function getConfigPrefix(); /** * Gets the config entity properties to export if declared on the annotation. * + * If the config entity properties are not declared it will fallback to + * determining the properties using configuration schema. + * + * param string $id + * The ID of the configuration entity. Used when checking schema instead of + * the annotation. + * * @return array|null * The properties to export or NULL if they can not be determine from the * config entity type annotation. + * + * @throws \Drupal\Core\Config\Schema\SchemaIncompleteException + * Thrown when the configuration entity type does not have the annotation or + * a configuration schema. */ - public function getPropertiesToExport(); + public function getPropertiesToExport($id = NULL); /** * Gets the keys that are available for fast lookup. 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 c25a577976..a6f3550c1a 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 @@ -158,6 +158,14 @@ config_test.foo: config_test.bar: type: config_test.foo +system.action.*.third_party.config_test: + type: mapping + label: 'Third party setting for action entity' + mapping: + integer: + type: integer + label: 'Integer' + config_test.validation: type: config_object label: 'Configuration type' diff --git a/core/modules/field/tests/src/Unit/FieldConfigEntityUnitTest.php b/core/modules/field/tests/src/Unit/FieldConfigEntityUnitTest.php index 36f9398937..10c0a7d9f5 100644 --- a/core/modules/field/tests/src/Unit/FieldConfigEntityUnitTest.php +++ b/core/modules/field/tests/src/Unit/FieldConfigEntityUnitTest.php @@ -71,13 +71,6 @@ class FieldConfigEntityUnitTest extends UnitTestCase { */ protected $fieldStorage; - /** - * The typed configuration manager used for testing. - * - * @var \Drupal\Core\Config\TypedConfigManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $typedConfigManager; - /** * The mock field type plugin manager; * @@ -98,8 +91,6 @@ protected function setUp() { $this->uuid = $this->getMock('\Drupal\Component\Uuid\UuidInterface'); - $this->typedConfigManager = $this->getMock('Drupal\Core\Config\TypedConfigManagerInterface'); - $this->fieldTypePluginManager = $this->getMock('Drupal\Core\Field\FieldTypePluginManagerInterface'); $container = new ContainerBuilder(); @@ -107,7 +98,6 @@ protected function setUp() { $container->set('entity_field.manager', $this->entityFieldManager); $container->set('entity_type.manager', $this->entityTypeManager); $container->set('uuid', $this->uuid); - $container->set('config.typed', $this->typedConfigManager); $container->set('plugin.manager.field.field_type', $this->fieldTypePluginManager); // Inject the container into entity.manager so it can defer to // entity_type.manager, etc. @@ -299,9 +289,10 @@ public function testToArray() { ->method('getKey') ->with('id') ->will($this->returnValue('id')); - $this->typedConfigManager->expects($this->once()) - ->method('getDefinition') - ->will($this->returnValue(['mapping' => array_fill_keys(array_keys($expected), '')])); + $this->entityType->expects($this->once()) + ->method('getPropertiesToExport') + ->with('test_entity_type.test_bundle.field_test') + ->will($this->returnValue(array_combine(array_keys($expected), array_keys($expected)))); $export = $field->toArray(); $this->assertEquals($expected, $export); diff --git a/core/modules/system/tests/src/Functional/Entity/ConfigEntityImportTest.php b/core/modules/system/tests/src/Functional/Entity/ConfigEntityImportTest.php index b699603755..88d9bb5d62 100644 --- a/core/modules/system/tests/src/Functional/Entity/ConfigEntityImportTest.php +++ b/core/modules/system/tests/src/Functional/Entity/ConfigEntityImportTest.php @@ -21,7 +21,7 @@ class ConfigEntityImportTest extends BrowserTestBase { * * @var array */ - public static $modules = ['action', 'block', 'filter', 'image', 'search', 'search_extra_type']; + public static $modules = ['action', 'block', 'filter', 'image', 'search', 'search_extra_type', 'config_test']; /** * {@inheritdoc} @@ -41,6 +41,7 @@ public function testConfigUpdateImport() { $this->doFilterFormatUpdate(); $this->doImageStyleUpdate(); $this->doSearchPageUpdate(); + $this->doThirdPartySettingsUpdate(); } /** @@ -172,6 +173,34 @@ protected function doSearchPageUpdate() { $this->assertConfigUpdateImport($name, $original_data, $custom_data); } + /** + * Tests updating of third party settings. + */ + protected function doThirdPartySettingsUpdate() { + // Create a test action with a known label. + $name = 'system.action.third_party_settings_test'; + + /** @var \Drupal\config_test\Entity\ConfigTest $entity */ + $entity = Action::create([ + 'id' => 'third_party_settings_test', + 'plugin' => 'action_message_action', + ]); + $entity->save(); + + $this->assertIdentical([], $entity->getThirdPartyProviders()); + // Get a copy of the configuration before the third party setting is added. + $no_third_part_setting_config = $this->container->get('config.storage')->read($name); + + // Add a third party setting. + $entity->setThirdPartySetting('config_test', 'integer', 1); + $entity->save(); + $this->assertIdentical(1, $entity->getThirdPartySetting('config_test', 'integer')); + $has_third_part_setting_config = $this->container->get('config.storage')->read($name); + + // Ensure configuration imports can completely remove third party settings. + $this->assertConfigUpdateImport($name, $has_third_part_setting_config, $no_third_part_setting_config); + } + /** * Tests that a single set of plugin config stays in sync. * diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php index 81d424b2ce..e2828cb744 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php @@ -8,7 +8,6 @@ namespace Drupal\Tests\Core\Config\Entity; use Drupal\Component\Plugin\PluginManagerInterface; -use Drupal\Core\Config\Schema\SchemaIncompleteException; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Language\Language; @@ -552,32 +551,6 @@ public function testToArrayIdKey() { $this->assertEquals(['configId' => $entity->id(), 'dependencies' => []], $properties); } - /** - * @covers ::toArray - */ - public function testToArraySchemaFallback() { - $this->typedConfigManager->expects($this->once()) - ->method('getDefinition') - ->will($this->returnValue(['mapping' => ['id' => '', 'dependencies' => '']])); - $this->entityType->expects($this->any()) - ->method('getPropertiesToExport') - ->willReturn([]); - $properties = $this->entity->toArray(); - $this->assertInternalType('array', $properties); - $this->assertEquals(['id' => $this->entity->id(), 'dependencies' => []], $properties); - } - - /** - * @covers ::toArray - */ - public function testToArrayFallback() { - $this->entityType->expects($this->any()) - ->method('getPropertiesToExport') - ->willReturn([]); - $this->setExpectedException(SchemaIncompleteException::class); - $this->entity->toArray(); - } - /** * @covers ::getThirdPartySetting * @covers ::setThirdPartySetting diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityTypeTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityTypeTest.php index c5d4c1c4ea..5c1e845393 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityTypeTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityTypeTest.php @@ -2,6 +2,9 @@ namespace Drupal\Tests\Core\Config\Entity; +use Drupal\Core\Config\Schema\SchemaIncompleteException; +use Drupal\Core\Config\TypedConfigManagerInterface; +use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Tests\UnitTestCase; use Drupal\Core\Config\Entity\ConfigEntityType; use Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException; @@ -12,6 +15,23 @@ */ class ConfigEntityTypeTest extends UnitTestCase { + /** + * The mocked typed config manager. + * + * @var \Drupal\Core\Config\TypedConfigManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $typedConfigManager; + + /** + * {@inheritdoc} + */ + protected function setUp() { + $this->typedConfigManager = $this->getMock(TypedConfigManagerInterface::class); + $container = new ContainerBuilder(); + $container->set('config.typed', $this->typedConfigManager); + \Drupal::setContainer($container); + } + /** * Sets up a ConfigEntityType object for a given set of values. * @@ -137,11 +157,6 @@ public function testGetPropertiesToExport($definition, $expected) { public function providerGetPropertiesToExport() { $data = []; - $data[] = [ - [], - NULL, - ]; - $data[] = [ [ 'config_export' => [ @@ -177,4 +192,28 @@ public function providerGetPropertiesToExport() { return $data; } + /** + * @covers ::getPropertiesToExport + */ + public function testGetPropertiesToExportSchemaFallback() { + $this->typedConfigManager->expects($this->once()) + ->method('getDefinition') + ->will($this->returnValue(['mapping' => ['id' => '', 'dependencies' => '']])); + $config_entity_type = new ConfigEntityType([ + 'id' => 'example_config_entity_type', + ]); + $this->assertEquals(['id' => 'id', 'dependencies' => 'dependencies'], $config_entity_type->getPropertiesToExport('test')); + } + + /** + * @covers ::getPropertiesToExport + */ + public function testGetPropertiesToExportException() { + $config_entity_type = new ConfigEntityType([ + 'id' => 'example_config_entity_type', + ]); + $this->setExpectedException(SchemaIncompleteException::class); + $config_entity_type->getPropertiesToExport(); + } + }