diff --git a/core/includes/update.inc b/core/includes/update.inc index 9e3a7a4e3445d2c64ba724942480575351131f5e..5254743c4fa10dab41977534db7a409df19b4c7c 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -320,14 +320,6 @@ function update_get_update_list() { // Otherwise, get the list of updates defined by this module. $updates = drupal_get_schema_versions($module); if ($updates !== FALSE) { - // \Drupal::moduleHandler()->invoke() returns NULL for non-existing hooks, - // so if no updates are removed, it will == 0. - $last_removed = \Drupal::moduleHandler()->invoke($module, 'update_last_removed'); - if ($schema_version < $last_removed) { - $ret[$module]['warning'] = '<em>' . $module . '</em> module cannot be updated. Its schema version is ' . $schema_version . '. Updates up to and including ' . $last_removed . ' have been removed in this release. In order to update <em>' . $module . '</em> module, you will first <a href="https://www.drupal.org/upgrade">need to upgrade</a> to the last version in which these updates were available.'; - continue; - } - foreach ($updates as $update) { if ($update == \Drupal::CORE_MINIMUM_SCHEMA_VERSION) { $ret[$module]['warning'] = '<em>' . $module . '</em> module cannot be updated. It contains an update numbered as ' . \Drupal::CORE_MINIMUM_SCHEMA_VERSION . ' which is reserved for the earliest installation of a module in Drupal ' . \Drupal::CORE_COMPATIBILITY . ', before any updates. In order to update <em>' . $module . '</em> module, you will need to install a version of the module with valid updates.'; diff --git a/core/modules/rest/tests/fixtures/update/rest-module-installed.php b/core/modules/rest/tests/fixtures/update/rest-module-installed.php index d9714e393fc28dc47e79427a0368e8091c3c0c1a..ae54885d0ae278763e91f62cbf37dd1ceed790f9 100644 --- a/core/modules/rest/tests/fixtures/update/rest-module-installed.php +++ b/core/modules/rest/tests/fixtures/update/rest-module-installed.php @@ -17,6 +17,13 @@ 'value' => 'i:8401;', ]) ->execute(); +$connection->insert('key_value') + ->fields([ + 'collection' => 'system.schema', + 'name' => 'serialization', + 'value' => 'i:8401;', + ]) + ->execute(); // Update core.extension. $extensions = $connection->select('config') diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 5903461c9a2ad5859b971efdaec3265f28953308..a1ef14e53cce1fac59a65f018c7e858096cd6fdf 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -1046,6 +1046,54 @@ function system_requirements($phase) { } } + // Ensure that no module has a current schema version that is lower than the + // one that was last removed. + if ($phase == 'update') { + $module_handler = \Drupal::moduleHandler(); + $module_list = []; + foreach ($module_handler->getImplementations('update_last_removed') as $module) { + $last_removed = $module_handler->invoke($module, 'update_last_removed'); + if ($last_removed && $last_removed > drupal_get_installed_schema_version($module)) { + + /** @var \Drupal\Core\Extension\Extension $module_info */ + $module_info = \Drupal::service('extension.list.module')->get($module); + $module_list[$module] = [ + 'name' => $module_info->info['name'], + 'last_removed' => $last_removed, + 'installed_version' => drupal_get_installed_schema_version($module), + ]; + } + } + + // If system.module is in the list then only show a specific message for + // Drupal core, otherwise show a more generic message for each module. + if (isset($module_list['system'])) { + $requirements['system_update_last_removed'] = [ + 'title' => t('The version of Drupal you are trying to update from is too old'), + 'description' => t('Updating to Drupal @current_major is only supported from Drupal version @required_min_version or higher. If you are trying to update from an older version, first update to the latest version of Drupal @previous_major. (<a href=":url">Drupal 9 upgrade guide</a>)', [ + '@current_major' => 9, + '@required_min_version' => '8.8.0', + '@previous_major' => 8, + ':url' => 'https://www.drupal.org/docs/9/how-to-prepare-your-drupal-7-or-8-site-for-drupal-9/upgrading-a-drupal-8-site-to-drupal-9', + ]), + 'severity' => REQUIREMENT_ERROR, + ]; + } + else { + foreach ($module_list as $module => $data) { + $requirements[$module . '_update_last_removed'] = [ + 'title' => t('Unsupported schema version: @module', ['@module' => $data['name']]), + 'description' => t('The installed version of the %module module is too old to update. Update to an intermediate version first (last removed version: @last_removed_version, installed version: @installed_version).', [ + '%module' => $data['name'], + '@last_removed_version' => $data['last_removed'], + '@installed_version' => $data['installed_version'], + ]), + 'severity' => REQUIREMENT_ERROR, + ]; + } + } + } + return $requirements; } @@ -1263,11 +1311,3 @@ function system_update_8901() { } } } - -/** - * Ensures that Drupal is updated from a supported version. - */ -function system_update_9000() { - // See update_get_update_list(), there needs to be at least one update - // function to check for the last removed schema version. -} diff --git a/core/modules/system/tests/modules/update_test_last_removed/update_test_last_removed.info.yml b/core/modules/system/tests/modules/update_test_last_removed/update_test_last_removed.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..f4828a7807347361c85cff28b887f38a90dc9b72 --- /dev/null +++ b/core/modules/system/tests/modules/update_test_last_removed/update_test_last_removed.info.yml @@ -0,0 +1,5 @@ +name: 'Update test with update_last_removed implementation' +type: module +description: 'Support module for update testing.' +package: Testing +version: VERSION diff --git a/core/modules/system/tests/modules/update_test_last_removed/update_test_last_removed.install b/core/modules/system/tests/modules/update_test_last_removed/update_test_last_removed.install new file mode 100644 index 0000000000000000000000000000000000000000..7b9700fca0ec3a2ebeb2540d25804b43f8a4dc22 --- /dev/null +++ b/core/modules/system/tests/modules/update_test_last_removed/update_test_last_removed.install @@ -0,0 +1,20 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for the update_test_invalid_hook module. + */ + +/** + * Implements hook_update_last_removed() + */ +function update_test_last_removed_update_last_removed() { + return 8002; +} + +/** + * Dummy update function to run during the tests. + */ +function update_test_last_removed_update_8003() { + return 'The update_test_last_removed_update_8003() update was executed successfully.'; +} diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathLastRemovedTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathLastRemovedTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fc34b6adb9129e7b7b50e478262b9caa1950edcd --- /dev/null +++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathLastRemovedTest.php @@ -0,0 +1,88 @@ +<?php + +namespace Drupal\Tests\system\Functional\UpdateSystem; + +use Drupal\Core\Url; +use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\UpdatePathTestTrait; + +/** + * Modules can define their last removed update function. + * + * @group system + */ +class UpdatePathLastRemovedTest extends BrowserTestBase { + use UpdatePathTestTrait; + + /** + * {@inheritdoc} + */ + protected static $modules = ['update_test_last_removed']; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * URL for the upgrade script. + * + * @var string + */ + protected $updateUrl; + + /** + * A user account with upgrade permission. + * + * @var \Drupal\user\UserInterface + */ + protected $updateUser; + + protected function setUp() { + parent::setUp(); + require_once $this->root . '/core/includes/update.inc'; + + $this->updateUrl = Url::fromRoute('system.db_update'); + $this->updateUser = $this->drupalCreateUser(['administer software updates']); + } + + /** + * Test that a module with a too old schema version can not be updated. + */ + public function testLastRemovedVersion() { + drupal_set_installed_schema_version('update_test_last_removed', 8000); + drupal_set_installed_schema_version('system', 8804); + + // Access the update page with too old versions for system and the test + // module, only the generic core message should be shown. + $this->drupalLogin($this->updateUser); + $this->drupalGet($this->updateUrl); + $assert_session = $this->assertSession(); + $assert_session->pageTextContains('Requirements problem'); + $assert_session->pageTextContains('The version of Drupal you are trying to update from is too old'); + $assert_session->pageTextContains('Updating to Drupal 9 is only supported from Drupal version 8.8.0 or higher. If you are trying to update from an older version, first update to the latest version of Drupal 8'); + $assert_session->pageTextNotContains('Unsupported schema version: Update test with update_last_removed implementation'); + + $assert_session->linkNotExists('Continue'); + + // Update the installed version of system and then assert that now, + // the test module is shown instead. + drupal_set_installed_schema_version('system', 8805); + $this->drupalGet($this->updateUrl); + + $assert_session->pageTextNotContains('The version of Drupal you are trying to update from is too old'); + + $assert_session->pageTextContains('Unsupported schema version: Update test with update_last_removed implementation'); + $assert_session->pageTextContains('The installed version of the Update test with update_last_removed implementation module is too old to update. Update to an intermediate version first (last removed version: 8002, installed version: 8000).'); + $assert_session->linkNotExists('Continue'); + + // Set the expected schema version for the node and test module, updates are + // successful now. + drupal_set_installed_schema_version('update_test_last_removed', 8002); + + $this->runUpdates(); + $this->assertEquals(8003, drupal_get_installed_schema_version('update_test_last_removed')); + + } + +} diff --git a/core/tests/Drupal/Tests/UpdatePathTestTrait.php b/core/tests/Drupal/Tests/UpdatePathTestTrait.php index 61facab0bd2659dfe472e0dd907c448fcefe3994..8c3fc723d50439f056613d165f0a23e78281203f 100644 --- a/core/tests/Drupal/Tests/UpdatePathTestTrait.php +++ b/core/tests/Drupal/Tests/UpdatePathTestTrait.php @@ -59,6 +59,7 @@ protected function runUpdates($update_url = NULL) { } // Ensure that there are no pending updates. + drupal_get_installed_schema_version(NULL, TRUE); foreach (['update', 'post_update'] as $update_type) { switch ($update_type) { case 'update':