diff --git a/core/lib/Drupal/Core/Config/ConfigManager.php b/core/lib/Drupal/Core/Config/ConfigManager.php index 1053b4f..2c69497 100644 --- a/core/lib/Drupal/Core/Config/ConfigManager.php +++ b/core/lib/Drupal/Core/Config/ConfigManager.php @@ -130,10 +130,11 @@ function createSnapshot(StorageInterface $source_storage, StorageInterface $snap * @param string $type * The extension type; e.g., 'module' or 'theme'. * @param string $name - * The name of the module or theme to install default configuration for. + * The name of the module or theme to uninstall default configuration for. */ function uninstall($type, $name) { $config_names = $this->configFactory->listAll($name . '.'); + // @todo this should also use the entity api, no ? foreach ($config_names as $config_name) { $this->configFactory->get($config_name)->delete(); } diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index 11b36bf..5b5c01e 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -43,6 +43,13 @@ private $isSyncing = FALSE; /** + * Whether the config is deleted through the uninstall process. + * + * @var bool + */ + private $isUninstalling = FALSE; + + /** * The configuration entity's dependencies. * * @var array @@ -150,6 +157,20 @@ public function isSyncing() { /** * {@inheritdoc} */ + public function setUninstalling($uninstalling) { + $this->isUninstalling = $uninstalling; + } + + /** + * {@inheritdoc} + */ + public function isUninstalling() { + return $this->isUninstalling; + } + + /** + * {@inheritdoc} + */ public function createDuplicate() { $duplicate = parent::createDuplicate(); // Prevent the new duplicate from being misinterpreted as a rename. diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityDependency.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityDependency.php index 96f5c09..ef9b28f 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityDependency.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityDependency.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Config\Entity; +use Drupal\Core\Entity\EntityTypeInterface; + /** * Class ConfigEntityDependency */ @@ -34,6 +36,11 @@ class ConfigEntityDependency { protected $dependencies; /** + * The configuration entity type. + */ + protected $entityType; + + /** * Constructs the configuration entity dependency from the entity values. * * @param string $name @@ -44,6 +51,7 @@ class ConfigEntityDependency { public function __construct($name, $values) { $this->name = $name; $this->uuid = $values['uuid']; + $this->entityType = $this->setEntityType($name); if (isset($values['dependencies'])) { $this->dependencies = $values['dependencies']; } @@ -109,6 +117,25 @@ public function getName() { } /** + * Gets the entity type of this object. + * + * @return string + * The entity type. + */ + public function getEntityType() { + return $this->entityType; + } + + public function setEntityType($name) { + // @todo copied from ConfigManager::getEntityTypeIdByName() + $entities = array_filter(\Drupal::entityManager()->getDefinitions(), function (EntityTypeInterface $entity_type) use ($name) { + return ($config_prefix = $entity_type->getConfigPrefix()) && strpos($name, $config_prefix . '.') === 0; + }); + return key($entities); + + } + + /** * Gets the configuration entity's configuration dependency name. * * @return string @@ -118,4 +145,9 @@ public function getConfigDependencyName() { return $this->getName() . ':' . $this->uuid(); } + // @todo copied from ConfigStorageController::getIDFromConfigName() + function getIDFromConfigName() { + return substr($this->name, strlen(\Drupal::entityManager()->getDefinition($this->getEntityType())->getConfigPrefix() . '.')); + } + } diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php index cdb8d38..7145310 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityInterface.php @@ -92,6 +92,14 @@ public function status(); public function isSyncing(); /** + * Returns whether the configuration entity is deleted through the import + * process. + * + * @return bool + */ + public function isUninstalling(); + + /** * Returns the value of a property. * * @param string $property_name diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index 472ba5a..72534a7 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -724,17 +724,26 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) { // Uninstall the module. module_load_install($module); $this->invoke($module, 'uninstall'); - drupal_uninstall_schema($module); // Remove all dependent configuration entities. - // @todo Move above schema uninstall and delete configuration entities - // using configuration entity API. This does not work at the moment - // because exceptions are thrown during a delete. For example - // \Drupal\language\Entity\Language::preDelete(). + // @todo maybe move the call to configManager::uninstall() to here + // and move this code in that method ? + // @todo start a batch purge any leftover field data. Field API does one + // purge call anyway, so in case there was no data in the tables, this + // will be fast. $dependent_entities = \Drupal::service('config.manager')->findConfigEntityDependents('module', array($module)); foreach ($dependent_entities as $dependent_entity) { - \Drupal::configFactory()->get($dependent_entity->getName())->delete(); + $entity = \Drupal::entityManager()->getStorageController($dependent_entity->getEntityType())->load($dependent_entity->getIDFromConfigName()); + // Sometimes entities can be deleted already, e.g. field instances. + if ($entity) { + $entity->setUninstalling(TRUE); + $entity->delete(); + } } + + // Remove the schema. + drupal_uninstall_schema($module); + // Remove the module's entry from the config. $module_config->clear("enabled.$module")->save(); diff --git a/core/modules/field/field.module b/core/modules/field/field.module index 3a6de8a..bec8166 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -144,49 +144,6 @@ function field_cron() { } /** - * Implements hook_system_info_alter(). - * - * Goes through a list of all modules that provide a field type and makes them - * required if there are any active fields of that type. - * - * @todo Remove once fields are deleted through the entity API in - * \Drupal\Core\Extension\ModuleHandler::uninstall(). See - * https://drupal.org/node/2080823 for more information. - */ -function field_system_info_alter(&$info, $file, $type) { - // It is not safe to call entity_load_multiple_by_properties() during - // maintenance mode. - if ($type == 'module' && !defined('MAINTENANCE_MODE')) { - $fields = entity_load_multiple_by_properties('field_entity', array('module' => $file->name, 'include_deleted' => TRUE)); - if ($fields) { - $info['required'] = TRUE; - - // Provide an explanation message (only mention pending deletions if there - // remains no actual, non-deleted fields) - $non_deleted = FALSE; - foreach ($fields as $field) { - if (empty($field->deleted)) { - $non_deleted = TRUE; - break; - } - } - if ($non_deleted) { - if (\Drupal::moduleHandler()->moduleExists('field_ui')) { - $explanation = t('Field type(s) in use - see Field list', array('@fields-page' => url('admin/reports/fields'))); - } - else { - $explanation = t('Fields type(s) in use'); - } - } - else { - $explanation = t('Fields pending deletion'); - } - $info['explanation'] = $explanation; - } - } -} - -/** * Implements hook_entity_field_info() to define all configured fields. */ function field_entity_field_info($entity_type) { diff --git a/core/modules/field/lib/Drupal/field/Entity/Field.php b/core/modules/field/lib/Drupal/field/Entity/Field.php index 8541614..ae2c050 100644 --- a/core/modules/field/lib/Drupal/field/Entity/Field.php +++ b/core/modules/field/lib/Drupal/field/Entity/Field.php @@ -423,6 +423,7 @@ public static function preDelete(EntityStorageControllerInterface $storage_contr $deleted_fields[$field->uuid] = $config; } } + $state->set('field.field.deleted', $deleted_fields); } diff --git a/core/modules/language/lib/Drupal/language/Entity/Language.php b/core/modules/language/lib/Drupal/language/Entity/Language.php index 63e8542..bd4a8f6 100644 --- a/core/modules/language/lib/Drupal/language/Entity/Language.php +++ b/core/modules/language/lib/Drupal/language/Entity/Language.php @@ -107,7 +107,7 @@ public function preSave(EntityStorageControllerInterface $storage_controller) { public static function preDelete(EntityStorageControllerInterface $storage_controller, array $entities) { $default_language = \Drupal::service('language.default')->get(); foreach ($entities as $entity) { - if ($entity->id() == $default_language->id) { + if ($entity->id() == $default_language->id && !$entity->isUninstalling()) { throw new DeleteDefaultLanguageException('Can not delete the default language'); } } diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php index b420a97..a61b152 100644 --- a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php +++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php @@ -149,6 +149,13 @@ class ViewUI implements ViewStorageInterface { private $isSyncing = FALSE; /** + * Whether the config is being deleted through the uninstall process. + * + * @var bool + */ + private $isUninstalling = FALSE; + + /** * Constructs a View UI object. * * @param \Drupal\views\ViewStorageInterface $storage @@ -207,11 +214,25 @@ public function setSyncing($syncing) { /** * {@inheritdoc} */ + public function setUninstalling($isUninstalling) { + $this->isUninstalling = $isUninstalling; + } + + /** + * {@inheritdoc} + */ public function isSyncing() { return $this->isSyncing; } /** + * {@inheritdoc} + */ + public function isUninstalling() { + return $this->isUninstalling; + } + + /** * Basic submit handler applicable to all 'standard' forms. * * This submit handler determines whether the user wants the submitted changes