diff --git a/core/core.services.yml b/core/core.services.yml index 328acd8..9291712 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -322,6 +322,11 @@ services: entity.form_builder: class: Drupal\Core\Entity\EntityFormBuilder arguments: ['@entity.manager', '@form_builder'] + entity.bundle_config_import_validator: + class: Drupal\Core\Entity\Event\BundleConfigImportValidate + arguments: ['@config.manager', '@entity.manager'] + tags: + - { name: event_subscriber } plugin.manager.block: class: Drupal\Core\Block\BlockManager parent: default_plugin_manager diff --git a/core/lib/Drupal/Core/Config/ConfigImporterEvent.php b/core/lib/Drupal/Core/Config/ConfigImporterEvent.php index caef42e..8286813 100644 --- a/core/lib/Drupal/Core/Config/ConfigImporterEvent.php +++ b/core/lib/Drupal/Core/Config/ConfigImporterEvent.php @@ -37,4 +37,23 @@ public function getConfigImporter() { return $this->configImporter; } + /** + * Gets the list of changes that will be imported. + * + * @param string $op + * (optional) A change operation. Either delete, create or update. If + * supplied the returned list will be limited to this operation. + * @param string $collection + * (optional) The collection to get the changelist for. Defaults to the + * default collection. + * + * @return array + * An array of config changes that are yet to be imported. + * + * @see \Drupal\Core\Config\StorageComparerInterface::getChangelist() + */ + public function getChangelist($op = NULL, $collection = StorageInterface::DEFAULT_COLLECTION) { + return $this->configImporter->getStorageComparer()->getChangelist($op, $collection); + } + } diff --git a/core/lib/Drupal/Core/Entity/Event/BundleConfigImportValidate.php b/core/lib/Drupal/Core/Entity/Event/BundleConfigImportValidate.php new file mode 100644 index 0000000..58ca1a2 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Event/BundleConfigImportValidate.php @@ -0,0 +1,79 @@ +configManager = $config_manager; + $this->entityManager = $entity_manager; + } + + /** + * Ensures bundles that will be deleted are not in use. + * + * @param \Drupal\Core\Config\ConfigImporterEvent $event + * The config import event. + */ + public function onConfigImporterValidate(ConfigImporterEvent $event) { + foreach ($event->getChangelist('delete') as $config_name) { + // Get the config entity type ID. This also ensure we are dealing with a + // configuration entity. + if ($entity_type_id = $this->configManager->getEntityTypeIdByName($config_name)) { + $entity_type = $this->entityManager->getDefinition($entity_type_id); + // Does this entity type define a bundle of another entity type. + if ($bundle_of = $entity_type->getBundleOf()) { + // Work out if there are entities with this bundle. + $bundle_of_entity_type = $this->entityManager->getDefinition($bundle_of); + $bundle_id = ConfigEntityStorage::getIDFromConfigName($config_name, $entity_type->getConfigPrefix()); + $entity_query = $this->entityManager->getStorage($bundle_of)->getQuery(); + $entity_ids = $entity_query->condition($bundle_of_entity_type->getKey('bundle'), $bundle_id) + ->accessCheck(FALSE) + ->range(0, 1) + ->execute(); + if (!empty($entity_ids)) { + $entity = $this->entityManager->getStorage($entity_type_id)->load($bundle_id); + $event->getConfigImporter()->logError($this->t('Entities exist of type %entity_type and %bundle_label %bundle. These entities need to be deleted before importing.', array('%entity_type' => $bundle_of_entity_type->getLabel(), '%bundle_label' => $bundle_of_entity_type->getBundleLabel(), '%bundle' => $entity->label()))); + } + } + } + } + } + +} diff --git a/core/modules/config/src/Tests/ConfigImportUITest.php b/core/modules/config/src/Tests/ConfigImportUITest.php index 8da2792..cf334af 100644 --- a/core/modules/config/src/Tests/ConfigImportUITest.php +++ b/core/modules/config/src/Tests/ConfigImportUITest.php @@ -393,4 +393,48 @@ function testImportErrorLog() { $this->assertText(t('There are no configuration changes to import.')); } + /** + * Tests the config importer cannot delete bundles with existing entities. + * + * @see \Drupal\Core\Entity\Event\BundleConfigImportValidate + */ + public function testEntityBundleDelete() { + \Drupal::service('module_installer')->install(array('node')); + $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging')); + + $node_type = $this->drupalCreateContentType(); + $node = $this->drupalCreateNode(array('type' => $node_type->id())); + $this->drupalGet('admin/config/development/configuration'); + // The node type, body field and entity displays will be scheduled for + // removal. + $this->assertText(format_string('node.type.@type', array('@type' => $node_type->id()))); + $this->assertText(format_string('field.field.node.@type.body', array('@type' => $node_type->id()))); + $this->assertText(format_string('core.entity_view_display.node.@type.teaser', array('@type' => $node_type->id()))); + $this->assertText(format_string('core.entity_view_display.node.@type.default', array('@type' => $node_type->id()))); + $this->assertText(format_string('core.entity_form_display.node.@type.default', array('@type' => $node_type->id()))); + + // Attempt to import configuration and verify that an error message appears + // and the node type, body field and entity displays are still scheduled for + // removal. + $this->drupalPostForm(NULL, array(), t('Import all')); + $validation_message = t('Entities exist of type %entity_type and %bundle_label %bundle. These entities need to be deleted before importing.', array('%entity_type' => $node->getEntityType()->getLabel(), '%bundle_label' => $node->getEntityType()->getBundleLabel(), '%bundle' => $node_type->label())); + $this->assertRaw($validation_message); + $this->assertText(format_string('node.type.@type', array('@type' => $node_type->id()))); + $this->assertText(format_string('field.field.node.@type.body', array('@type' => $node_type->id()))); + $this->assertText(format_string('core.entity_view_display.node.@type.teaser', array('@type' => $node_type->id()))); + $this->assertText(format_string('core.entity_view_display.node.@type.default', array('@type' => $node_type->id()))); + $this->assertText(format_string('core.entity_form_display.node.@type.default', array('@type' => $node_type->id()))); + + // Delete the node and try to import again. + $node->delete(); + $this->drupalPostForm(NULL, array(), t('Import all')); + $this->assertNoRaw($validation_message); + $this->assertText(t('There are no configuration changes to import.')); + $this->assertNoText(format_string('node.type.@type', array('@type' => $node_type->id()))); + $this->assertNoText(format_string('field.field.node.@type.body', array('@type' => $node_type->id()))); + $this->assertNoText(format_string('core.entity_view_display.node.@type.teaser', array('@type' => $node_type->id()))); + $this->assertNoText(format_string('core.entity_view_display.node.@type.default', array('@type' => $node_type->id()))); + $this->assertNoText(format_string('core.entity_form_display.node.@type.default', array('@type' => $node_type->id()))); + } + }