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);
+ }
+ }
+
}
}