diff --git a/core/lib/Drupal/Core/Config/ConfigManager.php b/core/lib/Drupal/Core/Config/ConfigManager.php index e0d57e1..cb318d3 100644 --- a/core/lib/Drupal/Core/Config/ConfigManager.php +++ b/core/lib/Drupal/Core/Config/ConfigManager.php @@ -181,12 +181,36 @@ public function createSnapshot(StorageInterface $source_storage, StorageInterfac */ public function uninstall($type, $name) { // Remove all dependent configuration entities. - $dependent_entities = $this->findConfigEntityDependentsAsEntities($type, array($name)); + $extension_dependent_entities = $this->findConfigEntityDependentsAsEntities($type, array($name)); + // Give config entities a chance to become independent of the entities we + // are going to delete. + foreach (array_reverse($extension_dependent_entities) as $entity) { + $entity_dependencies = $entity->calculateDependencies(); + if (empty($entity_dependencies) || empty($entity_dependencies['entity'])) { + // No dependent entities nothing to do. + continue; + } + // Work out if any of the entity's dependencies are going to be affected + // by the uninstall. + $affected_entity_dependencies = array(); + foreach ($extension_dependent_entities as $entity) { + if (in_array($entity->id(), $entity_dependencies['entity'])) { + $affected_entity_dependencies[] = $entity; + } + } + if (!empty($affected_entity_dependencies)) { + $entity->preDeleteFixDependencies($affected_entity_dependencies); + } + } + + // Recalculate the dependencies, some config entities may have fixed their + // dependencies on the to-be-removed entities. + $extension_dependent_entities = $this->findConfigEntityDependentsAsEntities($type, array($name)); // Reverse the array to that entities are removed in the correct order of // dependence. For example, this ensures that field instances are removed // before fields. - foreach (array_reverse($dependent_entities) as $entity) { + foreach (array_reverse($extension_dependent_entities) as $entity) { $entity->setUninstalling(TRUE); $entity->delete(); } diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index 318ad7a..50b73e2 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -355,4 +355,10 @@ public function getConfigDependencyName() { return $this->getEntityType()->getConfigPrefix() . '.' . $this->id(); } + /** + * {@inheritdoc} + */ + public function preDeleteFixDependencies(array $dependent_entities) { + } + } diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php index b355cb5..1b82c44 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php @@ -20,6 +20,13 @@ /** * Enables the configuration entity. * + * @param \Drupal\Core\Config\Entity\ConfigEntityInterface[] + */ + public function preDeleteFixDependencies(array $dependent_entities); + + /** + * Enables the configuration entity. + * * @return $this */ public function enable(); diff --git a/core/modules/config/src/Tests/ConfigDependencyTest.php b/core/modules/config/src/Tests/ConfigDependencyTest.php index 0cb93e8..0dc3cbf 100644 --- a/core/modules/config/src/Tests/ConfigDependencyTest.php +++ b/core/modules/config/src/Tests/ConfigDependencyTest.php @@ -152,6 +152,58 @@ public function testDependencyMangement() { } /** + * Tests ConfigManager::uninstall() and config entity dependency management. + */ + public function testConfigEntityUninstall() { + /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */ + $config_manager = \Drupal::service('config.manager'); + /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */ + $storage = $this->container->get('entity.manager')->getStorage('config_test'); + // Test dependencies between modules. + $entity1 = $storage->create( + array( + 'id' => 'entity1', + 'test_dependencies' => array( + 'module' => array('node', 'config_test') + ), + ) + ); + $entity1->save(); + $entity2 = $storage->create( + array( + 'id' => 'entity2', + 'test_dependencies' => array( + 'entity' => array($entity1->getConfigDependencyName()), + ), + ) + ); + $entity2->save(); + // Test that doing a config uninstall of the node module deletes entity2 + // since it is dependent on entity1 which is dependent on the node module. + $config_manager->uninstall('module', 'node'); + $this->assertFalse($storage->load('entity2'), 'Entity 2 deleted'); + + $entity2 = $storage->create( + array( + 'id' => 'entity2', + 'test_dependencies' => array( + 'entity' => array($entity1->getConfigDependencyName()), + ), + ) + ); + $entity2->save(); + \Drupal::state()->set('config_test.fix_dependencies', array($entity1->getConfigDependencyName())); + // Test that doing a config uninstall of the node module does not delete + // entity2 since the state setting allows + // \Drupal\config_test\Entity::preDeleteFixDependencies() to remove the + // dependency before config entities are deleted during the uninstall. + $config_manager->uninstall('module', 'node'); + $entity2 = $storage->load('entity2'); + $this->assertTrue($entity2, 'Entity 2 not deleted'); + $this->assertEqual($entity2->calculateDependencies()['entity'], array(), 'Entity 2 dependencies updated to remove dependency on Entity1.'); + } + + /** * Gets a list of identifiers from an array of configuration entities. * * @param \Drupal\Core\Config\Entity\ConfigEntityInterface[] $dependents diff --git a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php index 07ec50a..d605e1f 100644 --- a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php +++ b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php @@ -150,4 +150,23 @@ public function calculateDependencies() { } } + /** + * {@inheritdoc} + */ + public function preDeleteFixDependencies(array $dependent_entities) { + $changed = FALSE; + $fix_deps = \Drupal::state()->get('config_test.fix_dependencies', array()); + foreach ($dependent_entities as $entity) { + if (in_array($entity->id(), $fix_deps)) { + $key = array_search($entity->id(), $this->test_dependencies['entity']); + if ($key !== FALSE) { + unset($this->test_dependencies['entity'][$key]); + } + } + } + if ($changed) { + $this->save(); + } + } + } diff --git a/core/modules/entity/src/EntityDisplayBase.php b/core/modules/entity/src/EntityDisplayBase.php index 34dc815..d2933e8 100644 --- a/core/modules/entity/src/EntityDisplayBase.php +++ b/core/modules/entity/src/EntityDisplayBase.php @@ -389,4 +389,20 @@ private function fieldHasDisplayOptions(FieldDefinitionInterface $definition) { return $definition->getDisplayOptions($this->displayContext); } + /** + * {@inheritdoc} + */ + public function preDeleteFixDependencies(array $dependent_entities) { + $changed = FALSE; + foreach ($dependent_entities as $entity) { + if ($entity instanceof FieldInstanceConfig) { + $this->removeComponent($entity->getName()); + $changed = TRUE; + } + } + if ($changed) { + $this->save(); + } + } + } diff --git a/core/modules/system/src/Form/ModulesUninstallConfirmForm.php b/core/modules/system/src/Form/ModulesUninstallConfirmForm.php index dc30db9..cb08695 100644 --- a/core/modules/system/src/Form/ModulesUninstallConfirmForm.php +++ b/core/modules/system/src/Form/ModulesUninstallConfirmForm.php @@ -146,8 +146,8 @@ public function buildForm(array $form, array &$form_state) { $form['entities'] = array( '#type' => 'details', - '#title' => $this->t('Configuration deletions'), - '#description' => $this->t('The listed configuration will be deleted.'), + '#title' => $this->t('Affected configuration'), + '#description' => $this->t('The listed configuration will be updated, if possible, or deleted.'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#access' => FALSE, diff --git a/core/modules/system/src/Tests/Module/UninstallTest.php b/core/modules/system/src/Tests/Module/UninstallTest.php index bbb4399..9fdd985 100644 --- a/core/modules/system/src/Tests/Module/UninstallTest.php +++ b/core/modules/system/src/Tests/Module/UninstallTest.php @@ -55,7 +55,7 @@ function testUninstallPage() { $edit = array(); $edit['uninstall[module_test]'] = TRUE; $this->drupalPostForm('admin/modules/uninstall', $edit, t('Uninstall')); - $this->assertNoText(\Drupal::translation()->translate('Configuration deletions'), 'No configuration deletions listed on the module install confirmation page.'); + $this->assertNoText(\Drupal::translation()->translate('Affected configuration'), 'No configuration deletions listed on the module install confirmation page.'); $this->drupalPostForm(NULL, NULL, t('Uninstall')); $this->assertText(t('The selected modules have been uninstalled.'), 'Modules status has been updated.'); @@ -65,7 +65,7 @@ function testUninstallPage() { $edit = array(); $edit['uninstall[node]'] = TRUE; $this->drupalPostForm('admin/modules/uninstall', $edit, t('Uninstall')); - $this->assertText(\Drupal::translation()->translate('Configuration deletions'), 'Configuration deletions listed on the module install confirmation page.'); + $this->assertText(\Drupal::translation()->translate('Affected configuration'), 'Configuration deletions listed on the module install confirmation page.'); $entity_types = array(); foreach ($node_dependencies as $entity) { diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index 52d99b2..81941e9 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -1237,6 +1237,12 @@ public function getConfigDependencyName() { /** * {@inheritdoc} */ + public function preDeleteFixDependencies(array $dependent_entities) { + } + + /** + * {@inheritdoc} + */ public function getCacheTag() { $this->storage->getCacheTag(); }