diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index 12a4543..e357a54 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -10,6 +10,7 @@ use Drupal\Component\Plugin\ConfigurablePluginInterface; use Drupal\Component\Utility\String; use Drupal\Core\Cache\Cache; +use Drupal\Core\Config\ConfigException; use Drupal\Core\Config\Schema\SchemaIncompleteException; use Drupal\Core\Entity\Entity; use Drupal\Core\Config\ConfigDuplicateUUIDException; @@ -430,6 +431,7 @@ public function getConfigTarget() { * {@inheritdoc} */ public function onDependencyRemoval(array $dependencies) { + return FALSE; } /** @@ -452,4 +454,41 @@ protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_typ Cache::invalidateTags($entity_type->getListCacheTags()); } + public static function preDelete(EntityStorageInterface $storage, array $entities) { + foreach ($entities as $entity) { + if ($entity->isUninstalling() || $entity->isSyncing()) { + // During extension uninstall and configuration synchronisation + // deletions are already managed. + break; + } + $dependents = \Drupal::service('config.manager')->findConfigEntityDependentsAsEntities('config', [$entity->getConfigDependencyName()]); + if (!empty($dependents)) { + // Maybe the dependents can fix themselves. + foreach ($dependents as $dependent) { + // Build up the expected array to pass to onDependencyRemoval. + $dependencies_for_removal = [ + 'config' => [$entity], + 'content' => [], + 'module' => [], + 'theme' => [] + ]; + $dependent->onDependencyRemoval($dependencies_for_removal); + } + // Delete everything can't be fixed. Recalculate the dependencies since + // fixing dependencies can change the dependency chain. + $dependents = \Drupal::service('config.manager')->findConfigEntityDependentsAsEntities('config', [$entity->getConfigDependencyName()]); + foreach ($dependents as $dependent) { + // Ensure that the dependent still exists. + $storage = \Drupal::entityManager()->getStorage($dependent->getEntityTypeId()); + $dependent = $storage->load($dependent->id()); + if ($dependent) { + $dependent->delete(); + } + } + } + } + parent::preDelete($storage, $entities); + } + + } diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php index 15347e3..5b813e1 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php @@ -162,15 +162,15 @@ public function calculateDependencies(); * widgets and formatters if the plugin that supplies them depends on a * module that is being uninstalled. * - * @todo https://www.drupal.org/node/2336727 this method is only fired during - * extension uninstallation but it could be used during config entity - * deletion too. - * * @param array $dependencies * An array of dependencies that will be deleted keyed by dependency type. * Dependency types are, for example, entity, module and theme. * + * @return bool + * TRUE if the entity has been changed as a result, FALSE if not. + * * @see \Drupal\Core\Config\Entity\ConfigDependencyManager + * @see \Drupal\Core\Config\ConfigEntityBase::preDelete() * @see \Drupal\Core\Config\ConfigManager::uninstall() * @see \Drupal\Core\Entity\EntityDisplayBase::onDependencyRemoval() */ diff --git a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php index aba35e2..9bafe89 100644 --- a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php +++ b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php @@ -410,6 +410,7 @@ public function onDependencyRemoval(array $dependencies) { if ($changed) { $this->save(); } + return $changed; } /** diff --git a/core/lib/Drupal/Core/Field/FieldConfigBase.php b/core/lib/Drupal/Core/Field/FieldConfigBase.php index af1c399..0b791fc 100644 --- a/core/lib/Drupal/Core/Field/FieldConfigBase.php +++ b/core/lib/Drupal/Core/Field/FieldConfigBase.php @@ -267,6 +267,20 @@ public function calculateDependencies() { /** * {@inheritdoc} */ + public function onDependencyRemoval(array $dependencies) { + $field_type_manager = \Drupal::service('plugin.manager.field.field_type'); + $definition = $field_type_manager->getDefinition($this->getType()); + $changed = $definition['class']::onDependencyRemoval($this, $dependencies); + if ($changed) { + $this->save(); + } + return $changed; + } + + + /** + * {@inheritdoc} + */ public function postCreate(EntityStorageInterface $storage) { parent::postCreate($storage); // If it was not present in the $values passed to create(), (e.g. for diff --git a/core/lib/Drupal/Core/Field/FieldItemBase.php b/core/lib/Drupal/Core/Field/FieldItemBase.php index b411b88..b2119e3 100644 --- a/core/lib/Drupal/Core/Field/FieldItemBase.php +++ b/core/lib/Drupal/Core/Field/FieldItemBase.php @@ -273,4 +273,11 @@ public static function calculateDependencies(FieldDefinitionInterface $field_def return array(); } + /** + * {@inheritdoc} + */ + public static function onDependencyRemoval(FieldDefinitionInterface $field_definition, array $dependencies) { + return FALSE; + } + } diff --git a/core/lib/Drupal/Core/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Field/FieldItemInterface.php index 1d0de29..f74b3f7 100644 --- a/core/lib/Drupal/Core/Field/FieldItemInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php @@ -405,4 +405,20 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state); */ public static function calculateDependencies(FieldDefinitionInterface $field_definition); + /** + * Informs the plugin that a dependency of the field will be deleted. + * + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition. + * @param array $dependencies + * An array of dependencies that will be deleted keyed by dependency type. + * Dependency types are, for example, entity, module and theme. + * + * @return bool + * TRUE if the field definition has been changed as a result, FALSE if not. + * + * @see \Drupal\Core\Config\ConfigEntityInterface::onDependencyRemoval() + */ + public static function onDependencyRemoval(FieldDefinitionInterface $field_definition, array $dependencies); + } diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php index e03750d..e45aae7 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php @@ -286,4 +286,23 @@ public static function calculateDependencies(FieldDefinitionInterface $field_def return $dependencies; } + public static function onDependencyRemoval(FieldDefinitionInterface $field_definition, array $dependencies) { + $changed = FALSE; + if (is_array($field_definition->default_value) && count($field_definition->default_value)) { + $target_entity_type = \Drupal::entityManager()->getDefinition($field_definition->getFieldStorageDefinition()->getSetting('target_type')); + foreach ($field_definition->default_value as $default_value) { + if (is_array($default_value) && isset($default_value['target_uuid'])) { + $entity = \Drupal::entityManager()->loadEntityByUuid($target_entity_type->id(), $default_value['target_uuid']); + // If the entity does not exist do not create the dependency. + // @see \Drupal\Core\Field\EntityReferenceFieldItemList::processDefaultValue() + if ($entity && in_array($entity, $dependencies[$target_entity_type->getConfigDependencyKey()])) { + $field_definition->default_value = []; + $changed = TRUE; + } + } + } + } + return $changed; + } + } diff --git a/core/modules/config/src/Tests/ConfigImporterTest.php b/core/modules/config/src/Tests/ConfigImporterTest.php index d2c7cf0..bfef3a8 100644 --- a/core/modules/config/src/Tests/ConfigImporterTest.php +++ b/core/modules/config/src/Tests/ConfigImporterTest.php @@ -381,6 +381,10 @@ function testSecondaryUpdateDeletedDeleterFirst() { /** * Tests that secondary updates for deleted files work as expected. + * + * This test is completely hypothetical since we only support full + * configuration tree imports. Therefore, any configuration updates that cause + * secondary deletes should be reflected already in the staged configuration. */ function testSecondaryUpdateDeletedDeleteeFirst() { $name_deleter = 'config_test.dynamic.deleter'; @@ -416,10 +420,10 @@ function testSecondaryUpdateDeletedDeleteeFirst() { $this->configImporter->reset()->import(); $entity_storage = \Drupal::entityManager()->getStorage('config_test'); - $deleter = $entity_storage->load('deleter'); - $this->assertEqual($deleter->id(), 'deleter'); - $this->assertEqual($deleter->uuid(), $values_deleter['uuid']); - $this->assertEqual($deleter->label(), $values_deleter['label']); + // Both entities are deleted. ConfigTest::postSave() causes updates of the + // deleter entity to delete the deletee entity. Since the deleter depends on + // the deletee, removing the deletee causes the deleter to be removed. + $this->assertFalse($entity_storage->load('deleter')); // @todo The deletee entity does not exist as the update worked but the // entity was deleted after that. There is also no log message as this // happened outside of the config importer. 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 4357597..ff024a5 100644 --- a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php +++ b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php @@ -136,6 +136,7 @@ public function onDependencyRemoval(array $dependencies) { if ($changed) { $this->save(); } + return $changed; } /** diff --git a/core/modules/entity_reference/src/Tests/EntityReferenceIntegrationTest.php b/core/modules/entity_reference/src/Tests/EntityReferenceIntegrationTest.php index 6c5f8fc..90cc352 100644 --- a/core/modules/entity_reference/src/Tests/EntityReferenceIntegrationTest.php +++ b/core/modules/entity_reference/src/Tests/EntityReferenceIntegrationTest.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\String; use Drupal\config\Tests\AssertConfigEntityImportTrait; +use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\field\Entity\FieldConfig; use Drupal\simpletest\WebTestBase; @@ -152,6 +153,9 @@ public function testSupportedEntityTypesAndWidgets() { // Ensure that the field can be imported without change even after the // default value deleted. $referenced_entities[0]->delete(); + // Reload the field since deleting the default value can change the field. + \Drupal::entityManager()->getStorage($field->getEntityTypeId())->resetCache([$field->id()]); + $field = FieldConfig::loadByName($this->entityType, $this->bundle, $this->fieldName); $this->assertConfigEntityImport($field); // Once the default value has been removed after saving the dependency diff --git a/core/modules/filter/src/Entity/FilterFormat.php b/core/modules/filter/src/Entity/FilterFormat.php index dc4643a..036c730 100644 --- a/core/modules/filter/src/Entity/FilterFormat.php +++ b/core/modules/filter/src/Entity/FilterFormat.php @@ -413,6 +413,7 @@ public function onDependencyRemoval(array $dependencies) { if ($changed) { $this->save(); } + return $changed; } /** diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php index 4ed1c20..9ef8466 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php @@ -97,6 +97,13 @@ class ConfigEntityStorageTest extends UnitTestCase { protected $typedConfigManager; /** + * The configuration manager. + * + * @var \Drupal\Core\Config\ConfigManagerInterface + */ + protected $configManager; + + /** * {@inheritdoc} * * @covers ::__construct @@ -160,10 +167,14 @@ protected function setUp() { $this->typedConfigManager->expects($this->any()) ->method('getDefinition') ->will($this->returnValue(array('mapping' => array('id' => '', 'uuid' => '', 'dependencies' => '')))); + + $this->configManager = $this->getMock('Drupal\Core\Config\ConfigManagerInterface'); + $container = new ContainerBuilder(); $container->set('entity.manager', $this->entityManager); $container->set('config.typed', $this->typedConfigManager); $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator); + $container->set('config.manager', $this->configManager); \Drupal::setContainer($container); }