diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php index b5fddb1..b44c561 100644 --- a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php @@ -72,14 +72,33 @@ public function onConfigImporterValidate(ConfigImporterEvent $event) { } } $config_importer = $event->getConfigImporter(); - if ($config_importer->getStorageComparer()->getSourceStorage()->exists('core.extension')) { + if ($this->validateCoreExtension($config_importer)) { $this->validateModules($config_importer); $this->validateThemes($config_importer); $this->validateDependencies($config_importer); } - else { + } + + /** + * Validates the core.extension configuration file. + * + * @param \Drupal\Core\Config\ConfigImporter $config_importer + * The configuration importer. + * + * @return bool + * TRUE if the core.extension is valid, FALSE if not. + */ + protected function validateCoreExtension(ConfigImporter $config_importer) { + $data = $config_importer->getStorageComparer()->getSourceStorage()->read('core.extension'); + if ($data === FALSE) { $config_importer->logError($this->t('The core.extension configuration does not exist.')); + return FALSE; + } + elseif (empty($data['versions'])) { + $config_importer->logError($this->t('The core.extension file is out-of-date. Update the codebase of the source site, run update.php and export the configuration again.')); + return FALSE; } + return TRUE; } /** @@ -97,6 +116,23 @@ protected function validateModules(ConfigImporter $config_importer) { $config_importer->logError($this->t('Unable to install the %module module since it does not exist.', array('%module' => $module))); } + $existing_modules = array_keys(array_intersect_key($core_extension['module'], $config_importer->getStorageComparer()->getTargetStorage()->read('core.extension')['module'])); + foreach ($existing_modules as $module) { + if ($core_extension['versions'][$module]['current'] !== $module_data[$module]->info['version']) { + $config_importer->logError($this->t( + 'Unable to import configuration because the version of %module module on the source site (@source_version) does not match this site (@target_version).', + ['%module' => $module, '@source_version' => $core_extension['versions'][$module]['current'], '@target_version' => $module_data[$module]->info['version']] + )); + } + elseif ($core_extension['versions'][$module]['schema'] !== (string) drupal_get_installed_schema_version($module)) { + $config_importer->logError($this->t( + 'Unable to import configuration because the schema version of %module module on the source site (@source_schema) does not match this site (@target_schema).', + ['%module' => $module, '@source_schema' => $core_extension['versions'][$module]['schema'], '@target_schema' => drupal_get_installed_schema_version($module)] + )); + + } + } + // Ensure that all modules being installed have their dependencies met. $installs = $config_importer->getExtensionChangelist('module', 'install'); foreach ($installs as $module) { @@ -166,6 +202,16 @@ protected function validateThemes(ConfigImporter $config_importer) { } } + $existing_themes = array_keys(array_intersect_key($core_extension['theme'], $config_importer->getStorageComparer()->getTargetStorage()->read('core.extension')['theme'])); + foreach ($existing_themes as $theme) { + if ($core_extension['versions'][$theme]['current'] !== $theme_data[$theme]->info['version']) { + $config_importer->logError($this->t( + 'Unable to import configuration because the version of %theme theme on the source site (@source_version) does not match this site (@target_version).', + ['%theme' => $theme, '@source_version' => $core_extension['versions'][$theme]['current'], '@target_version' => $theme_data[$theme]->info['version']] + )); + } + } + // Ensure that all themes being installed have their dependencies met. foreach ($installs as $theme) { foreach (array_keys($theme_data[$theme]->requires) as $required_theme) { diff --git a/core/modules/config/src/Tests/ConfigImportUITest.php b/core/modules/config/src/Tests/ConfigImportUITest.php index c98915c..85529c1 100644 --- a/core/modules/config/src/Tests/ConfigImportUITest.php +++ b/core/modules/config/src/Tests/ConfigImportUITest.php @@ -523,6 +523,9 @@ public function testExtensionValidation() { // This theme does exist but the version is wrong. $core['theme']['seven'] = 0; $core['versions']['seven'] = ['current' => '8.0.0-rc3', 'install' => '8.0.0-rc3']; + $core['versions']['bartik']['current'] = '8.0.0-rc3'; + $core['versions']['text']['current'] = '8.0.0-rc3'; + $core['versions']['config_test']['schema'] = '8999'; $sync->write('core.extension', $core); @@ -534,6 +537,8 @@ public function testExtensionValidation() { $this->assertText('Unable to install the does_not_exist theme since it does not exist.'); $this->assertText('Unable to install the ban module since the installed version (8.0.0-rc3) does not match the code base (' . \Drupal::VERSION . ').'); $this->assertText('Unable to install the seven theme since the installed version (8.0.0-rc3) does not match the code base (' . \Drupal::VERSION . ').'); + $this->assertText('Unable to import configuration because the schema version of config_test module on the source site (8999) does not match this site (' . drupal_get_installed_schema_version('config_test') . ').'); + $this->assertText('Unable to import configuration because the version of bartik theme on the source site (8.0.0-rc3) does not match this site (' . \Drupal::VERSION . ').'); } } diff --git a/core/modules/config/src/Tests/ConfigImporterTest.php b/core/modules/config/src/Tests/ConfigImporterTest.php index e62a777..1f5724d 100644 --- a/core/modules/config/src/Tests/ConfigImporterTest.php +++ b/core/modules/config/src/Tests/ConfigImporterTest.php @@ -651,6 +651,58 @@ public function testMissingCoreExtension() { } /** + * Tests missing core.extension:versions during configuration import. + * + * Configuration exported before the versions information was added should + * fail. + * + * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber + */ + public function testMissingVersionInformation() { + $sync = $this->container->get('config.storage.sync'); + $data = $sync->read('core.extension'); + unset($data['versions']); + $sync->write('core.extension', $data); + try { + $this->configImporter->reset()->import(); + $this->fail('ConfigImporterException not thrown, invalid import was not stopped due to missing version information.'); + } + catch (ConfigImporterException $e) { + $this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.'); + $error_log = $this->configImporter->getErrors(); + $this->assertEqual(['The core.extension file is out-of-date. Update the codebase of the source site, run update.php and export the configuration again.'], $error_log); + } + } + + /** + * Tests missing core.extension:versions during configuration import. + * + * Configuration exported before the versions information was added should + * fail. + * + * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber + */ + public function testVersionMismatch() { + $sync = $this->container->get('config.storage.sync'); + $data = $sync->read('core.extension'); + $data['versions']['system']['current'] = '8.0.0-beta1'; + $data['versions']['config_test']['schema'] = '8999'; + $sync->write('core.extension', $data); + try { + $this->configImporter->reset()->import(); + $this->fail('ConfigImporterException not thrown, invalid import was not stopped due to missing version information.'); + } + catch (ConfigImporterException $e) { + $this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.'); + $error_log = $this->configImporter->getErrors(); + $this->assertEqual([ + 'Unable to import configuration because the schema version of config_test module on the source site (8999) does not match this site (-1).', + 'Unable to import configuration because the version of system module on the source site (8.0.0-beta1) does not match this site (8.0.0-dev).' + ], $error_log); + } + } + + /** * Tests install profile validation during configuration import. * * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php index a3d17c6..3f686e7 100644 --- a/core/modules/simpletest/src/KernelTestBase.php +++ b/core/modules/simpletest/src/KernelTestBase.php @@ -522,7 +522,7 @@ protected function enableModules(array $modules) { 'current' => $version, 'install' => $version, // @todo How to get schema - or should installSchema do this? - 'schema' => NULL, + 'schema' => SCHEMA_UNINSTALLED, ]; } $active_storage->write('core.extension', $extensions); diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php index fa05e4d..0149613 100644 --- a/core/tests/Drupal/KernelTests/KernelTestBase.php +++ b/core/tests/Drupal/KernelTests/KernelTestBase.php @@ -835,7 +835,7 @@ protected function enableModules(array $modules) { 'current' => $version, 'install' => $version, // @todo How to get schema - or should installSchema do this? - 'schema' => NULL, + 'schema' => SCHEMA_UNINSTALLED, ]; } $active_storage->write('core.extension', $extension_config);