Commit d43ad766 authored by catch's avatar catch

Issue #2912353 by maxocub, alexpott, heddn, phenaproxima, quietone: Handle...

Issue #2912353 by maxocub, alexpott, heddn, phenaproxima, quietone: Handle menu_items related to Drupal 6 and 7 node translations with different IDs
parent 7d0b699e
......@@ -56,3 +56,5 @@ destination:
migration_dependencies:
required:
- d6_menu
optional:
- d6_node
......@@ -52,3 +52,5 @@ destination:
migration_dependencies:
required:
- d7_menu
optional:
- d7_node
id: node_translation_menu_links
label: Node Translations Menu links
audit: true
migration_tags:
- Drupal 6
- Drupal 7
- Content
source:
plugin: menu_link
constants:
entity_prefix: 'entity:'
node_prefix: 'node/'
process:
id: mlid
title: link_title
description: description
menu_name:
-
plugin: migration_lookup
# The menu migration is in the system module.
migration:
- d6_menu
- d7_menu
source: menu_name
-
plugin: skip_on_empty
method: row
-
plugin: static_map
map:
management: admin
bypass: true
# In this process pipeline, given a menu link path that might be for a
# translated node which has been merged with the default language node, we are
# trying to determine the new node ID, that is the ID of the default language
# node.
new_nid:
-
# If the path is of the form "node/<ID>" and is not routed, we will get
# back an URI of the form "base:node/<ID>".
plugin: link_uri
source:
- link_path
validate_route: false
-
# Isolate the node ID.
plugin: explode
delimiter: 'base:node/'
-
# Extract the node ID.
plugin: extract
default: false
index:
- 1
-
# Skip row if node ID is empty.
plugin: skip_on_empty
method: row
-
# With the old node ID in hand, lookup in the d6_node_translation or
# d7_node_translation mapping tables to find the new node ID.
plugin: migration_lookup
migration:
- d6_node_translation
- d7_node_translation
no_stub: true
-
# Skip row if the new node ID is empty.
plugin: skip_on_empty
method: row
-
# Extract the node ID. The migration lookup will return an array with two
# items, the new node ID and the translation langcode. We need the node ID
# which is at index 0.
plugin: extract
index:
- 0
# This will be used in the "link/uri" and "route" processes below.
link_path:
plugin: concat
source:
- 'constants/node_prefix'
- '@new_nid'
link/uri:
plugin: concat
source:
- 'constants/entity_prefix'
- '@link_path'
link/options: options
route:
plugin: route
source:
- '@link_path'
- options
route_name: '@route/route_name'
route_parameters: '@route/route_parameters'
url: '@route/url'
options: '@route/options'
external: external
weight: weight
expanded: expanded
enabled: enabled
parent:
plugin: menu_link_parent
source:
- plid
- '@menu_name'
- parent_link_path
changed: updated
destination:
plugin: entity:menu_link_content
default_bundle: menu_link_content
no_stub: true
migration_dependencies:
optional:
- d6_menu_links
- d6_node_translation
- d7_menu_links
- d7_node_translation
......@@ -14,6 +14,8 @@
/**
* Processes a link path into an 'internal:' or 'entity:' URI.
*
* @todo: Add documentation in https://www.drupal.org/node/2954908
*
* @MigrateProcessPlugin(
* id = "link_uri"
* )
......@@ -40,6 +42,9 @@ class LinkUri extends ProcessPluginBase implements ContainerFactoryPluginInterfa
* The entity type manager, used to fetch entity link templates.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
$configuration += [
'validate_route' => TRUE,
];
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeManager = $entity_type_manager;
}
......@@ -82,7 +87,15 @@ public function transform($value, MigrateExecutableInterface $migrate_executable
}
}
else {
throw new MigrateException(sprintf('The path "%s" failed validation.', $path));
// If the URL is not routed, we might want to get something back to do
// other processing. If this is the case, the "validate_route"
// configuration option can be set to FALSE to return the URI.
if (!$this->configuration['validate_route']) {
return $url->getUri();
}
else {
throw new MigrateException(sprintf('The path "%s" failed validation.', $path));
}
}
}
return $path;
......
......@@ -3,19 +3,25 @@
namespace Drupal\Tests\menu_link_content\Kernel\Migrate\d6;
use Drupal\menu_link_content\Entity\MenuLinkContent;
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
use Drupal\menu_link_content\MenuLinkContentInterface;
use Drupal\Tests\node\Kernel\Migrate\d6\MigrateNodeTestBase;
/**
* Menu link migration.
*
* @group migrate_drupal_6
*/
class MigrateMenuLinkTest extends MigrateDrupal6TestBase {
class MigrateMenuLinkTest extends MigrateNodeTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['menu_ui', 'menu_link_content'];
public static $modules = [
'content_translation',
'language',
'menu_link_content',
'menu_ui',
];
/**
* {@inheritdoc}
......@@ -23,53 +29,73 @@ class MigrateMenuLinkTest extends MigrateDrupal6TestBase {
protected function setUp() {
parent::setUp();
$this->installEntitySchema('menu_link_content');
$this->executeMigrations(['d6_menu', 'd6_menu_links']);
$this->executeMigrations([
'language',
'd6_language_content_settings',
'd6_node',
'd6_node_translation',
'd6_menu',
'd6_menu_links',
'node_translation_menu_links',
]);
}
/**
* 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() {
$menu_link = MenuLinkContent::load(138);
$this->assertIdentical('Test 1', $menu_link->getTitle());
$this->assertIdentical('secondary-links', $menu_link->getMenuName());
$this->assertIdentical('Test menu link 1', $menu_link->getDescription());
$this->assertIdentical(TRUE, $menu_link->isEnabled());
$this->assertIdentical(FALSE, $menu_link->isExpanded());
$this->assertIdentical(['attributes' => ['title' => 'Test menu link 1']], $menu_link->link->options);
$this->assertIdentical('internal:/user/login', $menu_link->link->uri);
$this->assertIdentical(-50, $menu_link->getWeight());
$menu_link = MenuLinkContent::load(139);
$this->assertIdentical('Test 2', $menu_link->getTitle());
$this->assertIdentical('secondary-links', $menu_link->getMenuName());
$this->assertIdentical('Test menu link 2', $menu_link->getDescription());
$this->assertIdentical(TRUE, $menu_link->isEnabled());
$this->assertIdentical(TRUE, $menu_link->isExpanded());
$this->assertIdentical(['query' => 'foo=bar', 'attributes' => ['title' => 'Test menu link 2']], $menu_link->link->options);
$this->assertIdentical('internal:/admin', $menu_link->link->uri);
$this->assertIdentical(-49, $menu_link->getWeight());
$this->assertEntity('138', 'Test 1', 'secondary-links', 'Test menu link 1', TRUE, FALSE, ['attributes' => ['title' => 'Test menu link 1']], 'internal:/user/login', -50);
$this->assertEntity('139', 'Test 2', 'secondary-links', 'Test menu link 2', TRUE, TRUE, ['query' => 'foo=bar', 'attributes' => ['title' => 'Test menu link 2']], 'internal:/admin', -49);
$this->assertEntity('140', 'Drupal.org', 'secondary-links', NULL, TRUE, FALSE, ['attributes' => ['title' => '']], 'https://www.drupal.org', -50);
$menu_link = MenuLinkContent::load(140);
$this->assertIdentical('Drupal.org', $menu_link->getTitle());
$this->assertIdentical('secondary-links', $menu_link->getMenuName());
$this->assertIdentical(NULL, $menu_link->getDescription());
$this->assertIdentical(TRUE, $menu_link->isEnabled());
$this->assertIdentical(FALSE, $menu_link->isExpanded());
$this->assertIdentical(['attributes' => ['title' => '']], $menu_link->link->options);
$this->assertIdentical('https://www.drupal.org', $menu_link->link->uri);
$this->assertIdentical(-50, $menu_link->getWeight());
// Assert that missing title attributes don't stop or break migration.
$this->assertEntity('393', 'Test 3', 'secondary-links', NULL, TRUE, FALSE, [], 'internal:/user/login', -47);
// assert that missing title attributes don't stop or break migration.
$menu_link = MenuLinkContent::load(393);
$this->assertIdentical('Test 3', $menu_link->getTitle());
$this->assertIdentical('secondary-links', $menu_link->getMenuName());
$this->assertIdentical(NULL, $menu_link->getDescription());
$this->assertIdentical(TRUE, $menu_link->isEnabled());
$this->assertIdentical(FALSE, $menu_link->isExpanded());
$this->assertIdentical([], $menu_link->link->options);
$this->assertIdentical('internal:/user/login', $menu_link->link->uri);
$this->assertIdentical(-47, $menu_link->getWeight());
// Test the migration of menu links for translated nodes.
$this->assertEntity('459', 'The Real McCoy', 'primary-links', NULL, TRUE, FALSE, ['attributes' => ['title' => ''], 'alter' => TRUE], 'entity:node/10', 0);
$this->assertEntity('460', 'Le Vrai McCoy', 'primary-links', NULL, TRUE, FALSE, ['attributes' => ['title' => ''], 'alter' => TRUE], 'entity:node/10', 0);
$this->assertEntity('461', 'Abantu zulu', 'primary-links', NULL, TRUE, FALSE, ['attributes' => ['title' => ''], 'alter' => TRUE], 'entity:node/12', 0);
$this->assertEntity('462', 'The Zulu People', 'primary-links', NULL, TRUE, FALSE, ['attributes' => ['title' => ''], 'alter' => TRUE], 'entity:node/12', 0);
}
}
......@@ -5,7 +5,6 @@
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\menu_link_content\Entity\MenuLinkContent;
use Drupal\menu_link_content\MenuLinkContentInterface;
use Drupal\node\Entity\Node;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
......@@ -19,7 +18,15 @@ class MigrateMenuLinkTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['link', 'menu_ui', 'menu_link_content', 'node'];
public static $modules = [
'content_translation',
'language',
'link',
'menu_ui',
'menu_link_content',
'node',
'text',
];
/**
* {@inheritdoc}
......@@ -28,13 +35,20 @@ protected function setUp() {
parent::setUp();
$this->installEntitySchema('menu_link_content');
$this->installEntitySchema('node');
$node = Node::create([
'nid' => 2,
'title' => 'node link test',
'type' => 'article',
$this->installSchema('node', ['node_access']);
$this->installConfig(static::$modules);
$this->executeMigrations([
'language',
'd7_user_role',
'd7_user',
'd7_node_type',
'd7_language_content_settings',
'd7_node',
'd7_node_translation',
'd7_menu',
'd7_menu_links',
'node_translation_menu_links',
]);
$node->save();
$this->executeMigrations(['d7_menu', 'd7_menu_links']);
\Drupal::service('router.builder')->rebuild();
}
......@@ -52,7 +66,7 @@ protected function setUp() {
* @param bool $enabled
* Whether the link is enabled.
* @param bool $expanded
* Whether the link is expanded
* Whether the link is expanded.
* @param array $attributes
* Additional attributes the link is expected to have.
* @param string $uri
......@@ -66,11 +80,9 @@ protected function setUp() {
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->assertTrue($menu_link instanceof MenuLinkContentInterface);
$this->assertInstanceOf(MenuLinkContentInterface::class, $menu_link);
$this->assertSame($title, $menu_link->getTitle());
$this->assertSame($menu, $menu_link->getMenuName());
// The migration sets the description of the link to the value of the
// 'title' attribute. Bit strange, but there you go.
$this->assertSame($description, $menu_link->getDescription());
$this->assertSame($enabled, $menu_link->isEnabled());
$this->assertSame($expanded, $menu_link->isExpanded());
......@@ -120,6 +132,12 @@ 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);
}
}
......@@ -6,6 +6,7 @@
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use Drupal\node\Entity\Node;
use Drupal\KernelTests\KernelTestBase;
/**
......@@ -17,6 +18,22 @@
*/
class LinkUriTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['node', 'user'];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->installEntitySchema('node');
$this->installEntitySchema('user');
}
/**
* Tests LinkUri::transform().
*
......@@ -98,22 +115,70 @@ public function providerTestNotRouted() {
return $tests;
}
/**
* Tests disabling route validation in LinkUri::transform().
*
* @param array $value
* The value to pass to LinkUri::transform().
* @param string $expected
* The expected return value of LinkUri::transform().
*
* @dataProvider providerTestDisablingRouteValidation
*
* @covers ::transform
*/
public function testDisablingRouteValidation(array $value, $expected) {
// Create a node so we have a valid route.
Node::create([
'nid' => 1,
'title' => 'test',
'type' => 'page',
])->save();
$actual = $this->doTransform($value, ['validate_route' => FALSE]);
$this->assertSame($expected, $actual);
}
/**
* Provides test cases for LinkUriTest::testDisablingRouteValidation().
*
* @return array
* An array of test cases, each which the following values:
* - The value array to pass to LinkUri::transform().
* - The expected path returned by LinkUri::transform().
*/
public function providerTestDisablingRouteValidation() {
$tests = [];
$value = ['node/1'];
$expected = 'entity:node/1';
$tests['routed'] = [$value, $expected];
$value = ['node/2'];
$expected = 'base:node/2';
$tests['unrouted'] = [$value, $expected];
return $tests;
}
/**
* Transforms a link path into an 'internal:' or 'entity:' URI.
*
* @param array $value
* The value to pass to LinkUri::transform().
* @param array $configuration
* The plugin configuration.
*
* @return string
* The transformed link.
*/
public function doTransform(array $value) {
public function doTransform(array $value, $configuration = []) {
$entityTypeManager = $this->container->get('entity_type.manager');
$routeBuilder = $this->container->get('router.builder');
$row = new Row();
$executable = $this->prophesize(MigrateExecutableInterface::class)->reveal();
$plugin = new LinkUri([], 'link_uri', [], $entityTypeManager, $routeBuilder);
$plugin = new LinkUri($configuration, 'link_uri', [], $entityTypeManager, $routeBuilder);
$actual = $plugin->transform($value, $executable, $row, 'destinationproperty');
return $actual;
......
......@@ -33719,6 +33719,114 @@
'p9' => '0',
'updated' => '0',
))
->values(array(
'menu_name' => 'primary-links',
'mlid' => '459',
'plid' => '0',
'link_path' => 'node/10',
'router_path' => 'node/%',
'link_title' => 'The Real McCoy',
'options' => 'a:2:{s:10:"attributes";a:1:{s:5:"title";s:0:"";}s:5:"alter";b:1;}',
'module' => 'menu',
'hidden' => '0',
'external' => '0',
'has_children' => '0',
'expanded' => '0',
'weight' => '0',
'depth' => '1',
'customized' => '1',
'p1' => '459',
'p2' => '0',
'p3' => '0',
'p4' => '0',
'p5' => '0',
'p6' => '0',
'p7' => '0',
'p8' => '0',
'p9' => '0',
'updated' => '0',
))
->values(array(
'menu_name' => 'primary-links',
'mlid' => '460',
'plid' => '0',
'link_path' => 'node/11',
'router_path' => 'node/%',
'link_title' => 'Le Vrai McCoy',
'options' => 'a:2:{s:10:"attributes";a:1:{s:5:"title";s:0:"";}s:5:"alter";b:1;}',
'module' => 'menu',
'hidden' => '0',
'external' => '0',
'has_children' => '0',
'expanded' => '0',
'weight' => '0',
'depth' => '1',
'customized' => '1',
'p1' => '460',
'p2' => '0',
'p3' => '0',
'p4' => '0',
'p5' => '0',
'p6' => '0',
'p7' => '0',
'p8' => '0',
'p9' => '0',
'updated' => '0',
))
->values(array(
'menu_name' => 'primary-links',
'mlid' => '461',
'plid' => '0',
'link_path' => 'node/12',
'router_path' => 'node/%',
'link_title' => 'Abantu zulu',
'options' => 'a:2:{s:10:"attributes";a:1:{s:5:"title";s:0:"";}s:5:"alter";b:1;}',
'module' => 'menu',
'hidden' => '0',
'external' => '0',
'has_children' => '0',
'expanded' => '0',
'weight' => '0',
'depth' => '1',
'customized' => '1',
'p1' => '461',
'p2' => '0',
'p3' => '0',
'p4' => '0',
'p5' => '0',
'p6' => '0',
'p7' => '0',
'p8' => '0',
'p9' => '0',
'updated' => '0',
))
->values(array(
'menu_name' => 'primary-links',
'mlid' => '462',
'plid' => '0',
'link_path' => 'node/13',
'router_path' => 'node/%',
'link_title' => 'The Zulu People',
'options' => 'a:2:{s:10:"attributes";a:1:{s:5:"title";s:0:"";}s:5:"alter";b:1;}',
'module' => 'menu',
'hidden' => '0',
'external' => '0',
'has_children' => '0',
'expanded' => '0',
'weight' => '0',
'depth' => '1',
'customized' => '1',
'p1' => '462',
'p2' => '0',
'p3' => '0',
'p4' => '0',
'p5' => '0',
'p6' => '0',
'p7' => '0',
'p8' => '0',
'p9' => '0',
'updated' => '0',
))
->execute();
$connection->schema()->createTable('menu_router', array(
......@@ -23330,6 +23330,114 @@
'p9' => '0',
'updated' => '0',
))
->values(array(
'menu_name' => 'navigation',
'mlid' => '484',
'plid' => '0',
'link_path' => 'node/2',
'router_path' => 'node/%',
'link_title' => 'The thing about Deep Space 9',
'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:0:"";}}',
'module' => 'menu',
'hidden' => '0',
'external' => '0',
'has_children' => '0',
'expanded' => '0',
'weight' => '9',
'depth' => '1',
'customized' => '1',
'p1' => '484',
'p2' => '0',
'p3' => '0',
'p4' => '0',
'p5' => '0',
'p6' => '0',
'p7' => '0',
'p8' => '0',
'p9' => '0',
'updated' => '0',
))
->values(array(
'menu_name' => 'navigation',
'mlid' => '485',
'plid' => '0',
'link_path' => 'node/3',
'router_path' => 'node/%',
'link_title' => 'is - The thing about Deep Space 9',
'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:0:"";}}',
'module' => 'menu',
'hidden' => '0',
'external' => '0',
'has_children' => '0',
'expanded' => '0',
'weight' => '10',
'depth' => '1',
'customized' => '1',
'p1' => '485',
'p2' => '0',
'p3' => '0',
'p4' => '0',
'p5' => '0',
'p6' => '0',
'p7' => '0',
'p8' => '0',
'p9' => '0',
'updated' => '0',
))
->values(array(
'menu_name' => 'navigation',
'mlid' => '486',
'plid' => '0',
'link_path' => 'node/4',
'router_path' => 'node/%',
'link_title' => 'is - The thing about Firefly',
'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:0:"";}}',
'module' => 'menu',
'hidden' => '0',
'external' => '0',
'has_children' => '0',
'expanded' => '0',
'weight' => '11',
'depth' => '1',
'customized' => '1',
'p1' => '486',
'p2' => '0',
'p3' => '0',
'p4' => '0',
'p5' => '0',
'p6' => '0',
'p7' => '0',
'p8' => '0',
'p9' => '0',
'updated' => '0',
))