Skip to content

Commit

Permalink
Issue #3056543 by plach, jungle, Berdir, catch, xjm, amateescu: taxon…
Browse files Browse the repository at this point in the history
…omy_post_update_make_taxonomy_term_revisionable() and the menu link content equivalent fail when entities have no default translation
  • Loading branch information
catch committed Mar 11, 2020
1 parent e67272f commit b1a3f0d
Show file tree
Hide file tree
Showing 6 changed files with 536 additions and 2 deletions.
112 changes: 111 additions & 1 deletion modules/menu_link_content/menu_link_content.post_update.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,23 @@
*/

use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\Core\Site\Settings;
use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\menu_link_content\MenuLinkContentStorage;

/**
* Update custom menu links to be revisionable.
*/
function menu_link_content_post_update_make_menu_link_content_revisionable(&$sandbox) {
$finished = _menu_link_content_post_update_make_menu_link_content_revisionable__fix_default_langcode($sandbox);
if (!$finished) {
$sandbox['#finished'] = 0;
return NULL;
}

$definition_update_manager = \Drupal::entityDefinitionUpdateManager();
/** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */
$last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
Expand Down Expand Up @@ -99,5 +110,104 @@ function menu_link_content_post_update_make_menu_link_content_revisionable(&$san

$definition_update_manager->updateFieldableEntityType($entity_type, $field_storage_definitions, $sandbox);

return t('Custom menu links have been converted to be revisionable.');
if (!empty($sandbox['data_fix']['default_langcode']['processed'])) {
$count = $sandbox['data_fix']['default_langcode']['processed'];
if (\Drupal::moduleHandler()->moduleExists('dblog')) {
// @todo Simplify with https://www.drupal.org/node/2548095
$base_url = str_replace('/update.php', '', \Drupal::request()->getBaseUrl());
$args = [
':url' => Url::fromRoute('dblog.overview', [], ['query' => ['type' => ['update'], 'severity' => [RfcLogLevel::WARNING]]])
->setOption('base_url', $base_url)
->toString(TRUE)
->getGeneratedUrl(),
];
return new PluralTranslatableMarkup($count, 'Custom menu links have been converted to be revisionable. One menu link with data integrity issues was restored. More details have been <a href=":url">logged</a>.', 'Custom menu links have been converted to be revisionable. @count menu links with data integrity issues were restored. More details have been <a href=":url">logged</a>.', $args);
}
else {
return new PluralTranslatableMarkup($count, 'Custom menu links have been converted to be revisionable. One menu link with data integrity issues was restored. More details have been logged.', 'Custom menu links have been converted to be revisionable. @count menu links with data integrity issues were restored. More details have been logged.');
}
}
else {
return t('Custom menu links have been converted to be revisionable.');
}
}

/**
* Fixes recoverable data integrity issues in the "default_langcode" field.
*
* @param array $sandbox
* The update sandbox array.
*
* @return bool
* TRUE if the operation was finished, FALSE otherwise.
*
* @internal
*/
function _menu_link_content_post_update_make_menu_link_content_revisionable__fix_default_langcode(array &$sandbox) {
if (!empty($sandbox['data_fix']['default_langcode']['finished'])) {
return TRUE;
}

$storage = \Drupal::entityTypeManager()->getStorage('menu_link_content');
if (!$storage instanceof MenuLinkContentStorage) {
return TRUE;
}
elseif (!isset($sandbox['data_fix']['default_langcode']['last_id'])) {
$sandbox['data_fix']['default_langcode'] = [
'last_id' => 0,
'processed' => 0,
];
}

$database = \Drupal::database();
$data_table_name = 'menu_link_content_data';
$last_id = $sandbox['data_fix']['default_langcode']['last_id'];
$limit = Settings::get('update_sql_batch_size', 200);

// Detect records in the data table matching the base table language, but
// having the "default_langcode" flag set to with 0, which is not supported.
$query = $database->select($data_table_name, 'd');
$query->leftJoin('menu_link_content', 'b', 'd.id = b.id AND d.langcode = b.langcode AND d.default_langcode = 0');
$result = $query->fields('d', ['id', 'langcode'])
->condition('d.id', $last_id, '>')
->isNotNull('b.id')
->orderBy('d.id')
->range(0, $limit)
->execute();

foreach ($result as $record) {
$sandbox['data_fix']['default_langcode']['last_id'] = $record->id;

// We need to exclude any menu link already having also a data table record
// with the "default_langcode" flag set to 1, because this is a data
// integrity issue that cannot be fixed automatically. However the latter
// will not make the update fail.
$has_default_langcode = (bool) $database->select($data_table_name, 'd')
->fields('d', ['id'])
->condition('d.id', $record->id)
->condition('d.default_langcode', 1)
->range(0, 1)
->execute()
->fetchField();

if ($has_default_langcode) {
continue;
}

$database->update($data_table_name)
->fields(['default_langcode' => 1])
->condition('id', $record->id)
->condition('langcode', $record->langcode)
->execute();

$sandbox['data_fix']['default_langcode']['processed']++;

\Drupal::logger('update')
->warning('The menu link with ID @id had data integrity issues and was restored.', ['@id' => $record->id]);
}

$finished = $sandbox['data_fix']['default_langcode']['last_id'] === $last_id;
$sandbox['data_fix']['default_langcode']['finished'] = $finished;

return $finished;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

/**
* @file
* Contains database additions to drupal-8.filled.standard.php.gz for testing
* the upgrade path of https://www.drupal.org/project/drupal/issues/3056543.
*/

use Drupal\Core\Database\Database;

$connection = Database::getConnection();

$connection->insert('menu_link_content')
->fields([
'id' => 997,
'bundle' => 'menu_link_content',
'uuid' => 'ea32f399-b53b-416c-81a9-e66204236c97',
'langcode' => 'en',
])
->execute();
$connection->insert('menu_link_content_data')
->fields([
'id' => 997,
'bundle' => 'menu_link_content',
'langcode' => 'en',
'enabled' => 1,
'title' => 'menu_link_997',
'menu_name' => 'test-menu',
'link__uri' => 'https://drupal.org',
'link__title' => '',
'link__options' => 'a:0:{}',
'external' => 0,
'rediscover' => 0,
'weight' => 0,
'expanded' => 0,
'changed' => 1579555997,
'default_langcode' => 0,
])
->execute();

$connection->insert('menu_link_content')
->fields([
'id' => 998,
'bundle' => 'menu_link_content',
'uuid' => 'ea32f399-b53b-416c-81a9-e66204236c98',
'langcode' => 'en',
])
->execute();
$connection->insert('menu_link_content_data')
->fields([
'id' => 998,
'bundle' => 'menu_link_content',
'langcode' => 'en',
'enabled' => 1,
'title' => 'menu_link_998',
'menu_name' => 'test-menu',
'link__uri' => 'https://drupal.org',
'link__title' => '',
'link__options' => 'a:0:{}',
'external' => 0,
'rediscover' => 0,
'weight' => 0,
'expanded' => 0,
'changed' => 1579555997,
'default_langcode' => 0,
])
->execute();

$connection->insert('menu_link_content')
->fields([
'id' => 999,
'bundle' => 'menu_link_content',
'uuid' => 'ea32f399-b53b-416c-81a9-e66204236c99',
'langcode' => 'en',
])
->execute();
$connection->insert('menu_link_content_data')
->fields([
'id' => 999,
'bundle' => 'menu_link_content',
'langcode' => 'en',
'enabled' => 1,
'title' => 'menu_link_999',
'menu_name' => 'test-menu',
'link__uri' => 'https://drupal.org',
'link__title' => '',
'link__options' => 'a:0:{}',
'external' => 0,
'rediscover' => 0,
'weight' => 0,
'expanded' => 0,
'changed' => 1579555997,
'default_langcode' => 0,
])
->execute();
$connection->insert('menu_link_content_data')
->fields([
'id' => 999,
'bundle' => 'menu_link_content',
'langcode' => 'es',
'enabled' => 1,
'title' => 'menu_link_999-es',
'menu_name' => 'test-menu',
'link__uri' => 'https://drupal.org',
'link__title' => '',
'link__options' => 'a:0:{}',
'external' => 0,
'rediscover' => 0,
'weight' => 0,
'expanded' => 0,
'changed' => 1579555997,
'default_langcode' => 1,
])
->execute();
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class MenuLinkContentUpdateTest extends UpdatePathTestBase {
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz',
__DIR__ . '/../../../fixtures/update/drupal-8.menu-link-content-null-data-3056543.php',
];
}

Expand Down Expand Up @@ -69,8 +70,26 @@ public function testConversionToRevisionable() {
$entity_type = \Drupal::entityDefinitionUpdateManager()->getEntityType('menu_link_content');
$this->assertFalse($entity_type->isRevisionable());

// Set the batch size to 1 to test multiple steps.
drupal_rewrite_settings([
'settings' => [
'update_sql_batch_size' => (object) [
'value' => 1,
'required' => TRUE,
],
],
]);

// Check that there are broken menu links in the database tables, initially.
$this->assertMenuLinkTitle(997, '');
$this->assertMenuLinkTitle(998, '');
$this->assertMenuLinkTitle(999, 'menu_link_999-es');

$this->runUpdates();

// Check that the update function returned the expected message.
$this->assertSession()->pageTextContains('Custom menu links have been converted to be revisionable. 2 menu links with data integrity issues were restored. More details have been logged.');

$entity_type = \Drupal::entityDefinitionUpdateManager()->getEntityType('menu_link_content');
$this->assertTrue($entity_type->isRevisionable());

Expand Down Expand Up @@ -101,6 +120,35 @@ public function testConversionToRevisionable() {
$this->assertEquals('Pineapple', $menu_link->label());
$this->assertEquals('route:user.page', $menu_link->link->uri);
$this->assertTrue($menu_link->isPublished());

// Check that two menu links were restored and one was ignored. The latter
// cannot be manually restored, since we would end up with two data table
// records having "default_langcode" equalling 1, which would not make
// sense.
$this->assertMenuLinkTitle(997, 'menu_link_997');
$this->assertMenuLinkTitle(998, 'menu_link_998');
$this->assertMenuLinkTitle(999, 'menu_link_999-es');
}

/**
* Assert that a menu link label matches the expectation.
*
* @param string $id
* The menu link ID.
* @param string $expected_title
* The expected menu link title.
*/
protected function assertMenuLinkTitle($id, $expected_title) {
$database = \Drupal::database();
$query = $database->select('menu_link_content_data', 'd');
$query->join('menu_link_content', 'b', 'b.id = d.id AND d.default_langcode = 1');
$title = $query
->fields('d', ['title'])
->condition('d.id', $id)
->execute()
->fetchField();

$this->assertSame($expected_title, $title ?: '');
}

/**
Expand Down

0 comments on commit b1a3f0d

Please sign in to comment.