Commit 5d95f2e1 authored by catch's avatar catch

Issue #3136668 by dww, dawehner, pavnish, catch, daffie, alexpott, xjm:...

Issue #3136668 by dww, dawehner, pavnish, catch, daffie, alexpott, xjm: Invalid system.schema key_value entry causes fatal on updating to 8.8.5
parent a6eaea1b
......@@ -9,6 +9,7 @@
*/
use Drupal\Component\Graph\Graph;
use Drupal\Core\Extension\Exception\UnknownExtensionException;
use Drupal\Core\Utility\Error;
/**
......@@ -340,11 +341,38 @@ function update_get_update_list() {
$modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE);
/** @var \Drupal\Core\Extension\ExtensionList $extension_list */
$extension_list = \Drupal::service('extension.list.module');
/** @var array $installed_module_info */
$installed_module_info = $extension_list->getAllInstalledInfo();
foreach ($modules as $module => $schema_version) {
// Skip uninstalled and incompatible modules.
if ($schema_version == SCHEMA_UNINSTALLED || $extension_list->checkIncompatibility($module)) {
try {
if ($schema_version == SCHEMA_UNINSTALLED || $extension_list->checkIncompatibility($module)) {
continue;
}
}
// It is possible that the system schema has orphaned entries, so the
// incompatibility checking might throw an exception.
catch (UnknownExtensionException $e) {
$args = [
'%name' => $module,
':url' => 'https://www.drupal.org/node/3137656',
];
\Drupal::messenger()->addWarning(t('Module %name has an entry in the system.schema key/value storage, but is missing from your site. <a href=":url">More information about this error</a>.', $args));
\Drupal::logger('system')->notice('Module %name has an entry in the system.schema key/value storage, but is missing from your site. <a href=":url">More information about this error</a>.', $args);
continue;
}
// There might be orphaned entries for modules that are in the filesystem
// but not installed. Also skip those, but warn site admins about it.
if (empty($installed_module_info[$module])) {
$args = [
'%name' => $module,
':url' => 'https://www.drupal.org/node/3137656',
];
\Drupal::messenger()->addWarning(t('Module %name has an entry in the system.schema key/value storage, but is not installed. <a href=":url">More information about this error</a>.', $args));
\Drupal::logger('system')->notice('Module %name has an entry in the system.schema key/value storage, but is not installed. <a href=":url">More information about this error</a>.', $args);
continue;
}
// Display a requirements error if the user somehow has a schema version
// from the previous Drupal major version.
if ($schema_version < \Drupal::CORE_MINIMUM_SCHEMA_VERSION) {
......@@ -631,10 +659,19 @@ function update_already_performed($module, $number) {
*/
function update_retrieve_dependencies() {
$return = [];
/** @var \Drupal\Core\Extension\ModuleExtensionList */
$extension_list = \Drupal::service('extension.list.module');
// Get a list of installed modules, arranged so that we invoke their hooks in
// the same order that \Drupal::moduleHandler()->invokeAll() does.
foreach (\Drupal::keyValue('system.schema')->getAll() as $module => $schema) {
if ($schema == SCHEMA_UNINSTALLED) {
// Skip modules that are entirely missing from the filesystem here, since
// module_load_install() will call trigger_error() if invoked on a module
// that doesn't exist. There's no way to catch() that, so avoid it entirely.
// This can happen when there are orphaned entries in the system.schema k/v
// store for modules that have been removed from a site without first being
// cleanly uninstalled. We don't care here if the module has been installed
// or not, since we'll filter those out in update_get_update_list().
if ($schema == SCHEMA_UNINSTALLED || !$extension_list->exists($module)) {
// Nothing to upgrade.
continue;
}
......
......@@ -400,6 +400,68 @@ public function testMissingExtension($extension_type) {
$this->assertUpdateWithNoError($test_error_text, $extension_type, $extension_machine_name);
}
/**
* Tests that orphan schemas are handled properly.
*/
public function testOrphanedSchemaEntries() {
$this->drupalLogin($this->updateUser);
// Insert a bogus value into the system.schema key/value storage for a
// nonexistent module. This replicates what would happen if you had a module
// installed and then completely remove it from the filesystem and clear it
// out of the core.extension config list without uninstalling it cleanly.
\Drupal::keyValue('system.schema')->set('my_already_removed_module', 8000);
// Visit update.php and make sure we can click through to the 'No pending
// updates' page without errors.
$assert_session = $this->assertSession();
$this->drupalGet($this->updateUrl, ['external' => TRUE]);
$this->updateRequirementsProblem();
$this->clickLink(t('Continue'));
// Make sure there are no pending updates (or uncaught exceptions).
$status_messages = $this->xpath('//div[@aria-label="Status message"]');
$this->assertCount(1, $status_messages);
$this->assertStringContainsString('No pending updates.', $status_messages[0]->getText());
// Verify that we warn the admin about this situation.
$warning_messages = $this->xpath('//div[@aria-label="Warning message"]');
$this->assertCount(1, $warning_messages);
$this->assertEquals('Warning message Module my_already_removed_module has an entry in the system.schema key/value storage, but is missing from your site. More information about this error.', $warning_messages[0]->getText());
// Try again with another orphaned entry, this time for a test module that
// does exist in the filesystem.
\Drupal::keyValue('system.schema')->delete('my_already_removed_module');
\Drupal::keyValue('system.schema')->set('update_test_0', 8000);
$this->drupalGet($this->updateUrl, ['external' => TRUE]);
$this->updateRequirementsProblem();
$this->clickLink(t('Continue'));
// There should not be any pending updates.
$status_messages = $this->xpath('//div[@aria-label="Status message"]');
$this->assertCount(1, $status_messages);
$this->assertStringContainsString('No pending updates.', $status_messages[0]->getText());
// But verify that we warn the admin about this situation.
$warning_messages = $this->xpath('//div[@aria-label="Warning message"]');
$this->assertCount(1, $warning_messages);
$this->assertEquals('Warning message Module update_test_0 has an entry in the system.schema key/value storage, but is not installed. More information about this error.', $warning_messages[0]->getText());
// Finally, try with both kinds of orphans and make sure we get both warnings.
\Drupal::keyValue('system.schema')->set('my_already_removed_module', 8000);
$this->drupalGet($this->updateUrl, ['external' => TRUE]);
$this->updateRequirementsProblem();
$this->clickLink(t('Continue'));
// There still should not be any pending updates.
$status_messages = $this->xpath('//div[@aria-label="Status message"]');
$this->assertCount(1, $status_messages);
$this->assertStringContainsString('No pending updates.', $status_messages[0]->getText());
// Verify that we warn the admin about both orphaned entries.
$warning_messages = $this->xpath('//div[@aria-label="Warning message"]');
$this->assertCount(1, $warning_messages);
$warning_message_text = $warning_messages[0]->getText();
$this->assertStringContainsString('Module update_test_0 has an entry in the system.schema key/value storage, but is not installed. More information about this error.', $warning_message_text);
$this->assertStringNotContainsString('Module update_test_0 has an entry in the system.schema key/value storage, but is missing from your site.', $warning_message_text);
$this->assertStringContainsString('Module my_already_removed_module has an entry in the system.schema key/value storage, but is missing from your site. More information about this error.', $warning_message_text);
$this->assertStringNotContainsString('Module my_already_removed_module has an entry in the system.schema key/value storage, but is not installed.', $warning_message_text);
}
/**
* Data provider for testMissingExtension().
*/
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment