diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php index c8defe7..4eeeab8 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstaller.php +++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Config; +use Drupal\Component\Utility\String; use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\Entity\ConfigDependencyManager; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -145,16 +146,17 @@ protected function listDefaultConfigCollection($collection, $type, $name, array $extension_path = drupal_get_path($type, $name); if ($type !== 'core' && is_dir($extension_path . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY)) { $default_storage = new FileStorage($extension_path . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection); - $other_module_config = array_filter($default_storage->listAll(), function ($value) use ($name) { - return !preg_match('/^' . $name . '\./', $value); - }); - - $other_module_config = array_filter($other_module_config, function ($config_name) use ($enabled_extensions) { + $extension_provided_config = array_filter($default_storage->listAll(), function ($config_name) use ($config_to_install, $enabled_extensions) { + // Ensure that we have not already discovered the config to install. + if (in_array($config_name, $config_to_install)) { + return FALSE; + } + // Ensure the configuration is provided by an enabled module $provider = Unicode::substr($config_name, 0, strpos($config_name, '.')); return in_array($provider, $enabled_extensions); }); - $config_to_install = array_merge($config_to_install, $other_module_config); + $config_to_install = array_merge($config_to_install, $extension_provided_config); } return $config_to_install; @@ -322,4 +324,32 @@ public function setSyncing($status) { public function isSyncing() { return $this->isSyncing; } + + /** + * {@inheritdoc} + */ + public function findPreExistingConfiguration($type, $name) { + $existing_configuration = array(); + // Gather information about all the supported collections. + $collection_info = $this->configManager->getConfigCollectionInfo(); + + // Read enabled extensions directly from configuration to avoid circular + // dependencies with ModuleHandler and ThemeHandler. + $extension_config = $this->configFactory->get('core.extension'); + $enabled_extensions = array_keys((array) $extension_config->get('module')); + $enabled_extensions += array_keys((array) $extension_config->get('theme')); + // Add the extensions that will be enabled to the list of enabled + // extensions. + $enabled_extensions[] = $name; + foreach ($collection_info->getCollectionNames(TRUE) as $collection) { + $config_to_install = $this->listDefaultConfigCollection($collection, $type, $name, $enabled_extensions); + $active_storage = $this->getActiveStorage($collection); + foreach ($config_to_install as $config_name) { + if ($active_storage->exists($config_name)) { + $existing_configuration[$collection][] = $config_name; + } + } + } + return $existing_configuration; + } } diff --git a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php index 642044e..7dfa51f 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php @@ -84,4 +84,18 @@ public function setSyncing($status); */ public function isSyncing(); + /** + * Validate default configuration before installation. + * + * @param string $type + * Type of extension to install. + * @param string $name + * Name of extension to install. + * + * @return array + * Array of configuration objects that already exist keyed by collection. + * + */ + public function findPreExistingConfiguration($type, $name); + } diff --git a/core/lib/Drupal/Core/Config/PreExistingConfigException.php b/core/lib/Drupal/Core/Config/PreExistingConfigException.php new file mode 100644 index 0000000..5a2cba7 --- /dev/null +++ b/core/lib/Drupal/Core/Config/PreExistingConfigException.php @@ -0,0 +1,68 @@ +extensions = $extensions; + } + + /** + * Gets the extensions being installed. + * + * @return array + * The extensions being installed. + */ + public function getExtensions() { + return $this->extensions; + } + + /** + * Sets the configuration names. + * + * @param array $config_names + * The configuration names keyed by collection. + */ + public function setConfigNames($config_names) { + $this->configNames = $config_names; + } + + /** + * Gets the configuration names keyed by collection. + * + * @return string + * The configuration names. + */ + public function getConfigNames() { + return $this->configNames; + } +} diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index 89b47b3..8c8943f 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -11,6 +11,7 @@ use Drupal\Component\Serialization\Yaml; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Config\PreExistingConfigException; use Drupal\Core\Entity\Schema\EntitySchemaProviderInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -637,8 +638,8 @@ public static function parseDependency($dependency) { public function install(array $module_list, $enable_dependencies = TRUE) { $extension_config = \Drupal::config('core.extension'); if ($enable_dependencies) { - // Get all module data so we can find dependencies and sort. $module_data = system_rebuild_module_data(); + // Get all module data so we can find dependencies and sort. $module_list = $module_list ? array_combine($module_list, $module_list) : array(); if (array_diff_key($module_list, $module_data)) { // One or more of the given modules doesn't exist. @@ -676,6 +677,20 @@ public function install(array $module_list, $enable_dependencies = TRUE) { // Sort the module list by their weights (reverse). arsort($module_list); $module_list = array_keys($module_list); + + // Validate default configuration of this module. Bail if unable to + // install. Should not continue installing more modules because those + // may depend on this one. + $existing_configuration = array(); + foreach ($module_list as $module) { + $existing_configuration = array_merge_recursive(\Drupal::service('config.installer')->findPreExistingConfiguration($module_data[$module]->getType(), $module), $existing_configuration); + } + if (count($existing_configuration)) { + $exception = new PreExistingConfigException(); + $exception->setConfigNames($existing_configuration); + $exception->setExtensions($module_list); + throw $exception; + } } // Required for module installation checks. diff --git a/core/modules/config/src/Tests/ConfigInstallWebTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallWebTest.php.orig similarity index 97% copy from core/modules/config/src/Tests/ConfigInstallWebTest.php copy to core/modules/config/lib/Drupal/config/Tests/ConfigInstallWebTest.php.orig index 776daa4..7116bea 100644 --- a/core/modules/config/src/Tests/ConfigInstallWebTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallWebTest.php.orig @@ -7,7 +7,6 @@ namespace Drupal\config\Tests; -use Drupal\Core\Config\InstallStorage; use Drupal\simpletest\WebTestBase; use Drupal\Core\Config\FileStorage; @@ -123,7 +122,7 @@ function testInstallProfileConfigOverwrite() { // Verify that the original data matches. We have to read the module config // file directly, because the install profile default system.cron.yml // configuration file was used to create the active configuration. - $config_dir = drupal_get_path('module', 'system') . '/'. InstallStorage::CONFIG_INSTALL_DIRECTORY; + $config_dir = drupal_get_path('module', 'system') . '/config'; $this->assertTrue(is_dir($config_dir)); $source_storage = new FileStorage($config_dir); $data = $source_storage->read($config_name); diff --git a/core/modules/config/src/Tests/ConfigInstallWebTest.php b/core/modules/config/src/Tests/ConfigInstallWebTest.php index 776daa4..755cd75 100644 --- a/core/modules/config/src/Tests/ConfigInstallWebTest.php +++ b/core/modules/config/src/Tests/ConfigInstallWebTest.php @@ -8,6 +8,8 @@ namespace Drupal\config\Tests; use Drupal\Core\Config\InstallStorage; +use Drupal\Core\Config\PreExistingConfigException; +use Drupal\Core\Config\StorageInterface; use Drupal\simpletest\WebTestBase; use Drupal\Core\Config\FileStorage; @@ -83,6 +85,17 @@ function testIntegrationModuleReinstallation() { $this->assertIdentical($config_entity->get('label'), 'Customized integration config label'); // Reinstall the integration module. + try { + \Drupal::moduleHandler()->install(array('config_integration_test')); + $this->fail('Expected PreExistingConfigException not thrown.'); + } + catch (PreExistingConfigException $e) { + $this->assertEqual($e->getExtensions(), array('config_integration_test')); + $this->assertEqual($e->getConfigNames(), array(StorageInterface::DEFAULT_COLLECTION => array($default_configuration_entity))); + } + + // Delete the configuration entity so that the install will work. + $config_entity->delete(); \Drupal::moduleHandler()->install(array('config_integration_test')); // Verify the integration module's config was re-installed. @@ -92,10 +105,10 @@ function testIntegrationModuleReinstallation() { $this->assertIdentical($config_static->isNew(), FALSE); $this->assertIdentical($config_static->get('foo'), 'default setting'); - // Verify the customized integration config still exists. + // Verify the integration config is using the default. $config_entity = \Drupal::config($default_configuration_entity); $this->assertIdentical($config_entity->isNew(), FALSE); - $this->assertIdentical($config_entity->get('label'), 'Customized integration config label'); + $this->assertIdentical($config_entity->get('label'), 'Default integration config label'); } /** diff --git a/core/modules/config/src/Tests/ConfigOtherModuleTest.php b/core/modules/config/src/Tests/ConfigOtherModuleTest.php index 9ba92da..ebeb8a9 100644 --- a/core/modules/config/src/Tests/ConfigOtherModuleTest.php +++ b/core/modules/config/src/Tests/ConfigOtherModuleTest.php @@ -75,11 +75,6 @@ public function testInstallOtherModuleFirst() { // Default configuration provided by config_test should still exist. $this->assertTrue(entity_load('config_test', 'dotted.default', TRUE), 'The configuration is not deleted.'); - - // Re-enable module to test that default config is unchanged. - $this->moduleHandler->install(array('config_other_module_config')); - $config_entity = entity_load('config_test', 'other_module', TRUE); - $this->assertEqual($config_entity->get('style'), "The piano ain't got no wrong notes.", 'Re-enabling the module does not install default config over the existing config entity.'); } /** diff --git a/core/modules/config/tests/config_install_fail/config/config_test.dynamic.dotted.default.yml b/core/modules/config/tests/config_install_fail/config/config_test.dynamic.dotted.default.yml new file mode 100644 index 0000000..6e2af21 --- /dev/null +++ b/core/modules/config/tests/config_install_fail/config/config_test.dynamic.dotted.default.yml @@ -0,0 +1,6 @@ +id: dotted.default +label: 'Config install fail' +weight: 0 +protected_property: Default +# Intentionally commented out to verify default status behavior. +# status: 1 diff --git a/core/modules/config/tests/config_install_fail/config_install_fail.info.yml b/core/modules/config/tests/config_install_fail/config_install_fail.info.yml new file mode 100644 index 0000000..ebe72ad --- /dev/null +++ b/core/modules/config/tests/config_install_fail/config_install_fail.info.yml @@ -0,0 +1,8 @@ +name: 'Configuration test install fial' +type: module +package: Testing +version: VERSION +core: 8.x +hidden: true +dependencies: + - config_test diff --git a/core/modules/config/tests/config_install_fail/config_install_fail.module b/core/modules/config/tests/config_install_fail/config_install_fail.module new file mode 100644 index 0000000..e77c7ce --- /dev/null +++ b/core/modules/config/tests/config_install_fail/config_install_fail.module @@ -0,0 +1,6 @@ +installConfig(array('field_test_config')); $active = $this->container->get('config.storage'); $staging = $this->container->get('config.storage.staging'); $this->copyConfig($active, $staging); diff --git a/core/modules/field/src/Tests/FieldImportDeleteTest.php b/core/modules/field/src/Tests/FieldImportDeleteTest.php index b0485c4..c3142a3 100644 --- a/core/modules/field/src/Tests/FieldImportDeleteTest.php +++ b/core/modules/field/src/Tests/FieldImportDeleteTest.php @@ -17,6 +17,10 @@ class FieldImportDeleteTest extends FieldUnitTestBase { /** * Modules to enable. * + * The default configuration provided by field_test_config is imported by + * \Drupal\field\Tests\FieldUnitTestBase::setUp() when it installs field + * configuration. + * * @var array */ public static $modules = array('field_test_config'); @@ -57,9 +61,6 @@ public function testImportDelete() { // Create a second bundle for the 'Entity test' entity type. entity_test_create_bundle('test_bundle'); - // Import default config. - $this->installConfig(array('field_test_config')); - // Get the uuid's for the fields. $field_uuid = entity_load('field_config', $field_id)->uuid(); $field_uuid_2 = entity_load('field_config', $field_id_2)->uuid();