diff --git a/core/lib/Drupal/Core/Field/FieldModuleUninstallValidator.php b/core/lib/Drupal/Core/Field/FieldModuleUninstallValidator.php index 08a27cb76f..b9ec55680e 100644 --- a/core/lib/Drupal/Core/Field/FieldModuleUninstallValidator.php +++ b/core/lib/Drupal/Core/Field/FieldModuleUninstallValidator.php @@ -46,6 +46,7 @@ public function validate($module_name) { // See \Drupal\Core\Entity\ContentUninstallValidator. if ($entity_type->getProvider() != $module_name && $entity_type->entityClassImplements(FieldableEntityInterface::class)) { foreach ($this->entityManager->getFieldStorageDefinitions($entity_type_id) as $storage_definition) { + if ($storage_definition->getProvider() == $module_name) { $storage = $this->entityManager->getStorage($entity_type_id); if ($storage instanceof FieldableEntityStorageInterface && $storage->countFieldData($storage_definition, TRUE)) { diff --git a/core/modules/field/src/Entity/FieldConfig.php b/core/modules/field/src/Entity/FieldConfig.php index af2fcca738..e9e8cbdc89 100644 --- a/core/modules/field/src/Entity/FieldConfig.php +++ b/core/modules/field/src/Entity/FieldConfig.php @@ -3,6 +3,7 @@ namespace Drupal\field\Entity; use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\FieldableEntityStorageInterface; use Drupal\Core\Field\FieldConfigBase; use Drupal\Core\Field\FieldException; use Drupal\field\FieldStorageConfigInterface; @@ -194,8 +195,13 @@ public static function preDelete(EntityStorageInterface $storage, array $fields) // Keep the field definitions in the state storage so we can use them // later during field_purge_batch(). $deleted_fields = $state->get('field.field.deleted') ?: []; + + /** @var \Drupal\field\FieldConfigInterface $field */ foreach ($fields as $field) { - if (!$field->deleted) { + // Only mark a field for purging if there is data. Otherwise, just remove + // it. + $target_entity_storage = \Drupal::entityTypeManager()->getStorage($field->getTargetEntityTypeId()); + if (!$field->deleted && $target_entity_storage instanceof FieldableEntityStorageInterface && $target_entity_storage->countFieldData($field->getFieldStorageDefinition(), TRUE)) { $config = $field->toArray(); $config['deleted'] = TRUE; $config['field_storage_uuid'] = $field->getFieldStorageDefinition()->uuid(); diff --git a/core/modules/field/src/Entity/FieldStorageConfig.php b/core/modules/field/src/Entity/FieldStorageConfig.php index 78b3d602b6..bf2641ed4e 100644 --- a/core/modules/field/src/Entity/FieldStorageConfig.php +++ b/core/modules/field/src/Entity/FieldStorageConfig.php @@ -6,6 +6,7 @@ use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Entity\FieldableEntityStorageInterface; use Drupal\Core\Field\FieldException; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\TypedData\OptionsProviderInterface; @@ -409,8 +410,12 @@ public static function preDelete(EntityStorageInterface $storage, array $field_s // Keep the field definitions in the state storage so we can use them later // during field_purge_batch(). $deleted_storages = $state->get('field.storage.deleted') ?: []; + /** @var \Drupal\field\FieldStorageConfigInterface $field_storage */ foreach ($field_storages as $field_storage) { - if (!$field_storage->deleted) { + // Only mark a field for purging if there is data. Otherwise, just remove + // it. + $target_entity_storage = \Drupal::entityTypeManager()->getStorage($field_storage->getTargetEntityTypeId()); + if (!$field_storage->deleted && $target_entity_storage instanceof FieldableEntityStorageInterface && $target_entity_storage->countFieldData($field_storage, TRUE)) { $config = $field_storage->toArray(); $config['deleted'] = TRUE; $config['bundles'] = $field_storage->getBundles(); diff --git a/core/modules/field/tests/src/Kernel/FieldCrudTest.php b/core/modules/field/tests/src/Kernel/FieldCrudTest.php index 87441dc407..bae8c5fa67 100644 --- a/core/modules/field/tests/src/Kernel/FieldCrudTest.php +++ b/core/modules/field/tests/src/Kernel/FieldCrudTest.php @@ -207,12 +207,11 @@ public function testUpdateField() { } /** - * Test the deletion of a field. + * Test the deletion of a field with no data. */ - public function testDeleteField() { - // TODO: Test deletion of the data stored in the field also. - // Need to check that data for a 'deleted' field / storage doesn't get loaded - // Need to check data marked deleted is cleaned on cron (not implemented yet...) + public function testDeleteFieldNoData() { + // Deleting and purging fields with data is tested in + // \Drupal\Tests\field\Kernel\BulkDeleteTest. // Create two fields for the same field storage so we can test that only one // is deleted. @@ -227,17 +226,18 @@ public function testDeleteField() { $this->assertTrue(!empty($field) && empty($field->deleted), 'A new field is not marked for deletion.'); $field->delete(); - // Make sure the field is marked as deleted when it is specifically loaded. - $field = current(entity_load_multiple_by_properties('field_config', ['entity_type' => 'entity_test', 'field_name' => $this->fieldDefinition['field_name'], 'bundle' => $this->fieldDefinition['bundle'], 'include_deleted' => TRUE])); - $this->assertTrue($field->isDeleted(), 'A deleted field is marked for deletion.'); + // Make sure the field was deleted without being marked for purging as there + // was no data. + $fields = entity_load_multiple_by_properties('field_config', ['entity_type' => 'entity_test', 'field_name' => $this->fieldDefinition['field_name'], 'bundle' => $this->fieldDefinition['bundle'], 'include_deleted' => TRUE]); + $this->assertEquals(0, count($fields), 'A deleted field is marked for deletion.'); // Try to load the field normally and make sure it does not show up. $field = FieldConfig::load('entity_test.' . '.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']); - $this->assertTrue(empty($field), 'A deleted field is not loaded by default.'); + $this->assertTrue(empty($field), 'Field was deleted'); // Make sure the other field is not deleted. $another_field = FieldConfig::load('entity_test.' . $another_field_definition['bundle'] . '.' . $another_field_definition['field_name']); - $this->assertTrue(!empty($another_field) && empty($another_field->deleted), 'A non-deleted field is not marked for deletion.'); + $this->assertTrue(!empty($another_field) && !$another_field->isDeleted(), 'A non-deleted field is not marked for deletion.'); } /** diff --git a/core/modules/field/tests/src/Kernel/FieldImportDeleteTest.php b/core/modules/field/tests/src/Kernel/FieldImportDeleteTest.php index 0eef137165..f3408a52a0 100644 --- a/core/modules/field/tests/src/Kernel/FieldImportDeleteTest.php +++ b/core/modules/field/tests/src/Kernel/FieldImportDeleteTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\field\Kernel; use Drupal\Component\Utility\SafeMarkup; +use Drupal\entity_test\Entity\EntityTest; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; @@ -50,6 +51,14 @@ public function testImportDelete() { $field_config_name_2a = "field.field.$field_id_2a"; $field_config_name_2b = "field.field.$field_id_2b"; + // Create an entity with data in the first field to make sure that field + // needs to be purged. + $entity_test = EntityTest::create([ + 'type' => 'entity_test', + ]); + $entity_test->set($field_name, 'test data'); + $entity_test->save(); + // Create a second bundle for the 'Entity test' entity type. entity_test_create_bundle('test_bundle'); @@ -97,10 +106,10 @@ public function testImportDelete() { $this->assertIdentical($active->listAll($field_config_name_2a), []); $this->assertIdentical($active->listAll($field_config_name_2b), []); - // Check that the storage definition is preserved in state. + // Check that only the first storage definition is preserved in state. $deleted_storages = \Drupal::state()->get('field.storage.deleted') ?: []; $this->assertTrue(isset($deleted_storages[$field_storage_uuid])); - $this->assertTrue(isset($deleted_storages[$field_storage_uuid_2])); + $this->assertFalse(isset($deleted_storages[$field_storage_uuid_2])); // Purge field data, and check that the storage definition has been // completely removed once the data is purged. diff --git a/core/modules/field/tests/src/Kernel/FieldStorageCrudTest.php b/core/modules/field/tests/src/Kernel/FieldStorageCrudTest.php index f7993dcc7e..52eb4e4b72 100644 --- a/core/modules/field/tests/src/Kernel/FieldStorageCrudTest.php +++ b/core/modules/field/tests/src/Kernel/FieldStorageCrudTest.php @@ -284,8 +284,9 @@ public function testIndexes() { /** * Test the deletion of a field storage. */ - public function testDelete() { - // TODO: Also test deletion of the data stored in the field ? + public function testDeleteNoData() { + // Deleting and purging field storages with data is tested in + // \Drupal\Tests\field\Kernel\BulkDeleteTest. // Create two fields (so we can test that only one is deleted). $field_storage_definition = [ @@ -317,23 +318,22 @@ public function testDelete() { $this->assertTrue(!empty($field_storage) && !$field_storage->isDeleted(), 'A new storage is not marked for deletion.'); FieldStorageConfig::loadByName('entity_test', $field_storage_definition['field_name'])->delete(); - // Make sure that the field is marked as deleted when it is specifically - // loaded. - $field_storage = current(entity_load_multiple_by_properties('field_storage_config', ['field_name' => $field_storage_definition['field_name'], 'include_deleted' => TRUE])); - $this->assertTrue($field_storage->isDeleted(), 'A deleted storage is marked for deletion.'); + // Make sure that the field storage is deleted as it had no data. + $field_storages = entity_load_multiple_by_properties('field_storage_config', ['field_name' => $field_storage_definition['field_name'], 'include_deleted' => TRUE]); + $this->assertEquals(0, count($field_storages), 'Field storage was deleted'); // Make sure that this field is marked as deleted when it is // specifically loaded. - $field = current(entity_load_multiple_by_properties('field_config', ['entity_type' => 'entity_test', 'field_name' => $field_definition['field_name'], 'bundle' => $field_definition['bundle'], 'include_deleted' => TRUE])); - $this->assertTrue($field->isDeleted(), 'A field whose storage was deleted is marked for deletion.'); + $fields = entity_load_multiple_by_properties('field_config', ['entity_type' => 'entity_test', 'field_name' => $field_definition['field_name'], 'bundle' => $field_definition['bundle'], 'include_deleted' => TRUE]); + $this->assertEquals(0, count($fields), 'Field storage was deleted'); // Try to load the storage normally and make sure it does not show up. $field_storage = FieldStorageConfig::load('entity_test.' . $field_storage_definition['field_name']); - $this->assertTrue(empty($field_storage), 'A deleted storage is not loaded by default.'); + $this->assertTrue(empty($field_storage), 'Field storage was deleted'); // Try to load the field normally and make sure it does not show up. $field = FieldConfig::load('entity_test.' . '.' . $field_definition['bundle'] . '.' . $field_definition['field_name']); - $this->assertTrue(empty($field), 'A field whose storage was deleted is not loaded by default.'); + $this->assertTrue(empty($field), 'Field was deleted'); // Make sure the other field and its storage are not deleted. $another_field_storage = FieldStorageConfig::load('entity_test.' . $another_field_storage_definition['field_name']); diff --git a/core/modules/media/media.install b/core/modules/media/media.install index 823116418c..87aeb360a0 100644 --- a/core/modules/media/media.install +++ b/core/modules/media/media.install @@ -28,15 +28,6 @@ function media_install() { } /** - * Implements hook_uninstall(). - * - * @TODO Remove when https://www.drupal.org/node/2884202 is fixed. - */ -function media_uninstall() { - \Drupal::moduleHandler()->invoke('field', 'cron'); -} - -/** * Implements hook_requirements(). */ function media_requirements($phase) { diff --git a/core/modules/system/tests/src/Functional/Module/UninstallTest.php b/core/modules/system/tests/src/Functional/Module/UninstallTest.php index 2b17b8fcf6..aa3f41dbf6 100644 --- a/core/modules/system/tests/src/Functional/Module/UninstallTest.php +++ b/core/modules/system/tests/src/Functional/Module/UninstallTest.php @@ -21,7 +21,18 @@ class UninstallTest extends BrowserTestBase { * * @var array */ - public static $modules = ['module_test', 'user', 'views', 'node']; + public static $modules = ['module_test', 'user', 'views', 'node', 'media']; + + /** + * @group failing + */ + public function testUninstallMedia() { + \Drupal::service('module_installer')->uninstall(['media']); + + \Drupal::entityManager()->clearCachedDefinitions(); + + \Drupal::service('module_installer')->uninstall(['file']); + } /** * Tests the hook_modules_uninstalled() of the user module.