diff --git a/core/includes/config.inc b/core/includes/config.inc index 3e22534..6cebc9e 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -24,34 +24,53 @@ * The name of the module or theme to install default configuration for. */ function config_install_default_config($type, $name) { - $config_dir = drupal_get_path($type, $name) . '/config'; - if (is_dir($config_dir)) { - $source_storage = new FileStorage($config_dir); - $target_storage = drupal_container()->get('config.storage'); + if ($type == 'module') { + $list = module_list(); + } + else { + $list = array_keys(list_themes()); + } + $config_changes = array('delete' => array(), 'change' => array()); + $target_storage = drupal_container()->get('config.storage'); - // 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) { - list(, , $id) = explode('.', $config_name); - $manifest_data[$id]['name'] = $config_name; + // Determine config entity types implemented by the installed extension. + $config_entity_types = config_get_module_config_entities($name); + + foreach ($list as $extension) { + if (!is_dir($config_dir = drupal_get_path($type, $extension) . '/config')) { + continue; + } + $source_storage = new FileStorage($config_dir); + // For the extension being installed, install all configuration objects. + if ($extension == $name) { + $config_changes['create'] = $source_storage->listAll($extension . '.'); + } + // Other extensions may only provide default configuration for configuration + // entities. Static settings can neither be provided nor overridden. + else { + $config_changes['create'] = array(); + foreach ($config_entity_types as $entity_type => $entity_info) { + $config_changes['create'] = $source_storage->listAll($entity_info['config_prefix'] . '.'); } - $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; + continue; } $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage); - config_sync_changes($remaining_changes, $source_storage, $target_storage); + if ($extension == $name) { + config_sync_changes($remaining_changes, $source_storage, $target_storage); + } + } + // 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_entity_types as $entity_type => $entity_info) { + $manifest_config = config('manifest.' . $entity_info['config_prefix']); + $id_index = count(explode('.', $entity_info['config_prefix'])); + foreach ($target_storage->listAll($entity_info['config_prefix'] . '.') as $config_name) { + $parts = explode('.', $config_name); + $manifest_config->set($parts[$id_index] . '.name', $config_name); + } + $manifest_config->save(); } } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php index b549119..147be46 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php @@ -7,12 +7,12 @@ namespace Drupal\config\Tests; -use Drupal\simpletest\DrupalUnitTestBase; +use Drupal\simpletest\WebTestBase; /** * Tests installation of configuration objects in installation functionality. */ -class ConfigInstallTest extends DrupalUnitTestBase { +class ConfigInstallTest extends WebTestBase { public static function getInfo() { return array( 'name' => 'Installation functionality', @@ -26,42 +26,263 @@ function setUp() { // Ensure the global variable being asserted by this test does not exist; // a previous test executed in this request/process might have set it. + unset($GLOBALS['hook_config_import']); unset($GLOBALS['hook_config_test']); } /** - * Tests module installation. + * Tests installing and uninstalling a module that provides default config. */ function testModuleInstallation() { $default_config = 'config_test.system'; $default_configuration_entity = 'config_test.dynamic.default'; - // Verify that default module config does not exist before installation yet. - $config = config($default_config); - $this->assertIdentical($config->isNew(), TRUE); - $config = config($default_configuration_entity); - $this->assertIdentical($config->isNew(), TRUE); + // Verify that default module config does not exist before installation. + $this->assertNoConfig($default_config, 'after installing the module'); + $this->assertNoConfig($default_configuration_entity, 'after installing the module'); // Install the test module. - $this->enableModules(array('config_test')); + module_enable(array('config_test')); // Verify that default module config exists. - $config = config($default_config); - $this->assertIdentical($config->isNew(), FALSE); - $config = config($default_configuration_entity); - $this->assertIdentical($config->isNew(), FALSE); - - // Verify that configuration import callback was invoked for the dynamic - // configuration entity. - $this->assertTrue($GLOBALS['hook_config_import']); - - // Verify that config_test API hooks were invoked for the dynamic default - // configuration entity. - $this->assertFalse(isset($GLOBALS['hook_config_test']['load'])); - $this->assertTrue(isset($GLOBALS['hook_config_test']['presave'])); - $this->assertTrue(isset($GLOBALS['hook_config_test']['insert'])); - $this->assertFalse(isset($GLOBALS['hook_config_test']['update'])); - $this->assertFalse(isset($GLOBALS['hook_config_test']['predelete'])); - $this->assertFalse(isset($GLOBALS['hook_config_test']['delete'])); + $this->assertConfig($default_config, 'after installing the module'); + $this->assertConfig($default_configuration_entity, 'after installing the module'); + + // Confirm that hook_config_import_create() was invoked for the provided + // default config entity. + $import_hooks = array( + 'create' => TRUE, + 'change' => FALSE, + 'delete' => FALSE, + ); + // $GLOBALS['hook_config_import'] is set in config_test.module. + $this->assertConfigHooks($GLOBALS['hook_config_import'], $import_hooks, 'after installing the module'); + + // Confirm that the insert and presave config_test API hooks were invoked + // for the provided default config entity. + $api_hooks = array( + 'load' => FALSE, + 'presave' => TRUE, + 'insert' => TRUE, + 'update' => FALSE, + 'predelete' => FALSE, + 'delete' => FALSE, + ); + // $GLOBALS['hook_config_test'] is set in config_test.hooks.inc. + $this->assertConfigHooks($GLOBALS['hook_config_test'], $api_hooks, 'after installing the module'); + + // Disable the module and verify that the config is not removed. + module_disable(array('config_test')); + $this->assertConfig($default_config, 'after disabling the module'); + $this->assertConfig($default_configuration_entity, 'after disabling the module'); + + // Uninstall the module and verify that the config was removed. + module_uninstall(array('config_test')); + $this->assertNoConfig($default_config, 'after uninstalling the module'); + $this->assertNoConfig($default_configuration_entity, 'after uninstalling the module'); + } + + /** + * Tests installing config_test_extension_test before config_test. + * + * config_test_extension_test.module provides default configuration in + * config_test.module's namespace. + */ + function testExtensionInstallationFirst() { + // Verify that default module config does not exist before installation. + $extension_config_entity = 'config_test.dynamic.extension_default'; + $this->assertNoConfig($extension_config_entity, 'before installing either module'); + + // Install the extension module. + module_enable(array('config_test_extension_test')); + + // The config_test API is not available, so configuration objects in its + // namespace should not be installed. + $this->assertNoConfig($extension_config_entity, 'after installing config_test_extension_test first'); + + // Install the base API module. + module_enable(array('config_test')); + + // The extension module's default config should now be installed. + $this->assertConfig($extension_config_entity, 'after installing config_test second'); + + // Confirm that hook_config_import_create() was invoked for the provided + // default config entity. + $import_hooks = array( + 'create' => TRUE, + 'change' => FALSE, + 'delete' => FALSE, + ); + // $GLOBALS['hook_config_import'] is set in config_test.module. + $this->assertConfigHooks($GLOBALS['hook_config_import'], $import_hooks, 'after installing config_test second'); + + // Confirm that the insert and presave config_test API hooks were invoked + // for the provided default config entity. + $api_hooks = array( + 'load' => FALSE, + 'presave' => TRUE, + 'insert' => TRUE, + 'update' => FALSE, + 'predelete' => FALSE, + 'delete' => FALSE, + ); + // $GLOBALS['hook_config_test'] is set in config_test.hooks.inc. + $this->assertConfigHooks($GLOBALS['hook_config_test'], $api_hooks, 'after installing config_test second'); + + // Uninstall the extension module. + module_enable(array('config_test_extension_test')); + + // @todo Should the configuration be available here or not? } + + /** + * Tests installing config_test_extension_test after config_test. + * + * config_test_extension_test.module provides default configuration in + * config_test.module's namespace. + */ + function testExtensionInstallationSecond() { + // Verify that default module config does not exist before installation. + $extension_config_entity = 'config_test.dynamic.extension_default'; + $this->assertNoConfig($extension_config_entity, 'before installing either module'); + + // Install the base API module. + module_enable(array('config_test')); + + // The config_test_extension_test module is not yet installed, so its + // default configuration is not installed. + $this->assertNoConfig($extension_config_entity, 'after installing config_test first'); + + // Install the extension module. + module_enable(array('config_test_extension_test')); + + // The extension module's default config should now be installed. + $this->assertConfig($extension_config_entity, 'after installing config_test_extension_test second'); + + // Confirm that hook_config_import_create() was invoked for the provided + // default config entity. + $import_hooks = array( + 'create' => TRUE, + 'change' => FALSE, + 'delete' => FALSE, + ); + // $GLOBALS['hook_config_import'] is set in config_test.module. + $this->assertConfigHooks($GLOBALS['hook_config_import'], $import_hooks, 'after installing config_test_extension_test second'); + + // Confirm that the insert and presave config_test API hooks were invoked + // for the provided default config entity. + $api_hooks = array( + 'load' => FALSE, + 'presave' => TRUE, + 'insert' => TRUE, + 'update' => FALSE, + 'predelete' => FALSE, + 'delete' => FALSE, + ); + // $GLOBALS['hook_config_test'] is set in config_test.hooks.inc. + $this->assertConfigHooks($GLOBALS['hook_config_test'], $api_hooks, 'after installing config_test_extension_test second'); + + // Uninstall the base API module. + module_uninstall(array('config_test')); + + // The test configuration should no longer be available. + $this->assertNoConfig($extension_config_entity, 'after uninstalling config_test'); + } + + /** + * Asserts that a set of config hooks were invoked or not from passed data. + * + * @param array $actual + * An array of the hooks that were invoked (set in globals), with the key + * indicating the type of hook and the value as the hook implementation + * if it was invoked. + * @param array $expected + * An array indicating whether each hook was expected to be invoked or not, + * with the key indicating the type of hook and the value a Boolean + * indicating whether the hook should be invoked. + * @param string $when + * (optional) A text string describing when the assertion was called. + * + * @see config_test.module + * @see config_test.hooks.inc + */ + function assertConfigHooks(array $actual, array $expected, $when = '') { + foreach ($expected as $hook => $value) { + if (!empty($actual[$hook])) { + $function = $actual[$hook]; + } + else { + $function = $hook . ' hook'; + } + $this->assertTrue( + // The hook flag should evaluate to empty if it was not invoked. + ($value != empty($actual[$hook])), + format_string( + '%hook was @invoked @when.', + array( + '%hook' => $function, + '@invoked' => $value ? 'invoked' : 'not invoked', + '@when' => $when, + ) + ) + ); + } + } + + /** + * Asserts that the configuration with the given name is installed. + * + * @param string $name + * The full configuration object name to check, e.g. module.foo.bar. + * @param string $when + * (optional) A text string describing when the assertion was called. + */ + function assertConfig($name, $when = '') { + $config = config($name); + $file = config_get_config_directory() . '/' . $name . '.yml'; + $this->assertIdentical($config->isNew(), FALSE, format_string( + '%config config object exists @when.', + array( + '%config' => $name, + '@when' => $when, + ) + )); + $this->assertTrue(file_exists($file), format_string( + 'File for %config found in %file @when.', + array( + '%config' => $name, + '%file' => $file, + '@when' => $when, + ) + )); + } + + /** + * Asserts that the configuration with the given name is not installed. + * + * @param string $name + * The full configuration object name to check, e.g. module.foo.bar. + * @param string $when + * (optional) A text string describing when the assertion was called. + */ + function assertNoConfig($name, $when = '') { + $file = config_get_config_directory() . '/' . $name . '.yml'; + $config = config($name); + $this->assertIdentical($config->isNew(), TRUE, format_string( + '%config config object does not exist @when.', + array( + '%config' => $name, + '@when' => $when, + ) + )); + $this->assertFalse(file_exists($file), format_string( + 'File for %config not found in %file @when.', + array( + '%config' => $name, + '%file' => $file, + '@when' => $when, + ) + )); + } + } diff --git a/core/modules/config/tests/config_test/config_test.module b/core/modules/config/tests/config_test/config_test.module index 96d4aa0..9d465db 100644 --- a/core/modules/config/tests/config_test/config_test.module +++ b/core/modules/config/tests/config_test/config_test.module @@ -17,7 +17,7 @@ function config_test_config_import_create($name, $new_config, $old_config) { return FALSE; } // Set a global value we can check in test code. - $GLOBALS['hook_config_import'] = __FUNCTION__; + $GLOBALS['hook_config_import']['create'] = __FUNCTION__; $config_test = entity_create('config_test', $new_config->get()); $config_test->save(); @@ -32,7 +32,7 @@ function config_test_config_import_change($name, $new_config, $old_config) { return FALSE; } // Set a global value we can check in test code. - $GLOBALS['hook_config_import'] = __FUNCTION__; + $GLOBALS['hook_config_import']['change'] = __FUNCTION__; // @todo Make this less ugly. list(, , $id) = explode('.', $name); @@ -62,7 +62,7 @@ function config_test_config_import_delete($name, $new_config, $old_config) { return FALSE; } // Set a global value we can check in test code. - $GLOBALS['hook_config_import'] = __FUNCTION__; + $GLOBALS['hook_config_import']['delete'] = __FUNCTION__; // @todo Make this less ugly. list(, , $id) = explode('.', $name); diff --git a/core/modules/config/tests/config_test_extension_test/config/config_test.dynamic.extension_default.yml b/core/modules/config/tests/config_test_extension_test/config/config_test.dynamic.extension_default.yml new file mode 100644 index 0000000..a024b46 --- /dev/null +++ b/core/modules/config/tests/config_test_extension_test/config/config_test.dynamic.extension_default.yml @@ -0,0 +1,2 @@ +id: extension_default +label: Extension module default diff --git a/core/modules/config/tests/config_test_extension_test/config_test_extension_test.info b/core/modules/config/tests/config_test_extension_test/config_test_extension_test.info new file mode 100644 index 0000000..1057788 --- /dev/null +++ b/core/modules/config/tests/config_test_extension_test/config_test_extension_test.info @@ -0,0 +1,6 @@ +name = config_test extension test module +description = Provides default configuration in the config_test namespace. +package = Core +version = VERSION +core = 8.x +hidden = TRUE diff --git a/core/modules/config/tests/config_test_extension_test/config_test_extension_test.module b/core/modules/config/tests/config_test_extension_test/config_test_extension_test.module new file mode 100644 index 0000000..c471336 --- /dev/null +++ b/core/modules/config/tests/config_test_extension_test/config_test_extension_test.module @@ -0,0 +1,8 @@ +