diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php index d3023cc..f05a46f 100644 --- a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php @@ -20,6 +20,20 @@ class ConfigImportSubscriber extends ConfigImportValidateEventSubscriberBase { /** + * Theme data. + * + * @var \Drupal\Core\Extension\Extension[] + */ + protected $themeData; + + /** + * Module data. + * + * @var \Drupal\Core\Extension\Extension[] + */ + protected $moduleData; + + /** * The theme handler. * * @var \Drupal\Core\Extension\ThemeHandlerInterface @@ -76,7 +90,7 @@ public function onConfigImporterValidate(ConfigImporterEvent $event) { protected function validateModules(ConfigImporter $config_importer) { $core_extension = $config_importer->getStorageComparer()->getSourceStorage()->read('core.extension'); // Get a list of modules with dependency weights as values. - $module_data = system_rebuild_module_data(); + $module_data = $this->getModuleData(); $nonexistent_modules = array_keys(array_diff_key($core_extension['module'], $module_data)); foreach ($nonexistent_modules as $module) { $config_importer->logError($this->t('Unable to install module %module since it does not exist.', array('%module' => $module))); @@ -103,7 +117,7 @@ protected function validateModules(ConfigImporter $config_importer) { */ protected function validateThemes(ConfigImporter $config_importer) { // Get all themes including those that are not installed. - $theme_data = $this->themeHandler->rebuildThemeData(); + $theme_data = $this->getThemeData(); foreach ($config_importer->getExtensionChangelist('theme', 'install') as $theme) { if (!isset($theme_data[$theme])) { $config_importer->logError($this->t('Unable to install theme %theme since it does not exist.', array('%theme' => $theme))); @@ -136,6 +150,9 @@ protected function validateDependencies(ConfigImporter $config_importer) { 'theme' => array_keys($core_extension['theme']), ]; + $theme_data = $this->getThemeData(); + $module_data = $this->getModuleData(); + // Validate the dependencies of all the configuration. We have to validate // the entire tree because existing configuration might depend on // configuration that is being deleted. @@ -143,9 +160,31 @@ protected function validateDependencies(ConfigImporter $config_importer) { // Ensure that the config owner is installed. This checks all // configuration including configuration entities. list($owner,) = explode('.', $name, 2); - if ($owner !== 'core' && !isset($core_extension['module'][$owner]) && !isset($core_extension['theme'][$owner])) { - $config_importer->logError($this->t('Configuration %name depends on the %owner extension that will not be installed after import.', array('%name' => $name, '%owner' => $owner))); - continue; + if ($owner !== 'core') { + $message = FALSE; + if (!isset($core_extension['module'][$owner]) && isset($module_data[$owner])) { + $message = $this->t('Configuration %name depends on the %owner module that will not be installed after import.', array( + '%name' => $name, + '%owner' => $module_data[$owner]->info['name'] + )); + } + elseif (!isset($core_extension['theme'][$owner]) && isset($theme_data[$owner])) { + $message = $this->t('Configuration %name depends on the %owner theme that will not be installed after import.', array( + '%name' => $name, + '%owner' => $theme_data[$owner]->info['name'] + )); + } + elseif (!isset($core_extension['module'][$owner]) && !isset($core_extension['theme'][$owner])) { + $message = $this->t('Configuration %name depends on the %owner extension that will not be installed after import.', array( + '%name' => $name, + '%owner' => $owner + )); + } + + if ($message) { + $config_importer->logError($message); + continue; + } } $data = $config_importer->getStorageComparer()->getSourceStorage()->read($name); @@ -156,22 +195,86 @@ protected function validateDependencies(ConfigImporter $config_importer) { foreach ($dependencies_to_check as $type => $dependencies) { $diffs = array_diff($dependencies, $existing_dependencies[$type]); if (!empty($diffs)) { + $message = FALSE; switch ($type) { case 'module': - $config_importer->logError($this->t('Configuration %name depends on a module that will not be installed after import.', array('%name' => $name))); + $message = $this->formatPlural( + count($diffs), + 'Configuration %name depends on the %module module that will not be installed after import.', + 'Configuration %name depends on modules (%module) that will not be installed after import.', + array('%name' => $name, '%module' => implode(', ', $this->getNames($diffs, $module_data))) + ); break; case 'theme': - $config_importer->logError($this->t('Configuration %name depends on a theme that will not be installed after import.', array('%name' => $name))); + $message = $this->formatPlural( + count($diffs), + 'Configuration %name depends on the %theme theme that will not be installed after import.', + 'Configuration %name depends on themes (%theme) that will not be installed after import.', + array('%name' => $name, '%theme' => implode(', ', $this->getNames($diffs, $theme_data))) + ); break; case 'config': - $config_importer->logError($this->t('Configuration %name depends on configuration that will not exist after import.', array('%name' => $name))); + $message = $this->formatPlural( + count($diffs), + 'Configuration %name depends on the %config configuration that will not exist after import.', + 'Configuration %name depends on configuration (%config) that will not exist after import.', + array('%name' => $name, '%config' => implode(', ', $diffs)) + ); break; } + + if ($message) { + $config_importer->logError($message); + } } } } } + } + + /** + * Gets theme data. + * + * @return \Drupal\Core\Extension\Extension[] + */ + protected function getThemeData() { + if (!isset($this->themeData)) { + $this->themeData = $this->themeHandler->rebuildThemeData(); + } + return $this->themeData; + } + /** + * Gets module data. + * + * @return \Drupal\Core\Extension\Extension[] + */ + protected function getModuleData() { + if (!isset($this->moduleData)) { + $this->moduleData = system_rebuild_module_data(); + } + return $this->moduleData; + } + + /** + * Gets human readable extension names. + * + * @param array $names + * A list of extension machine names. + * @param \Drupal\Core\Extension\Extension[] $extension_data + * Extension data. + * + * @return array + * A list of human readable extension names or machine names if not + * available. + */ + protected function getNames(array $names, array $extension_data) { + return array_map(function ($name) use ($extension_data) { + if (isset($extension_data[$name])) { + $name = $extension_data[$name]->info['name']; + } + return $name; + }, $names); } } diff --git a/core/modules/config/src/Tests/ConfigImporterTest.php b/core/modules/config/src/Tests/ConfigImporterTest.php index f1c16a6..563ba4f 100644 --- a/core/modules/config/src/Tests/ConfigImporterTest.php +++ b/core/modules/config/src/Tests/ConfigImporterTest.php @@ -586,16 +586,42 @@ public function testUnmetDependency() { $expected = [ 'Unable to install module unknown_module since it does not exist.', 'Unable to install theme unknown_theme since it does not exist.', - 'Configuration config_test.dynamic.dotted.config depends on configuration that will not exist after import.', - 'Configuration config_test.dynamic.dotted.existing depends on configuration that will not exist after import.', - 'Configuration config_test.dynamic.dotted.module depends on a module that will not be installed after import.', - 'Configuration config_test.dynamic.dotted.theme depends on a theme that will not be installed after import.', + 'Configuration config_test.dynamic.dotted.config depends on the unknown configuration that will not exist after import.', + 'Configuration config_test.dynamic.dotted.existing depends on the config_test.dynamic.dotted.deleted configuration that will not exist after import.', + 'Configuration config_test.dynamic.dotted.module depends on the unknown module that will not be installed after import.', + 'Configuration config_test.dynamic.dotted.theme depends on the unknown theme that will not be installed after import.', 'Configuration unknown.config depends on the unknown extension that will not be installed after import.' ]; foreach ($expected as $expected_message) { $this->assertTrue(in_array($expected_message, $error_log), $expected_message); } } + + // Make a config entity have mulitple unmet dependencies. + $config_entity_data = $staging->read('config_test.dynamic.dotted.default'); + $config_entity_data['dependencies'] = ['module' => ['unknown', 'dblog']]; + $staging->write('config_test.dynamic.dotted.module', $config_entity_data); + $config_entity_data['dependencies'] = ['theme' => ['unknown', 'seven']]; + $staging->write('config_test.dynamic.dotted.theme', $config_entity_data); + $config_entity_data['dependencies'] = ['config' => ['unknown', 'unknown2']]; + $staging->write('config_test.dynamic.dotted.config', $config_entity_data); + try { + $this->configImporter->reset()->import(); + $this->fail('ConfigImporterException not thrown, invalid import was not stopped due to missing dependencies.'); + } + catch (ConfigImporterException $e) { + $this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.'); + $error_log = $this->configImporter->getErrors(); + $expected = [ + 'Configuration config_test.dynamic.dotted.config depends on configuration (unknown, unknown2) that will not exist after import.', + 'Configuration config_test.dynamic.dotted.module depends on modules (unknown, Database Logging) that will not be installed after import.', + 'Configuration config_test.dynamic.dotted.theme depends on themes (unknown, Seven) that will not be installed after import.', + ]; + foreach ($expected as $expected_message) { + $this->assertTrue(in_array($expected_message, $error_log), $expected_message); + } + } + } }