diff --git a/core/includes/config.inc b/core/includes/config.inc index 5e07c54..dad65dc 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -23,15 +23,28 @@ function config_install_default_config($type, $name) { if (is_dir($config_dir)) { $source_storage = new FileStorage($config_dir); $target_storage = drupal_container()->get('config.storage'); - $null_storage = new NullStorage(); - - // Upon installation, only new config objects need to be created. - // config_sync_get_changes() would potentially perform a diff of hundreds or - // even thousands of config objects that happen to be contained in the - // active configuration. We leverage the NullStorage to avoid that needless - // computation of differences. - $config_changes = config_sync_get_changes($source_storage, $null_storage); - if (empty($config_changes)) { + + // If this module defines any ConfigEntity types, then create a manifest file + // for each of them with a listing of the objects it maintains. + foreach (config_get_module_config_entities($name) as $entity_type => $entity_info) { + $manifest_config = config('manifest.' . $entity_info['config prefix']); + $manifest_data = array(); + foreach ($source_storage->listAll($entity_info['config prefix']) as $config_name) { + // @todo Figure out a better way to strip the prefix from the $config_name. Note + // that we can't use the whole $config_name here, because config keys can not + // contain the "." character. See also http://drupal.org/node/1760358. + $manifest_data[str_replace($entity_info['config prefix'] . '.', '', $config_name)] = $config_name; + } + $manifest_config->setData($manifest_data)->save(); + } + + $config_changes = array( + 'delete' => array(), + 'create' => array(), + 'change' => array(), + ); + $config_changes['create'] = $source_storage->listAll(); + if (empty($config_changes['create'])) { return; } $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage); @@ -40,6 +53,28 @@ function config_install_default_config($type, $name) { } /** + * Uninstalls the default configuration of a given extension. + * + * @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. + */ +function config_uninstall_default_config($type, $name) { + $storage = drupal_container()->get('config.storage'); + $config_names = $storage->listAll($name . '.'); + foreach ($config_names as $config_name) { + config($config_name)->delete(); + } + + // If this module defines any ConfigEntity types, then delete the manifest + // file for each of them. + foreach (config_get_module_config_entities($name) as $entity_type) { + config('manifest.' . $entity_info['config prefix'])->delete(); + } +} + +/** * Gets configuration object names starting with a given prefix. * * @see Drupal\Core\Config\StorageInterface::listAll() @@ -80,18 +115,32 @@ function config($name) { * storage, or FALSE if there are no differences. */ function config_sync_get_changes(StorageInterface $source_storage, StorageInterface $target_storage) { - $source_names = $source_storage->listAll(); - $target_names = $target_storage->listAll(); + // Config entities maintain 'manifest' files that list the objects they + // are currently handling. Each file is a simple indexed array of config + // object names. In order to generate a list of objects that have been + // created or deleted we need to open these files in both the source and + // target storage, generate an array of the objects, and compare them. + $source_config_data = array(); + $target_config_data = array(); + foreach ($source_storage->listAll('manifest') as $name) { + $source_config_data = array_merge($source_config_data, $source_storage->read($name)); + $target_config_data = array_merge($target_config_data, $target_storage->read($name)); + } + $config_changes = array( - 'create' => array_diff($source_names, $target_names), + 'create' => array_diff($source_config_data, $target_config_data), 'change' => array(), - 'delete' => array_diff($target_names, $source_names), + 'delete' => array_diff($target_config_data, $source_config_data), ); - foreach (array_intersect($source_names, $target_names) as $name) { - $source_config_data = $source_storage->read($name); - $target_config_data = $target_storage->read($name); - if ($source_config_data !== $target_config_data) { - $config_changes['change'][] = $name; + + foreach (array_intersect($source_storage->listAll(), $target_storage->listAll()) as $name) { + // Ignore manifest files + if (substr($name, 0, 9) != 'manifest.') { + $source_config_data = $source_storage->read($name); + $target_config_data = $target_storage->read($name); + if ($source_config_data !== $target_config_data) { + $config_changes['change'][] = $name; + } } } @@ -213,17 +262,33 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou } /** - * Exports the active configuration to staging. + * Return a list of all config entity types provided by a module. + * + * @param string $module + * The name of the module possibly providing config entities. + * + * @return array + * An associative array containing the entity info for any config entities + * provided by the requested module, keyed by the entity type. */ -function config_export() { - // Retrieve a list of differences between the active configuration and staging. - $source_storage = drupal_container()->get('config.storage'); - $target_storage = drupal_container()->get('config.storage.staging'); - - $config_changes = config_sync_get_changes($source_storage, $target_storage); - if (empty($config_changes)) { - return; +function config_get_module_config_entities($module) { + // @todo The proper way to get this info would be to call get_entity_info() + // which adds defaults and acknowledges hook_entity_info_alter(). However, at + // this time, you can not query that info to check entities owned by a + // arbitrary module. There is a patch in the queue which would add a + // required 'module' key to the entity info which could be used to query the + // way we want to here, but until it lands the code below is our only option. + // @see http://drupal.org/node/1763974#comment-6620908 + $config_entities = array(); + if (function_exists($module . '_entity_info')) { + $info = module_invoke($module, 'entity_info'); + foreach ($info as $entity_type => $entity_info) { + // @todo is this a sane and/or trustworthy way to determine that this + // is in fact a config entity? + if (isset($entity_info['config prefix'])) { + $config_entities[$entity_type] = $entity_info; + } + } } - config_sync_changes($config_changes, $source_storage, $target_storage); - return TRUE; + return $config_entities; } diff --git a/core/includes/module.inc b/core/includes/module.inc index c1d99f2..efef6d5 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -695,10 +695,7 @@ function module_uninstall($module_list = array(), $uninstall_dependents = TRUE) drupal_uninstall_schema($module); // Remove all configuration belonging to the module. - $config_names = $storage->listAll($module . '.'); - foreach ($config_names as $config_name) { - config($config_name)->delete(); - } + config_uninstall_default_config('module', $module); watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO); $schema_store->delete($module); diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php index 07849c6..dd3ccd8 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php @@ -250,6 +250,11 @@ public function delete($ids) { foreach ($entities as $id => $entity) { $config = config($this->entityInfo['config prefix'] . '.' . $entity->id()); $config->delete(); + + // Remove the entity from the manifest file. + config('manifest.' . $this->entityInfo['config prefix']) + ->clear($entity->id()) + ->save(); } $this->postDelete($entities); @@ -304,6 +309,14 @@ public function save(EntityInterface $entity) { $this->invokeHook('insert', $entity); } + // Add this entity to the manifest file if necessary. + $config = config('manifest.' . $this->entityInfo['config prefix']); + $manifest = $config->get(); + if (!in_array($this->entityInfo['config prefix'] . '.' . $entity->id(), $manifest)) { + $manifest[$entity->id()] = $this->entityInfo['config prefix'] . '.' . $entity->id(); + } + $config->setData($manifest)->save(); + unset($entity->original); return $return; diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php index b659ace..9b346c3 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php @@ -48,12 +48,6 @@ function testNoImport() { // Verify that a bare config() does not involve module APIs. $this->assertFalse(isset($GLOBALS['hook_config_test'])); - - // Export. - config_export(); - - // Verify that config_export() does not involve module APIs. - $this->assertFalse(isset($GLOBALS['hook_config_test'])); } /** @@ -71,9 +65,6 @@ function testDeleted() { $config = config($dynamic_name); $this->assertIdentical($config->get('id'), 'default'); - // Export. - config_export(); - // Delete the configuration objects from the staging directory. $staging->delete($name); $staging->delete($dynamic_name); @@ -111,9 +102,6 @@ function testNew() { $storage = $this->container->get('config.storage'); $staging = $this->container->get('config.storage.staging'); - // Export. - config_export(); - // Verify the configuration to create does not exist yet. $this->assertIdentical($storage->exists($name), FALSE, $name . ' not found.'); $this->assertIdentical($storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.'); @@ -167,9 +155,6 @@ function testUpdated() { $storage = $this->container->get('config.storage'); $staging = $this->container->get('config.storage.staging'); - // Export. - config_export(); - // Verify that the configuration objects to import exist. $this->assertIdentical($storage->exists($name), TRUE, $name . ' found.'); $this->assertIdentical($storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php index f3fd97f..173fe0d 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php @@ -32,38 +32,6 @@ function setUp() { } /** - * Tests exporting configuration. - */ - function testExport() { - $name = 'config_test.system'; - $dynamic_name = 'config_test.dynamic.default'; - - // Verify the default configuration values exist. - $config = config($name); - $this->assertIdentical($config->get('foo'), 'bar'); - $config = config($dynamic_name); - $this->assertIdentical($config->get('id'), 'default'); - - // Verify that both appear as deleted by default. - $this->drupalGet('admin/config/development/sync/export'); - $this->assertText($name); - $this->assertText($dynamic_name); - - // Export and verify that both do not appear anymore. - $this->drupalPost(NULL, array(), t('Export all')); - $this->assertUrl('admin/config/development/sync/export'); - $this->assertNoText($name); - $this->assertNoText($dynamic_name); - - // Verify that there are no further changes to export. - $this->assertText(t('There are no configuration changes.')); - - // Verify that the import screen shows no changes either. - $this->drupalGet('admin/config/development/sync'); - $this->assertText(t('There are no configuration changes.')); - } - - /** * Tests importing configuration. */ function testImport() {