diff --git a/core/lib/Drupal/Core/Config/ConfigUUIDException.php b/core/lib/Drupal/Core/Config/ConfigUUIDException.php new file mode 100644 index 0000000..cc81a95 --- /dev/null +++ b/core/lib/Drupal/Core/Config/ConfigUUIDException.php @@ -0,0 +1,15 @@ +idKey = $this->entityInfo['entity_keys']['id']; @@ -87,6 +98,7 @@ public function __construct($entity_type, array $entity_info, ConfigFactory $con $this->configFactory = $config_factory; $this->configStorage = $config_storage; + $this->entityQueryFactory = $entity_query_factory; } /** @@ -97,7 +109,8 @@ public static function createInstance(ContainerInterface $container, $entity_typ $entity_type, $entity_info, $container->get('config.factory'), - $container->get('config.storage') + $container->get('config.storage'), + $container->get('entity.query') ); } @@ -403,6 +416,23 @@ public function save(EntityInterface $entity) { * Used before the entity is saved and before invoking the presave hook. */ protected function preSave(EntityInterface $entity) { + // Ensure this entity's UUID does not exist with a different ID, regardless + // of whether it's new or updated. + $matching_entities = $this->entityQueryFactory->get($this->entityType) + ->condition('uuid', $entity->uuid()) + ->execute(); + $matched_entity = reset($matching_entities); + if (!empty($matched_entity) && ($matched_entity != $entity->id())) { + throw new ConfigUUIDException(sprintf('Attempt to save a configuration object %s with UUID %s when this UUID is already used for %s', $entity->id(), $entity->uuid(), $matched_entity)); + } + + if (!$entity->isNew()) { + $original = $this->loadUnchanged($entity->id()); + // Ensure that the UUID cannot be changed for an existing entity. + if ($original && ($original->uuid() != $entity->uuid())) { + throw new ConfigUUIDException(sprintf('Attempt to save a configuration entity %s with UUID %s when this entity already exists with UUID %s', $entity->id(), $entity->uuid(), $original->uuid())); + } + } } /** diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityStorageControllerTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityStorageControllerTest.php new file mode 100644 index 0000000..efb9a14 --- /dev/null +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityStorageControllerTest.php @@ -0,0 +1,64 @@ + 'Configuration entity UUID conflict', + 'description' => 'Tests staging and importing config entities with IDs and UUIDs that match existing config.', + 'group' => 'Configuration', + ); + } + + /** + * Tests importing fields and instances with changed IDs or UUIDs. + */ + public function testUUIDConflict() { + $entity_type = 'config_test'; + $id = 'test_1'; + // Load the original field and instance entities. + entity_create($entity_type, array('id' => $id))->save(); + $entity = entity_load($entity_type, $id); + + $original_properties = $entity->getExportProperties(); + + // Override with a new UUID and try to save. + $uuid = new Uuid(); + $new_uuid = $uuid->generate(); + $entity->set('uuid', $new_uuid); + + try { + $entity->save(); + $this->fail('Exception thrown when attempting to save a configuration entity with a UUID that does not match the existing UUID.'); + } + catch (ConfigUUIDException $e) { + $this->pass(format_string('Exception thrown when attempting to save a configuration entity with a UUID that does not match existing data: %e.', array('%e' => $e))); + } + + // Ensure that the config entity was not corrupted. + $entity = entity_load('config_test', $entity->id(), TRUE); + $this->assertIdentical($entity->getExportProperties(), $original_properties); + } + +} diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php index 05f3d74..bffcb3b 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php @@ -154,6 +154,8 @@ function testCRUD() { $this->assertIdentical($same_id->label(), ''); $this->assertNotEqual($same_id->uuid(), $config_test->uuid()); + // Delete the overridden entity first. + $same_id->delete(); // Revert to previous state. $config_test->save(); diff --git a/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php b/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php index cb412ef..9535bee 100644 --- a/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php +++ b/core/modules/field/lib/Drupal/field/FieldInstanceStorageController.php @@ -9,6 +9,7 @@ use Drupal\Core\Config\Config; use Drupal\Core\Config\Entity\ConfigStorageController; +use Drupal\Core\Entity\Query\QueryFactory; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Config\ConfigFactory; use Drupal\Core\Config\StorageInterface; @@ -65,8 +66,8 @@ class FieldInstanceStorageController extends ConfigStorageController { * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $state * The state key value store. */ - public function __construct($entity_type, array $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, EntityManager $entity_manager, ModuleHandler $module_handler, KeyValueStoreInterface $state) { - parent::__construct($entity_type, $entity_info, $config_factory, $config_storage); + public function __construct($entity_type, array $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory, EntityManager $entity_manager, ModuleHandler $module_handler, KeyValueStoreInterface $state) { + parent::__construct($entity_type, $entity_info, $config_factory, $config_storage, $entity_query_factory); $this->entityManager = $entity_manager; $this->moduleHandler = $module_handler; $this->state = $state; @@ -81,6 +82,7 @@ public static function createInstance(ContainerInterface $container, $entity_typ $entity_info, $container->get('config.factory'), $container->get('config.storage'), + $container->get('entity.query'), $container->get('plugin.manager.entity'), $container->get('module_handler'), $container->get('state') diff --git a/core/modules/field/lib/Drupal/field/FieldStorageController.php b/core/modules/field/lib/Drupal/field/FieldStorageController.php index 4eb2c89..07c6c71 100644 --- a/core/modules/field/lib/Drupal/field/FieldStorageController.php +++ b/core/modules/field/lib/Drupal/field/FieldStorageController.php @@ -9,6 +9,7 @@ use Drupal\Core\Config\Config; use Drupal\Core\Config\Entity\ConfigStorageController; +use Drupal\Core\Entity\Query\QueryFactory; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Config\ConfigFactory; use Drupal\Core\Config\StorageInterface; @@ -60,8 +61,9 @@ class FieldStorageController extends ConfigStorageController { * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $state * The state key value store. */ - public function __construct($entity_type, array $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, EntityManager $entity_manager, ModuleHandler $module_handler, KeyValueStoreInterface $state) { - parent::__construct($entity_type, $entity_info, $config_factory, $config_storage); + public function __construct($entity_type, array $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory, EntityManager $entity_manager, ModuleHandler $module_handler, KeyValueStoreInterface $state) { + parent::__construct($entity_type, $entity_info, $config_factory, $config_storage, $entity_query_factory); + $this->entityManager = $entity_manager; $this->moduleHandler = $module_handler; $this->state = $state; @@ -76,6 +78,7 @@ public static function createInstance(ContainerInterface $container, $entity_typ $entity_info, $container->get('config.factory'), $container->get('config.storage'), + $container->get('entity.query'), $container->get('plugin.manager.entity'), $container->get('module_handler'), $container->get('state') diff --git a/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php b/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php index 60f020e..93cceab 100644 --- a/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php +++ b/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php @@ -55,6 +55,13 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface { public $label; /** + * The UUID for this entity. + * + * @var string + */ + public $uuid; + + /** * The array of image effects for this image style. * * @var string diff --git a/core/modules/views/lib/Drupal/views/Tests/ViewStorageTest.php b/core/modules/views/lib/Drupal/views/Tests/ViewStorageTest.php index 6a86adf..97144aa 100644 --- a/core/modules/views/lib/Drupal/views/Tests/ViewStorageTest.php +++ b/core/modules/views/lib/Drupal/views/Tests/ViewStorageTest.php @@ -179,6 +179,8 @@ protected function displayTests() { $executable->initDisplay(); $this->assertTrue($executable->displayHandlers->get($new_id) instanceof Page, 'New page display "test" uses the right display plugin.'); + // To save this with a new ID, we should use createDuplicate(). + $view = $view->createDuplicate(); $view->set('id', 'test_view_storage_new_new2'); $view->save(); $values = config('views.view.test_view_storage_new_new2')->get(); diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewCloneFormController.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewCloneFormController.php index 5e7666c..687b233 100644 --- a/core/modules/views_ui/lib/Drupal/views_ui/ViewCloneFormController.php +++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewCloneFormController.php @@ -74,8 +74,9 @@ protected function actions(array $form, array &$form_state) { * Overrides \Drupal\Core\Entity\EntityFormController::form(). */ public function submit(array $form, array &$form_state) { - $this->entity = parent::submit($form, $form_state); - $this->entity->setOriginalID(NULL); + $original = parent::submit($form, $form_state); + $this->entity = $original->createDuplicate(); + $this->entity->set('id', $form_state['values']['id']); $this->entity->save(); // Redirect the user to the view admin form.