diff --git a/core/lib/Drupal/Core/Update/UpdateRegistryTest.php b/core/lib/Drupal/Core/Update/UpdateRegistryTest.php new file mode 100644 index 0000000..a62b12d --- /dev/null +++ b/core/lib/Drupal/Core/Update/UpdateRegistryTest.php @@ -0,0 +1,45 @@ +routerBuilder = $router_builder; + $this->moduleHandler = $module_handler; + $this->postUpdateRegistry = $post_update_registry; } /** @@ -78,6 +99,52 @@ public function onConfigImporterValidateSiteUUID(ConfigImporterEvent $event) { } /** + * Checks for any pending database updates. + * + * As pending database update can cause issues as they potentially make + * changes code base and database. + * + * @see https://www.drupal.org/node/2628144 + * + * @param ConfigImporterEvent $event + * The config import event. + */ + public function onConfigImporterValidateDatabaseUpdate(ConfigImporterEvent $event) { + if ($this->hasModuleUpdates()) { + $event->getConfigImporter()->logError($this->t('Pending database updates available.')); + } + } + + /** + * Checks if there any pending database updates. + * + * @return bool + * TRUE, if there any pending update on modules, FALSE otherwise. + */ + protected function hasModuleUpdates() { + // Check installed modules. + $has_pending_updates = FALSE; + foreach ($this->moduleHandler->getModuleList() as $module => $filename) { + $updates = drupal_get_schema_versions($module); + if ($updates !== FALSE) { + $default = drupal_get_installed_schema_version($module); + if (max($updates) > $default) { + $has_pending_updates = TRUE; + break; + } + } + } + if (!$has_pending_updates) { + $missing_post_update_functions = $this->postUpdateRegistry->getPendingUpdateFunctions(); + if (!empty($missing_post_update_functions)) { + $has_pending_updates = TRUE; + } + } + + return $has_pending_updates; + } + + /** * {@inheritdoc} */ public static function getSubscribedEvents() { @@ -86,6 +153,7 @@ public static function getSubscribedEvents() { // there is no configuration to import. $events[ConfigEvents::IMPORT_VALIDATE][] = ['onConfigImporterValidateNotEmpty', 512]; $events[ConfigEvents::IMPORT_VALIDATE][] = ['onConfigImporterValidateSiteUUID', 256]; + $events[ConfigEvents::IMPORT_VALIDATE][] = ['onConfigImporterValidateDatabaseUpdate', 255]; return $events; } diff --git a/core/modules/system/src/Tests/Update/UpdatePendingConfigImportTest.php b/core/modules/system/src/Tests/Update/UpdatePendingConfigImportTest.php new file mode 100644 index 0000000..4047991 --- /dev/null +++ b/core/modules/system/src/Tests/Update/UpdatePendingConfigImportTest.php @@ -0,0 +1,77 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../tests/fixtures/update/drupal-8.bare.standard.php.gz', + __DIR__ . '/../../../tests/fixtures/update/drupal-8.update-test-config-import.php', + ]; + } + + /** + * {@inheritdoc} + */ + protected function doSelectionTest() { + parent::doSelectionTest(); + $this->assertRaw('8001 - Pending update.'); + } + + /** + * Tests database update with configuration import. + */ + public function testConfigImport() { + + // Add 'profile' key so we don't get notices. + \Drupal::configFactory()->getEditable('core.extension')->set('profile', \Drupal::installProfile())->save(); + // Export active config to sync. + $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync')); + // Get site name. + $site_name = \Drupal::config('system.site')->get('name'); + $new_site_name = 'Overridden site name.'; + // Change site name. + \Drupal::configFactory()->getEditable('system.site')->set('name', $new_site_name)->save(); + + try { + // Try running config import with pending. + $this->configImporter()->import(); + } + catch (ConfigImporterException $e) { + $this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.'); + $error_log = $this->configImporter->getErrors(); + $expected = array('Pending database updates available.'); + $this->assertEqual($expected, $error_log); + } + + // Run database updates. + $this->runUpdates(); + $this->drupalGet('update.php/selection'); + $this->assertText('No pending updates.'); + + // Check site name. + $this->assertEqual($new_site_name, \Drupal::config('system.site')->get('name')); + + // Use the UI to import (for some reason I can't get the config importer to + // get rid of it's error log of pending database updates, and that error log + // is probably stuck somewhere deeper). + $this->drupalLogin($this->rootUser); + $this->drupalGet('admin/config/development/configuration'); + $this->drupalPostForm(NULL, [], 'Import all'); + + // Site name should be reverted again. + $this->assertEqual($site_name, \Drupal::config('system.site')->get('name')); + } + +} diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml index c70889d..0c4e23a 100644 --- a/core/modules/system/system.services.yml +++ b/core/modules/system/system.services.yml @@ -35,7 +35,7 @@ services: - { name: theme_negotiator, priority: 100 } system.config_subscriber: class: Drupal\system\SystemConfigSubscriber - arguments: ['@router.builder'] + arguments: ['@router.builder', '@module_handler', '@update.post_update_registry'] tags: - { name: event_subscriber } system.config_cache_tag: diff --git a/core/modules/system/tests/fixtures/update/drupal-8.update-test-config-import.php b/core/modules/system/tests/fixtures/update/drupal-8.update-test-config-import.php new file mode 100644 index 0000000..7993a77 --- /dev/null +++ b/core/modules/system/tests/fixtures/update/drupal-8.update-test-config-import.php @@ -0,0 +1,40 @@ +merge('key_value') + ->condition('collection', 'system.schema') + ->condition('name', 'update_test_config_import') + ->fields([ + 'collection' => 'system.schema', + 'name' => 'update_test_config_import', + 'value' => 'i:8000;', + ]) + ->execute(); + +// Update core.extension. +$extensions = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute() + ->fetchField(); +$extensions = unserialize($extensions); +$extensions['module']['update_test_config_import'] = 8000; +$extensions['theme']['stable'] = 0; +$connection->update('config') + ->fields([ + 'data' => serialize($extensions), + ]) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute(); diff --git a/core/modules/system/tests/modules/update_test_config_import/update_test_config_import.info.yml b/core/modules/system/tests/modules/update_test_config_import/update_test_config_import.info.yml new file mode 100644 index 0000000..faf112f --- /dev/null +++ b/core/modules/system/tests/modules/update_test_config_import/update_test_config_import.info.yml @@ -0,0 +1,5 @@ +core: 8.x +name: Update test config import +type: module +package: Testing +version: VERSION diff --git a/core/modules/system/tests/modules/update_test_config_import/update_test_config_import.install b/core/modules/system/tests/modules/update_test_config_import/update_test_config_import.install new file mode 100644 index 0000000..11ec5e3 --- /dev/null +++ b/core/modules/system/tests/modules/update_test_config_import/update_test_config_import.install @@ -0,0 +1,12 @@ +addTag('event_subscriber'); } + // We don't care about post updates in the kernel test base. + $container->removeDefinition('update.post_update_registry'); + $container->register('update.post_update_registry', 'Drupal\Core\Update\UpdateRegistryTest'); + if ($container->hasDefinition('path_processor_alias')) { // Prevent the alias-based path processor, which requires a url_alias db // table, from being registered to the path processor manager. We do this @@ -805,6 +809,10 @@ protected function installSchema($module, $tables) { throw new \LogicException("$module module does not define a schema for table '$table'."); } $this->container->get('database')->schema()->createTable($table, $schema); + + // Set a very high schema version so no updates are found because we + // don't want to run them anyway. + drupal_set_installed_schema_version($module, 8888); } }