Commit 0f336ae8 authored by Gábor Hojtsy's avatar Gábor Hojtsy

Issue #2975666 by maxocub, benjifisher, masipila, jcnventura, jigarius,...

Issue #2975666 by maxocub, benjifisher, masipila, jcnventura, jigarius, phenaproxima, catch, quietone: Migrate Drupal 7 node entity translations data to Drupal 8
parent 12215774
......@@ -60,6 +60,12 @@ public function providerSource() {
'translate' => '0',
],
];
$tests[0]['source_data']['field_config'] = [
[
'id' => '1',
'translatable' => '0',
],
];
$tests[0]['source_data']['field_config_instance'] = [
[
'id' => '14',
......
......@@ -69,6 +69,7 @@ public function testLanguageNegotiationWithPrefix() {
$this->assertSame('site_default', $config->get('selected_langcode'));
$expected_prefixes = [
'en' => '',
'fr' => 'fr',
'is' => 'is',
];
$this->assertSame($expected_prefixes, $config->get('url.prefixes'));
......@@ -78,6 +79,7 @@ public function testLanguageNegotiationWithPrefix() {
// language detection configuration from the UI.
$expected_domains = [
'en' => '',
'fr' => '',
'is' => '',
];
$this->assertSame($expected_domains, $config->get('url.domains'));
......@@ -105,6 +107,7 @@ public function testLanguageNegotiationWithDomain() {
$this->assertSame('site_default', $config->get('selected_langcode'));
$expected_domains = [
'en' => parse_url($base_url, PHP_URL_HOST),
'fr' => 'fr.drupal.org',
'is' => 'is.drupal.org',
];
$this->assertSame($expected_domains, $config->get('url.domains'));
......@@ -130,6 +133,7 @@ public function testLanguageNegotiationWithNonExistentVariables() {
$this->assertSame('site_default', $config->get('selected_langcode'));
$expected_prefixes = [
'en' => '',
'fr' => 'fr',
'is' => 'is',
];
$this->assertSame($expected_prefixes, $config->get('url.prefixes'));
......
......@@ -22,13 +22,18 @@ abstract class FieldableEntity extends DrupalSqlBase {
* The field instances, keyed by field name.
*/
protected function getFields($entity_type, $bundle = NULL) {
return $this->select('field_config_instance', 'fci')
$query = $this->select('field_config_instance', 'fci')
->fields('fci')
->condition('entity_type', $entity_type)
->condition('bundle', isset($bundle) ? $bundle : $entity_type)
->condition('deleted', 0)
->execute()
->fetchAllAssoc('field_name');
->condition('fci.entity_type', $entity_type)
->condition('fci.bundle', isset($bundle) ? $bundle : $entity_type)
->condition('fci.deleted', 0);
// Join the 'field_config' table and add the 'translatable' setting to the
// query.
$query->leftJoin('field_config', 'fc', 'fci.field_id = fc.id');
$query->addField('fc', 'translatable');
return $query->execute()->fetchAllAssoc('field_name');
}
/**
......@@ -42,13 +47,13 @@ protected function getFields($entity_type, $bundle = NULL) {
* The entity ID.
* @param int|null $revision_id
* (optional) The entity revision ID.
* @param string $language
* (optional) The field language.
*
* @return array
* The raw field values, keyed by delta.
*
* @todo Support multilingual field values.
*/
protected function getFieldValues($entity_type, $field, $entity_id, $revision_id = NULL) {
protected function getFieldValues($entity_type, $field, $entity_id, $revision_id = NULL, $language = NULL) {
$table = (isset($revision_id) ? 'field_revision_' : 'field_data_') . $field;
$query = $this->select($table, 't')
->fields('t')
......@@ -58,6 +63,11 @@ protected function getFieldValues($entity_type, $field, $entity_id, $revision_id
if (isset($revision_id)) {
$query->condition('revision_id', $revision_id);
}
// Add 'language' as a query condition if it has been defined by Entity
// Translation.
if ($language) {
$query->condition('language', $language);
}
$values = [];
foreach ($query->execute() as $row) {
foreach ($row as $key => $value) {
......@@ -71,4 +81,44 @@ protected function getFieldValues($entity_type, $field, $entity_id, $revision_id
return $values;
}
/**
* Checks if an entity type uses Entity Translation.
*
* @param string $entity_type
* The entity type.
*
* @return bool
* Whether the entity type uses entity translation.
*/
protected function isEntityTranslatable($entity_type) {
return in_array($entity_type, $this->variableGet('entity_translation_entity_types', []), TRUE);
}
/**
* Gets an entity source language from the 'entity_translation' table.
*
* @param string $entity_type
* The entity type.
* @param int $entity_id
* The entity ID.
*
* @return string|bool
* The entity source language or FALSE if no source language was found.
*/
protected function getEntityTranslationSourceLanguage($entity_type, $entity_id) {
try {
return $this->select('entity_translation', 'et')
->fields('et', ['language'])
->condition('entity_type', $entity_type)
->condition('entity_id', $entity_id)
->condition('source', '')
->execute()
->fetchField();
}
// The table might not exist.
catch (\Exception $e) {
return FALSE;
}
}
}
......@@ -3107,6 +3107,42 @@
'created' => '1421727536',
'changed' => '1421727536',
))
->values(array(
'entity_type' => 'node',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'en',
'source' => '',
'uid' => '1',
'status' => '1',
'translate' => '0',
'created' => '1529615790',
'changed' => '1529615790',
))
->values(array(
'entity_type' => 'node',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'fr',
'source' => 'en',
'uid' => '1',
'status' => '1',
'translate' => '0',
'created' => '1529615802',
'changed' => '1529615802',
))
->values(array(
'entity_type' => 'node',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'is',
'source' => 'en',
'uid' => '1',
'status' => '1',
'translate' => '0',
'created' => '1529615813',
'changed' => '1529615813',
))
->values(array(
'entity_type' => 'user',
'entity_id' => '2',
......@@ -5809,6 +5845,26 @@
'delta' => '0',
'field_integer_value' => '5',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'test_content_type',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'fr',
'delta' => '0',
'field_integer_value' => '6',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'test_content_type',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'is',
'delta' => '0',
'field_integer_value' => '7',
))
->values(array(
'entity_type' => 'user',
'bundle' => 'user',
......@@ -9562,6 +9618,26 @@
'delta' => '0',
'field_integer_value' => '5',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'test_content_type',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'fr',
'delta' => '0',
'field_integer_value' => '6',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'test_content_type',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'is',
'delta' => '0',
'field_integer_value' => '7',
))
->values(array(
'entity_type' => 'user',
'bundle' => 'user',
......@@ -12974,6 +13050,19 @@
'weight' => '0',
'javascript' => '',
))
->values(array(
'language' => 'fr',
'name' => 'French',
'native' => 'Français',
'direction' => '0',
'enabled' => '1',
'plurals' => '0',
'formula' => '',
'domain' => 'fr.drupal.org',
'prefix' => 'fr',
'weight' => '0',
'javascript' => '',
))
->values(array(
'language' => 'is',
'name' => 'Icelandic',
......@@ -48305,7 +48394,7 @@
))
->values(array(
'name' => 'language_count',
'value' => 'i:2;',
'value' => 'i:3;',
))
->values(array(
'name' => 'language_default',
......@@ -207,7 +207,7 @@ public function testMigrateUpgradeExecute() {
}
$edits = $this->translatePostValues($edit);
$this->drupalPostForm(NULL, $edits, t('Review upgrade'));
$session->pageTextContains("Install migrate_drupal_multilingual to run migration 'd7_node_translation:article'.");
$session->pageTextContains("Install migrate_drupal_multilingual to run migration 'd7_node_entity_translation:article'.");
}
}
......@@ -64,8 +64,9 @@ protected function getEntityCounts() {
// The 'standard' profile provides the 'comment' comment type, and the
// migration creates 6 comment types, one per node type.
'comment_type' => 7,
// Module 'language' comes with 'en', 'und', 'zxx'. Migration adds 'is'.
'configurable_language' => 4,
// Module 'language' comes with 'en', 'und', 'zxx'. Migration adds 'is'
// and 'fr'.
'configurable_language' => 5,
'contact_form' => 3,
'editor' => 2,
'field_config' => 67,
......
id: d7_node_entity_translation
label: Node entity translations
migration_tags:
- Drupal 7
- translation
- Content
- Multilingual
deriver: Drupal\node\Plugin\migrate\D7NodeDeriver
source:
plugin: d7_node_entity_translation
process:
nid: entity_id
type: type
langcode: language
title: title
uid: uid
status: status
created: created
changed: changed
promote: promote
sticky: sticky
revision_uid: revision_uid
revision_log: log
revision_timestamp: timestamp
content_translation_source: source
destination:
plugin: entity:node
translations: true
destination_module: content_translation
migration_dependencies:
required:
- d7_node
......@@ -104,11 +104,23 @@ public function query() {
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$nid = $row->getSourceProperty('nid');
$vid = $row->getSourceProperty('vid');
$type = $row->getSourceProperty('type');
// If this entity was translated using Entity Translation, we need to get
// its source language to get the field values in the right language.
// The translations will be migrated by the d7_node_entity_translation
// migration.
$entity_translatable = $this->isEntityTranslatable('node') && (int) $this->variableGet('language_content_type_' . $type, 0) === 4;
$language = $entity_translatable ? $this->getEntityTranslationSourceLanguage('node', $nid) : $row->getSourceProperty('language');
// Get Field API field values.
foreach (array_keys($this->getFields('node', $row->getSourceProperty('type'))) as $field) {
$nid = $row->getSourceProperty('nid');
$vid = $row->getSourceProperty('vid');
$row->setSourceProperty($field, $this->getFieldValues('node', $field, $nid, $vid));
foreach ($this->getFields('node', $type) as $field_name => $field) {
// Ensure we're using the right language if the entity and the field are
// translatable.
$field_language = $entity_translatable && $field['translatable'] ? $language : NULL;
$row->setSourceProperty($field_name, $this->getFieldValues('node', $field_name, $nid, $vid, $field_language));
}
// Make sure we always have a translation set.
......
<?php
namespace Drupal\node\Plugin\migrate\source\d7;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
/**
* Provides Drupal 7 node entity translations source plugin.
*
* @MigrateSource(
* id = "d7_node_entity_translation",
* source_module = "entity_translation"
* )
*/
class NodeEntityTranslation extends FieldableEntity {
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('entity_translation', 'et')
->fields('et', [
'entity_id',
'revision_id',
'language',
'source',
'uid',
'status',
'created',
'changed',
])
->fields('n', [
'title',
'type',
'promote',
'sticky',
])
->fields('nr', [
'log',
'timestamp',
])
->condition('et.entity_type', 'node')
->condition('et.source', '', '<>');
$query->addField('nr', 'uid', 'revision_uid');
$query->innerJoin('node', 'n', 'n.nid = et.entity_id');
$query->innerJoin('node_revision', 'nr', 'nr.vid = et.revision_id');
if (isset($this->configuration['node_type'])) {
$query->condition('n.type', $this->configuration['node_type']);
}
return $query;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$nid = $row->getSourceProperty('entity_id');
$vid = $row->getSourceProperty('revision_id');
$type = $row->getSourceProperty('type');
$language = $row->getSourceProperty('language');
// Get Field API field values.
foreach ($this->getFields('node', $type) as $field_name => $field) {
// Ensure we're using the right language if the entity is translatable.
$field_language = $field['translatable'] ? $language : NULL;
$row->setSourceProperty($field_name, $this->getFieldValues('node', $field_name, $nid, $vid, $field_language));
}
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'entity_id' => $this->t('Entity ID'),
'revision_id' => $this->t('Revision ID'),
'language' => $this->t('Node translation language'),
'source' => $this->t('Node translation source language'),
'uid' => $this->t('Node translation authored by (uid)'),
'status' => $this->t('Published'),
'created' => $this->t('Created timestamp'),
'changed' => $this->t('Modified timestamp'),
'title' => $this->t('Node title'),
'type' => $this->t('Node type'),
'promote' => $this->t('Promoted to front page'),
'sticky' => $this->t('Sticky at top of lists'),
'log' => $this->t('Revision log'),
'timestamp' => $this->t('The timestamp the latest revision of this node was created.'),
'revision_uid' => $this->t('Revision authored by (uid)'),
];
}
/**
* {@inheritdoc}
*/
public function getIds() {
return [
'entity_id' => [
'type' => 'integer',
'alias' => 'et',
],
'language' => [
'type' => 'string',
'alias' => 'et',
],
];
}
}
......@@ -70,6 +70,7 @@ protected function setUp() {
'd7_field_instance',
'd7_node',
'd7_node_translation',
'd7_node_entity_translation',
]);
}
......@@ -180,6 +181,12 @@ public function testNode() {
$this->assertEquals(CommentItemInterface::OPEN, $node->comment_node_test_content_type->status);
$this->assertEquals('3.1416', $node->field_float_list[0]->value);
// Test that fields translated with Entity Translation are migrated.
$node_fr = $node->getTranslation('fr');
$this->assertEquals('6', $node_fr->field_integer->value);
$node_is = $node->getTranslation('is');
$this->assertEquals('7', $node_is->field_integer->value);
$node = Node::load(2);
$this->assertEquals('en', $node->langcode->value);
$this->assertEquals("...is that it's the absolute best show ever. Trust me, I would know.", $node->body->value);
......
<?php
namespace Drupal\Tests\node\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests Drupal 7 node entity translations source plugin.
*
* @covers \Drupal\node\Plugin\migrate\source\d7\NodeEntityTranslation
*
* @group node
*/
class NodeEntityTranslationTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['node', 'user', 'migrate_drupal'];
/**
* {@inheritdoc}
*/
public function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['entity_translation'] = [
[
'entity_type' => 'node',
'entity_id' => 2,
'revision_id' => 2,
'language' => 'en',
'source' => '',
'uid' => 1,
'status' => 1,
'translate' => 0,
'created' => 1531343498,
'changed' => 1531343498,
],
[
'entity_type' => 'node',
'entity_id' => 2,
'revision_id' => 2,
'language' => 'fr',
'source' => 'en',
'uid' => 1,
'status' => 1,
'translate' => 0,
'created' => 1531343508,
'changed' => 1531343508,
],
[
'entity_type' => 'node',
'entity_id' => 2,
'revision_id' => 2,
'language' => 'es',
'source' => 'en',
'uid' => 1,
'status' => 1,
'translate' => 0,
'created' => 1531343528,
'changed' => 1531343528,
],
];
$tests[0]['source_data']['field_config'] = [
[
'id' => 1,
'field_name' => 'body',
'type' => 'text_with_summary',
'module' => 'text',
'active' => 1,
'storage_type' => 'field_sql_storage',
'storage_module' => 'field_sql_storage',
'storage_active' => 1,
'locked' => 1,
'data' => 'a:0:{}',
'cardinality' => 1,
'translatable' => 1,
'deleted' => 0,
],
];
$tests[0]['source_data']['field_config_instance'] = [
[
'id' => 1,
'field_id' => 1,
'field_name' => 'body',
'entity_type' => 'node',
'bundle' => 'article',
'data' => 'a:0:{}',
'deleted' => 0,
],
[
'id' => 1,
'field_id' => 1,
'field_name' => 'body',
'entity_type' => 'node',
'bundle' => 'page',
'data' => 'a:0:{}',
'deleted' => 0,
],
];
$tests[0]['source_data']['field_revision_body'] = [
[
'entity_type' => 'node',
'bundle' => 'article',
'deleted' => 0,
'entity_id' => 1,
'revision_id' => 1,
'language' => 'en',
'delta' => 0,
'body_value' => 'Untranslated body',
'body_summary' => 'Untranslated summary',
'body_format' => 'filtered_html',
],
[
'entity_type' => 'node',
'bundle' => 'page',
'deleted' => 0,
'entity_id' => 2,
'revision_id' => 2,
'language' => 'en',
'delta' => 0,
'body_value' => 'English body',
'body_summary' => 'English summary',
'body_format' => 'filtered_html',
],
[
'entity_type' => 'node',
'bundle' => 'page',
'deleted' => 0,
'entity_id' => 2,
'revision_id' => 2,
'language' => 'fr',
'delta' => 0,
'body_value' => 'French body',
'body_summary' => 'French summary',
'body_format' => 'filtered_html',
],
[
'entity_type' => 'node',
'bundle' => 'page',
'deleted' => 0,
'entity_id' => 2,
'revision_id' => 2,
'language' => 'es',
'delta' => 0,
'body_value' => 'Spanish body',
'body_summary' => 'Spanish summary',
'body_format' => 'filtered_html',
],
];
$tests[0]['source_data']['node'] = [
[
'nid' => 1,
'vid' => 1,
'type' => 'article',
'language' => 'en',
'title' => 'Untranslated article',
'uid' => 1,
'status' => 1,
'created' => 1531343456,
'changed' => 1531343456,
'comment' => 2,
'promote' => 1,
'sticky' => 0,
'tnid' => 0,
'translate' => 0,
],
[
'nid' => 2,
'vid' => 2,
'type' => 'page',
'language' => 'en',
'title' => 'Translated page',
'uid' => 1,
'status' => 1,
'created' => 1531343528,
'changed' => 1531343528,
'comment' => 1,
'promote' => 0,
'sticky' => 0,
'tnid' => 0,
'translate' => 0,
],
];
$tests[0]['source_data']['node_revision'] = [
[
'nid' => 1,
'vid' => 1,
'uid' => 1,
'title' => 'Untranslated article',
'log' => '',
'timestamp' => 1531343456,
'status' => 1,
'comment' => 2,
'promote' => 1,
'sticky' => 0,
],
[
'nid' => 2,
'vid' => 2,
'uid' => 1,
'title' => 'Translated page',
'log' => '',