Commit a9f1812a authored by catch's avatar catch

Issue #3056543 by plach, jungle, Berdir, catch, xjm, amateescu:...

Issue #3056543 by plach, jungle, Berdir, catch, xjm, amateescu: taxonomy_post_update_make_taxonomy_term_revisionable() and the menu link content equivalent fail when entities have no default translation

(cherry picked from commit c1d333241eca0c2b30f9da843bc1aaa837dbb436)
parent 3e07da4a
......@@ -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');
......@@ -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;
}
<?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();
......@@ -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',
];
}
......@@ -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());
......@@ -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 ?: '');
}
/**
......
......@@ -8,7 +8,12 @@
use Drupal\Core\Config\Entity\ConfigEntityUpdater;
use Drupal\Core\Entity\Display\EntityDisplayInterface;
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\taxonomy\TermStorage;
use Drupal\views\ViewExecutable;
/**
......@@ -147,6 +152,12 @@ function taxonomy_post_update_remove_hierarchy_from_vocabularies(&$sandbox = NUL
* Update taxonomy terms to be revisionable.
*/
function taxonomy_post_update_make_taxonomy_term_revisionable(&$sandbox) {
$finished = _taxonomy_post_update_make_taxonomy_term_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');
......@@ -231,7 +242,107 @@ function taxonomy_post_update_make_taxonomy_term_revisionable(&$sandbox) {
$definition_update_manager->updateFieldableEntityType($entity_type, $field_storage_definitions, $sandbox);
return t('Taxonomy terms 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, 'Taxonomy terms have been converted to be revisionable. One term with data integrity issues was restored. More details have been <a href=":url">logged</a>.', 'Taxonomy terms have been converted to be revisionable. @count terms with data integrity issues were restored. More details have been <a href=":url">logged</a>.', $args);
}
else {
return new PluralTranslatableMarkup($count, 'Taxonomy terms have been converted to be revisionable. One term with data integrity issues was restored. More details have been logged.', 'Taxonomy terms have been converted to be revisionable. @count terms with data integrity issues were restored. More details have been logged.');
}
}
else {
return t('Taxonomy terms 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 _taxonomy_post_update_make_taxonomy_term_revisionable__fix_default_langcode(array &$sandbox) {
if (!empty($sandbox['data_fix']['default_langcode']['finished'])) {
return TRUE;
}
$storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
if (!$storage instanceof TermStorage) {
$sandbox['data_fix']['default_langcode']['finished'] = TRUE;
return TRUE;
}
elseif (!isset($sandbox['data_fix']['default_langcode'])) {
$sandbox['data_fix']['default_langcode'] = [
'last_id' => 0,
'processed' => 0,
];
}
$database = \Drupal::database();
$data_table_name = 'taxonomy_term_field_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('taxonomy_term_data', 'b', 'd.tid = b.tid AND d.langcode = b.langcode AND d.default_langcode = 0');
$result = $query->fields('d', ['tid', 'langcode'])
->condition('d.tid', $last_id, '>')
->isNotNull('b.tid')
->orderBy('d.tid')
->range(0, $limit)
->execute();
foreach ($result as $record) {
$sandbox['data_fix']['default_langcode']['last_id'] = $record->tid;
// We need to exclude any term 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', ['tid'])
->condition('d.tid', $record->tid)
->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('tid', $record->tid)
->condition('langcode', $record->langcode)
->execute();
$sandbox['data_fix']['default_langcode']['processed']++;
\Drupal::logger('update')
->warning('The term with ID @id had data integrity issues and was restored.', ['@id' => $record->tid]);
}
$finished = $sandbox['data_fix']['default_langcode']['last_id'] === $last_id;
$sandbox['data_fix']['default_langcode']['finished'] = $finished;
return $finished;
}
/**
......
<?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('taxonomy_term_data')
->fields([
'tid' => 997,
'vid' => 'tags',
'uuid' => 'ea32f399-a53b-416c-81a9-e66204236c97',
'langcode' => 'en',
])
->execute();
$connection->insert('taxonomy_term_field_data')
->fields([
'tid' => 997,
'vid' => 'tags',
'langcode' => 'en',
'default_langcode' => 0,
'name' => 'tag997',
'weight' => 0,
'changed' => 1579555997,
'content_translation_status' => NULL,
])
->execute();
$connection->insert('taxonomy_term_hierarchy')
->fields([
'tid' => 997,
'parent' => 0,
])
->execute();
$connection->insert('taxonomy_term_data')
->fields([
'tid' => 998,
'vid' => 'tags',
'uuid' => 'ea32f399-a53b-416c-81a9-e66204236c98',
'langcode' => 'en',
])
->execute();
$connection->insert('taxonomy_term_field_data')
->fields([
'tid' => 998,
'vid' => 'tags',
'langcode' => 'en',
'default_langcode' => 0,
'name' => 'tag998',
'weight' => 0,
'changed' => 1579555998,
'content_translation_status' => NULL,
])
->execute();
$connection->insert('taxonomy_term_hierarchy')
->fields([
'tid' => 998,
'parent' => 0,
])
->execute();
$connection->insert('taxonomy_term_data')
->fields([
'tid' => 999,
'vid' => 'tags',
'uuid' => 'ea32f399-a53b-416c-81a9-e66204236c99',
'langcode' => 'en',
])
->execute();
$connection->insert('taxonomy_term_field_data')
->fields([
'tid' => 999,
'vid' => 'tags',
'langcode' => 'en',
'default_langcode' => 0,
'name' => 'tag999-en',
'weight' => 0,
'changed' => 1579555999,
'content_translation_status' => NULL,
])
->execute();
$connection->insert('taxonomy_term_field_data')
->fields([
'tid' => 999,
'vid' => 'tags',
'langcode' => 'es',
'default_langcode' => 1,
'name' => 'tag999-es',
'weight' => 0,
'changed' => 1579555999,
'content_translation_status' => NULL,
])
->execute();
$connection->insert('taxonomy_term_hierarchy')
->fields([
'tid' => 999,
'parent' => 0,
])
->execute();
......@@ -25,6 +25,7 @@ protected function setDatabaseDumpFiles() {
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz',
__DIR__ . '/../../../fixtures/update/drupal-8.views-taxonomy-term-publishing-status-2981887.php',
__DIR__ . '/../../../fixtures/update/drupal-8.taxonomy-term-publishing-status-ui-2899923.php',
__DIR__ . '/../../../fixtures/update/drupal-8.taxonomy-term-null-data-3056543.php',
];
}
......@@ -154,8 +155,26 @@ public function testPublishingStatusUpdateForTaxonomyTermViews() {
* @see taxonomy_post_update_make_taxonomy_term_revisionable()
*/
public function testConversionToRevisionable() {
// 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 terms in the taxonomy tables, initially.
$this->assertTermName(997, '');
$this->assertTermName(998, '');
$this->assertTermName(999, 'tag999-es');
$this->runUpdates();
// Check that the update function returned the expected message.
$this->assertSession()->pageTextContains('Taxonomy terms have been converted to be revisionable. 2 terms with data integrity issues were restored. More details have been logged.');
// Check the database tables and the field storage definitions.
$schema = \Drupal::database()->schema();
$this->assertTrue($schema->tableExists('taxonomy_term_data'));
......@@ -207,6 +226,34 @@ public function testConversionToRevisionable() {
$this->assertEquals('article', $term->bundle());
$this->assertEquals('Initial revision.', $term->getRevisionLogMessage());
$this->assertTrue($term->isPublished());
// Check that two terms 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->assertTermName(997, 'tag997');
$this->assertTermName(998, 'tag998');
$this->assertTermName(999, 'tag999-es');
}
/**
* Assert that a term name matches the expectation.
*
* @param string $id
* The term ID.
* @param string $expected_name
* The expected term name.
*/
protected function assertTermName($id, $expected_name) {
$database = \Drupal::database();
$query = $database->select('taxonomy_term_field_data', 'd');
$query->join('taxonomy_term_data', 't', 't.tid = d.tid AND d.default_langcode = 1');
$name = $query
->fields('d', ['name'])
->condition('d.tid', $id)
->execute()
->fetchField();
$this->assertSame($expected_name, $name ?: '');
}
/**
......
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