Commit ce767b8a authored by Gábor Hojtsy's avatar Gábor Hojtsy

Issue #3008028 by quietone, bserem, naresh_bavaskar, ravi.shankar, daffie,...

Issue #3008028 by quietone, bserem, naresh_bavaskar, ravi.shankar, daffie, Gábor Hojtsy, phenaproxima, catch, Zekvyrin, stefanos.petrakis: Migrate D7 i18n menu links
parent a77d83e3
id: d7_menu_links_localized
label: Menu links
migration_tags:
- Drupal 7
- Content
- Multilingual
source:
plugin: d7_menu_link_localized
constants:
bundle: menu_link_content
process:
skip_translation:
plugin: skip_on_empty
method: row
source: skip_source_translation
exists:
-
plugin: migration_lookup
migration: d7_menu_links
source: mlid
-
plugin: skip_on_empty
method: row
id: mlid
langcode: language
title: link_title
description: description
menu_name:
-
plugin: migration_lookup
migration: d7_menu
source: menu_name
-
plugin: skip_on_empty
method: row
destination:
plugin: entity:menu_link_content
no_stub: true
translations: true
destination_module: content_translation
migration_dependencies:
required:
- language
- d7_language_content_menu_settings
- d7_menu_links
id: d7_menu_links_translation
label: Menu links
migration_tags:
- Drupal 7
- Content
- Multilingual
source:
plugin: d7_menu_link_translation
process:
exists:
-
plugin: migration_lookup
migration: d7_menu_links
source: mlid
-
plugin: skip_on_empty
method: row
id: mlid
# Use the language from the locales_target table.
langcode: language
title:
-
plugin: callback
source:
- title_translated
- link_title
callable: array_filter
-
plugin: callback
callable: current
description:
-
plugin: callback
source:
- description_translated
- description
callable: array_filter
-
plugin: callback
callable: current
destination:
plugin: entity:menu_link_content
default_bundle: menu_link_content
no_stub: true
translations: true
migration_dependencies:
required:
- language
- d7_language_content_menu_settings
- d7_menu_links
......@@ -21,6 +21,8 @@ finished:
- content_translation
locale: content_translation
menu: content_translation
# menu links.
i18n_menu: content_translation
statistics: statistics
# Node revision translations.
node: content_translation
......@@ -36,9 +38,6 @@ not_finished:
# @TODO: Move to finished when remaining Drupal 7 i18n issues are resolved.
# See https://www.drupal.org/project/drupal/issues/2208401
i18n: content_translation
# Menu links.
# See https://www.drupal.org/project/drupal/issues/3008028
i18n_menu: content_translation
# @TODO: Remove when taxonomy term field translations are migrated.
# See https://www.drupal.org/project/drupal/issues/3073050
i18n_taxonomy: content_translation
......@@ -9,7 +9,15 @@ source:
constants:
bundle: menu_link_content
process:
skip_translation:
plugin: skip_on_empty
method: row
source: skip_translation
id: mlid
langcode:
plugin: default_value
source: language
default_value: und
bundle: 'constants/bundle'
title: link_title
description: description
......
......@@ -40,7 +40,7 @@ public function query() {
* {@inheritdoc}
*/
public function fields() {
return [
$fields = [
'menu_name' => t("The menu name. All links with the same menu name (such as 'navigation') are part of the same menu."),
'mlid' => t('The menu link ID (mlid) is the integer primary key.'),
'plid' => t('The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link is at the top level in its menu.'),
......@@ -67,12 +67,48 @@ public function fields() {
'p9' => t('The ninth mlid in the materialized path. See p1.'),
'updated' => t('Flag that indicates that this link was generated during the update from Drupal 5.'),
];
$schema = $this->getDatabase()->schema();
if ($schema->fieldExists('menu_links', 'language')) {
$fields['language'] = $this->t("Menu link language code.");
}
if ($schema->fieldExists('menu_links', 'i18n_tsid')) {
$fields['i18n_tsid'] = $this->t("Translation set id.");
}
return $fields;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
// In Drupal 7 a language neutral menu_link can be translated. The menu
// link is treated as if it is in the site default language. So, here
// we look to see if this menu link has a translation and if so, the
// language is changed to the default language. With the language set
// the entity API will allow the saving of the translations.
if ($row->hasSourceProperty('language') &&
$row->getSourceProperty('language') == 'und' &&
$this->hasTranslation($row->getSourceProperty('mlid'))) {
$default_language = $this->variableGet('language_default', (object) ['language' => 'und']);
$default_language = $default_language->language;
$row->setSourceProperty('language', $default_language);
}
// If this menu link is part of translation set skip the translations. The
// translations are migrated in d7_menu_link_localized.yml.
$row->setSourceProperty('skip_translation', TRUE);
if ($row->hasSourceProperty('i18n_tsid') && $row->getSourceProperty('i18n_tsid') != 0) {
$source_mlid = $this->select('menu_links', 'ml')
->fields('ml', ['mlid'])
->condition('i18n_tsid', $row->getSourceProperty('i18n_tsid'))
->orderBy('mlid')
->range(0, 1)
->execute()
->fetchField();
if ($source_mlid !== $row->getSourceProperty('mlid')) {
$row->setSourceProperty('skip_translation', FALSE);
}
}
$row->setSourceProperty('options', unserialize($row->getSourceProperty('options')));
$row->setSourceProperty('enabled', !$row->getSourceProperty('hidden'));
$row->setSourceProperty('description', Unicode::truncate($row->getSourceProperty('options/attributes/title'), 255));
......@@ -80,6 +116,31 @@ public function prepareRow(Row $row) {
return parent::prepareRow($row);
}
/**
* Determines if this menu_link has an i18n translation.
*
* @param string $mlid
* The menu id.
*
* @return bool
* True if the menu_link has an i18n translation.
*/
public function hasTranslation($mlid) {
if ($this->getDatabase()->schema()->tableExists('i18n_string')) {
$results = $this->select('i18n_string', 'i18n')
->fields('i18n')
->condition('textgroup', 'menu')
->condition('type', 'item')
->condition('objectid', $mlid)
->execute()
->fetchAll();
if ($results) {
return TRUE;
}
}
return FALSE;
}
/**
* {@inheritdoc}
*/
......
<?php
namespace Drupal\menu_link_content\Plugin\migrate\source\d7;
use Drupal\menu_link_content\Plugin\migrate\source\MenuLink;
use Drupal\migrate\Row;
/**
* Gets localized menu link translations from source database.
*
* @MigrateSource(
* id = "d7_menu_link_localized",
* source_module = "i18n_menu"
* )
*/
class MenuLinkLocalized extends MenuLink {
/**
* {@inheritdoc}
*/
public function query() {
$query = parent::query();
$query->condition('ml.i18n_tsid', '0', '<>');
// The first row in a translation set is the source.
$query->orderBy('ml.i18n_tsid');
return $query;
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'ml_language' => $this->t('Menu link ID of the source language menu link.'),
'skip_source_translation' => $this->t('Menu link description translation.'),
];
return parent::fields() + $fields;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$row->setSourceProperty('skip_source_translation', TRUE);
// Get the mlid for the source menu_link.
$source_mlid = $this->select('menu_links', 'ml')
->fields('ml', ['mlid'])
->condition('i18n_tsid', $row->getSourceProperty('i18n_tsid'))
->orderBy('mlid')
->range(0, 1)
->execute()
->fetchField();
if ($source_mlid == $row->getSourceProperty('mlid')) {
$row->setSourceProperty('skip_source_translation', FALSE);
}
$row->setSourceProperty('mlid', $source_mlid);
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['language']['type'] = 'string';
$ids['language']['alias'] = 'ml';
return parent::getIds() + $ids;
}
}
<?php
namespace Drupal\menu_link_content\Plugin\migrate\source\d7;
use Drupal\content_translation\Plugin\migrate\source\I18nQueryTrait;
use Drupal\migrate\Row;
use Drupal\menu_link_content\Plugin\migrate\source\MenuLink;
/**
* Gets Menu link translations from source database.
*
* @MigrateSource(
* id = "d7_menu_link_translation",
* source_module = "i18n_menu"
* )
*/
class MenuLinkTranslation extends MenuLink {
use I18nQueryTrait;
/**
* {@inheritdoc}
*/
public function query() {
$query = parent::query();
// Add in the property, which is either title or description. Cast the mlid
// to text so PostgreSQL can make the join.
$query->leftJoin('i18n_string', 'i18n', 'CAST(ml.mlid AS CHAR(255)) = i18n.objectid');
$query->fields('i18n', ['lid', 'objectid', 'property', 'textgroup'])
->condition('i18n.textgroup', 'menu')
->condition('i18n.type', 'item');
// Add in the translation for the property.
$query->innerJoin('locales_target', 'lt', 'i18n.lid = lt.lid');
$query->addField('lt', 'language', 'lt_language');
$query->fields('lt', ['translation']);
$query->isNotNull('lt.language');
return $query;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
parent::prepareRow($row);
// Put the language on the row as 'language'.
$row->setSourceProperty('language', $row->getSourceProperty('lt_language'));
// Save the translation for this property.
$property_in_row = $row->getSourceProperty('property');
// Set the i18n string table for use in I18nQueryTrait.
$this->i18nStringTable = 'i18n_string';
// Get the translation for the property not already in the row and save it
// in the row.
$property_not_in_row = ($property_in_row == 'title') ? 'description' : 'title';
return $this->getPropertyNotInRowTranslation($row, $property_not_in_row, 'mlid', $this->idMap);
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'title_translated' => $this->t('Menu link title translation.'),
'description_translated' => $this->t('Menu link description translation.'),
];
return parent::fields() + $fields;
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['language']['type'] = 'string';
$ids['language']['alias'] = 'lt';
return parent::getIds() + $ids;
}
}
<?php
namespace Drupal\Tests\menu_link_content\Kernel\Migrate;
use Drupal\menu_link_content\Entity\MenuLinkContent;
use Drupal\menu_link_content\MenuLinkContentInterface;
/**
* Provides assertions for testing MenuLinkContent.
*/
trait MigrateMenuLinkTestTrait {
/**
* Asserts various aspects of a menu link entity.
*
* @param string $id
* The link ID.
* @param string $langcode
* The language of the menu link.
* @param string $title
* The expected title of the link.
* @param string $menu
* The expected ID of the menu to which the link will belong.
* @param string $description
* The link's expected description.
* @param bool $enabled
* Whether the link is enabled.
* @param bool $expanded
* Whether the link is expanded.
* @param array $attributes
* Additional attributes the link is expected to have.
* @param string $uri
* The expected URI of the link.
* @param int $weight
* The expected weight of the link.
*
* @return \Drupal\menu_link_content\MenuLinkContentInterface
* The menu link content.
*/
protected function assertEntity($id, $langcode, $title, $menu, $description, $enabled, $expanded, array $attributes, $uri, $weight) {
/** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link */
$menu_link = MenuLinkContent::load($id);
$menu_link = $menu_link->getTranslation($langcode);
$this->assertInstanceOf(MenuLinkContentInterface::class, $menu_link);
$this->assertSame($title, $menu_link->getTitle());
$this->assertSame($langcode, $menu_link->language()->getId());
$this->assertSame($menu, $menu_link->getMenuName());
$this->assertSame($description, $menu_link->getDescription());
$this->assertSame($enabled, $menu_link->isEnabled());
$this->assertSame($expanded, $menu_link->isExpanded());
$this->assertSame($attributes, $menu_link->link->options);
$this->assertSame($uri, $menu_link->link->uri);
$this->assertSame($weight, $menu_link->getWeight());
return $menu_link;
}
}
<?php
namespace Drupal\Tests\menu_link_content\Kernel\Migrate\d7;
use Drupal\Tests\menu_link_content\Kernel\Migrate\MigrateMenuLinkTestTrait;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests Menu link localized translation migration.
*
* @group migrate_drupal_7
*/
class MigrateMenuLinkLocalizedTest extends MigrateDrupal7TestBase {
use MigrateMenuLinkTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'content_translation',
'language',
'link',
'menu_link_content',
'menu_ui',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->executeMigrations(['language']);
$this->installEntitySchema('menu_link_content');
$this->executeMigrations([
'd7_menu',
'd7_language_content_menu_settings',
'd7_menu_links',
'd7_menu_links_localized',
]);
}
/**
* Tests migration of menu link localized translations.
*/
public function testMenuLinkLocalized() {
// A translate and localize menu, menu-test-menu.
$this->assertEntity(468, 'en', 'Yahoo', 'menu-test-menu', 'english description', TRUE, FALSE, ['attributes' => ['title' => 'english description'], 'alter' => TRUE], 'http://yahoo.com', 0);
$this->assertEntity(468, 'fr', 'fr - Yahoo', 'menu-test-menu', 'fr - description', TRUE, FALSE, ['attributes' => ['title' => 'english description'], 'alter' => TRUE], 'http://yahoo.com', 0);
$this->assertEntity(468, 'is', 'is - Yahoo', 'menu-test-menu', 'is - description', TRUE, FALSE, ['attributes' => ['title' => 'english description'], 'alter' => TRUE], 'http://yahoo.com', 0);
}
}
......@@ -3,8 +3,7 @@
namespace Drupal\Tests\menu_link_content\Kernel\Migrate\d7;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\menu_link_content\Entity\MenuLinkContent;
use Drupal\menu_link_content\MenuLinkContentInterface;
use Drupal\Tests\menu_link_content\Kernel\Migrate\MigrateMenuLinkTestTrait;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
use Drupal\Tests\user\Traits\UserCreationTrait;
......@@ -17,6 +16,7 @@ class MigrateMenuLinkTest extends MigrateDrupal7TestBase {
const MENU_NAME = 'menu-test-menu';
use UserCreationTrait;
use MigrateMenuLinkTestTrait;
/**
* {@inheritdoc}
......@@ -55,58 +55,22 @@ protected function setUp(): void {
]);
}
/**
* Asserts various aspects of a menu link entity.
*
* @param string $id
* The link ID.
* @param string $title
* The expected title of the link.
* @param string $menu
* The expected ID of the menu to which the link will belong.
* @param string $description
* The link's expected description.
* @param bool $enabled
* Whether the link is enabled.
* @param bool $expanded
* Whether the link is expanded.
* @param array $attributes
* Additional attributes the link is expected to have.
* @param string $uri
* The expected URI of the link.
* @param int $weight
* The expected weight of the link.
*
* @return \Drupal\menu_link_content\MenuLinkContentInterface
* The menu link content.
*/
protected function assertEntity($id, $title, $menu, $description, $enabled, $expanded, array $attributes, $uri, $weight) {
/** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link */
$menu_link = MenuLinkContent::load($id);
$this->assertInstanceOf(MenuLinkContentInterface::class, $menu_link);
$this->assertSame($title, $menu_link->getTitle());
$this->assertSame($menu, $menu_link->getMenuName());
$this->assertSame($description, $menu_link->getDescription());
$this->assertSame($enabled, $menu_link->isEnabled());
$this->assertSame($expanded, $menu_link->isExpanded());
$this->assertSame($attributes, $menu_link->link->options);
$this->assertSame($uri, $menu_link->link->uri);
$this->assertSame($weight, $menu_link->getWeight());
return $menu_link;
}
/**
* Tests migration of menu links.
*/
public function testMenuLinks() {
$this->assertEntity(469, 'Bing', static::MENU_NAME, 'Bing', TRUE, FALSE, ['attributes' => ['title' => 'Bing']], 'http://bing.com', 0);
$this->assertEntity(467, 'Google', static::MENU_NAME, 'Google', TRUE, FALSE, ['attributes' => ['title' => 'Google']], 'http://google.com', 0);
$this->assertEntity(468, 'Yahoo', static::MENU_NAME, 'Yahoo', TRUE, FALSE, ['attributes' => ['title' => 'Yahoo']], 'http://yahoo.com', 0);
$this->assertEntity(469, 'und', 'Bing', static::MENU_NAME, 'Bing', TRUE, FALSE, ['attributes' => ['title' => 'Bing']], 'http://bing.com', 0);
// This link has an i18n translation so the language is changed to the
// default language of the source site.
$this->assertEntity(467, 'en', 'Google', static::MENU_NAME, 'Google', TRUE, FALSE, ['attributes' => ['title' => 'Google']], 'http://google.com', 0);
$this->assertEntity(468, 'en', 'Yahoo', static::MENU_NAME, 'english description', TRUE, FALSE, ['attributes' => ['title' => 'english description'], 'alter' => TRUE], 'http://yahoo.com', 0);
// Tests migrating an external link with an undefined title attribute.
$this->assertEntity(470, 'Ask', static::MENU_NAME, NULL, TRUE, FALSE, [], 'http://ask.com', 0);
$this->assertEntity(245, 'Home', 'main', NULL, TRUE, FALSE, [], 'internal:/', 0);
$this->assertEntity(478, 'custom link test', 'admin', NULL, TRUE, FALSE, ['attributes' => ['title' => '']], 'internal:/admin/content', 0);
$this->assertEntity(479, 'node link test', 'tools', 'node 2', TRUE, FALSE, ['attributes' => ['title' => 'node 2']], 'entity:node/2', 3);
$this->assertEntity(470, 'und', 'Ask', static::MENU_NAME, NULL, TRUE, FALSE, [], 'http://ask.com', 0);
$this->assertEntity(245, 'und', 'Home', 'main', NULL, TRUE, FALSE, [], 'internal:/', 0);
$this->assertEntity(478, 'und', 'custom link test', 'admin', NULL, TRUE, FALSE, ['attributes' => ['title' => '']], 'internal:/admin/content', 0);
$this->assertEntity(479, 'und', 'node link test', 'tools', 'node 2', TRUE, FALSE, ['attributes' => ['title' => 'node 2']], 'entity:node/2', 3);
$menu_link_tree_service = \Drupal::service('menu.link_tree');
$parameters = new MenuTreeParameters();
......@@ -137,10 +101,10 @@ public function testMenuLinks() {
$this->assertTrue($found);
// Test the migration of menu links for translated nodes.
$this->assertEntity(484, 'The thing about Deep Space 9', 'tools', NULL, TRUE, FALSE, ['attributes' => ['title' => '']], 'entity:node/2', 9);
$this->assertEntity(485, 'is - The thing about Deep Space 9', 'tools', NULL, TRUE, FALSE, ['attributes' => ['title' => '']], 'entity:node/2', 10);
$this->assertEntity(486, 'is - The thing about Firefly', 'tools', NULL, TRUE, FALSE, ['attributes' => ['title' => '']], 'entity:node/4', 11);
$this->assertEntity(487, 'en - The thing about Firefly', 'tools', NULL, TRUE, FALSE, ['attributes' => ['title' => '']], 'entity:node/4', 12);
$this->assertEntity(484, 'und', 'The thing about Deep Space 9', 'tools', NULL, TRUE, FALSE, ['attributes' => ['title' => '']], 'entity:node/2', 9);
$this->assertEntity(485, 'en', 'is - The thing about Deep Space 9', 'tools', NULL, TRUE, FALSE, ['attributes' => ['title' => '']], 'entity:node/2', 10);
$this->assertEntity(486, 'und', 'is - The thing about Firefly', 'tools', NULL, TRUE, FALSE, ['attributes' => ['title' => '']], 'entity:node/4', 11);
$this->assertEntity(487, 'en', 'en - The thing about Firefly', 'tools', NULL, TRUE, FALSE, ['attributes' => ['title' => '']], 'entity:node/4', 12);
}
}