Commit 9eb79881 authored by catch's avatar catch

Issue #2921661 by heddn, maxocub, alexpott, phenaproxima, Jo Fitzgerald,...

Issue #2921661 by heddn, maxocub, alexpott, phenaproxima, Jo Fitzgerald, badmetevils, quietone: Add support to migrate multilingual revisions
parent 2dc41c87
......@@ -148,7 +148,7 @@ public function import(Row $row, array $old_destination_id_values = []) {
}
$ids = $this->save($entity, $old_destination_id_values);
if (!empty($this->configuration['translations'])) {
if ($this->isTranslationDestination()) {
$ids[] = $entity->language()->getId();
}
return $ids;
......@@ -181,12 +181,15 @@ public function isTranslationDestination() {
* {@inheritdoc}
*/
public function getIds() {
$ids = [];
$id_key = $this->getKey('id');
$ids[$id_key] = $this->getDefinitionFromEntity($id_key);
if ($this->isTranslationDestination()) {
if (!$langcode_key = $this->getKey('langcode')) {
throw new MigrateException('This entity type does not support translation.');
$langcode_key = $this->getKey('langcode');
if (!$langcode_key) {
throw new MigrateException(sprintf('The "%s" entity type does not support translations.', $this->storage->getEntityTypeId()));
}
$ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key);
}
......@@ -202,8 +205,8 @@ public function getIds() {
* @param \Drupal\migrate\Row $row
* The row object to update from.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* An updated entity, or NULL if it's the same as the one passed in.
* @return \Drupal\Core\Entity\EntityInterface
* An updated entity from row values.
*/
protected function updateEntity(EntityInterface $entity, Row $row) {
$empty_destinations = $row->getEmptyDestinationProperties();
......
......@@ -160,7 +160,9 @@ protected function getEntity(Row $row, array $old_destination_id_values) {
$entity->enforceIsNew(FALSE);
$entity->setNewRevision(TRUE);
}
$this->updateEntity($entity, $row);
// We need to update the entity, so that the destination row IDs are
// correct.
$entity = $this->updateEntity($entity, $row);
$entity->isDefaultRevision(FALSE);
return $entity;
}
......@@ -177,10 +179,23 @@ protected function save(ContentEntityInterface $entity, array $old_destination_i
* {@inheritdoc}
*/
public function getIds() {
if ($key = $this->getKey('revision')) {
return [$key => $this->getDefinitionFromEntity($key)];
$ids = [];
$revision_key = $this->getKey('revision');
if (!$revision_key) {
throw new MigrateException(sprintf('The "%s" entity type does not support revisions.', $this->storage->getEntityTypeId()));
}
throw new MigrateException('This entity type does not support revisions.');
$ids[$revision_key] = $this->getDefinitionFromEntity($revision_key);
if ($this->isTranslationDestination()) {
$langcode_key = $this->getKey('langcode');
if (!$langcode_key) {
throw new MigrateException(sprintf('The "%s" entity type does not support translations.', $this->storage->getEntityTypeId()));
}
$ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key);
}
return $ids;
}
/**
......
<?php
namespace Drupal\Tests\migrate\Kernel\Plugin;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\Tests\migrate\Kernel\MigrateTestBase;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
/**
* Tests the EntityRevision destination plugin.
*
* @group migrate
*/
class EntityRevisionTest extends MigrateTestBase {
use ContentTypeCreationTrait;
/**
* {@inheritdoc}
*/
public static $modules = [
'content_translation',
'field',
'filter',
'language',
'node',
'system',
'text',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig('node');
$this->installSchema('node', ['node_access']);
$this->installEntitySchema('node');
$this->installEntitySchema('user');
}
/**
* Tests that EntityRevision correctly handles revision translations.
*/
public function testRevisionTranslation() {
ConfigurableLanguage::createFromLangcode('fr')->save();
/** @var \Drupal\node\NodeInterface $node */
$node = Node::create([
'type' => $this->createContentType()->id(),
'title' => 'Default 1',
]);
$node->addTranslation('fr', [
'title' => 'French 1',
]);
$node->save();
$node->setNewRevision();
$node->setTitle('Default 2');
$node->getTranslation('fr')->setTitle('French 2');
$node->save();
$migration = [
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
[
'nid' => $node->id(),
'vid' => $node->getRevisionId(),
'langcode' => 'fr',
'title' => 'Titre nouveau, tabarnak!',
],
],
'ids' => [
'nid' => [
'type' => 'integer',
],
'vid' => [
'type' => 'integer',
],
'langcode' => [
'type' => 'string',
],
],
],
'process' => [
'nid' => 'nid',
'vid' => 'vid',
'langcode' => 'langcode',
'title' => 'title',
],
'destination' => [
'plugin' => 'entity_revision:node',
'translations' => TRUE,
],
];
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
$migration = $this->container
->get('plugin.manager.migration')
->createStubMigration($migration);
$this->executeMigration($migration);
// The entity_revision destination uses the revision ID and langcode as its
// keys (the langcode is only used if the destination is configured for
// translation), so we should be able to look up the source IDs by revision
// ID and langcode.
$source_ids = $migration->getIdMap()->lookupSourceID([
'vid' => $node->getRevisionId(),
'langcode' => 'fr',
]);
$this->assertNotEmpty($source_ids);
$this->assertSame($node->id(), $source_ids['nid']);
$this->assertSame($node->getRevisionId(), $source_ids['vid']);
$this->assertSame('fr', $source_ids['langcode']);
// Confirm the french revision was used in the migration, instead of the
// default revision.
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
$entity_type_manager = \Drupal::entityTypeManager();
$revision = $entity_type_manager->getStorage('node')->loadRevision(1);
$this->assertSame('Default 1', $revision->label());
$this->assertSame('French 1', $revision->getTranslation('fr')->label());
$revision = $entity_type_manager->getStorage('node')->loadRevision(2);
$this->assertSame('Default 2', $revision->label());
$this->assertSame('Titre nouveau, tabarnak!', $revision->getTranslation('fr')->label());
}
}
......@@ -60,6 +60,7 @@ protected function setUp() {
$this->entityType = $this->prophesize(EntityTypeInterface::class);
$this->entityType->getPluralLabel()->willReturn('wonkiness');
$this->storage->getEntityType()->willReturn($this->entityType->reveal());
$this->storage->getEntityTypeId()->willReturn('foo');
$this->entityManager = $this->prophesize(EntityManagerInterface::class);
}
......@@ -129,7 +130,7 @@ public function testUntranslatable() {
$this->entityManager->reveal(),
$this->prophesize(FieldTypePluginManagerInterface::class)->reveal()
);
$this->setExpectedException(MigrateException::class, 'This entity type does not support translation');
$this->setExpectedException(MigrateException::class, 'The "foo" entity type does not support translations.');
$destination->getIds();
}
......
<?php
namespace Drupal\Tests\migrate\Unit\Plugin\migrate\destination;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\migrate\destination\EntityRevision;
use Drupal\migrate\Row;
use Drupal\Tests\UnitTestCase;
/**
* Tests entity revision destination functionality.
*
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\destination\EntityRevision
* @group migrate
*/
class EntityRevisionTest extends UnitTestCase {
/**
* The migration.
*
* @var \Drupal\migrate\Plugin\MigrationInterface
*/
protected $migration;
/**
* The entity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storage;
/**
* The entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->migration = $this->prophesize(MigrationInterface::class);
$this->storage = $this->prophesize(EntityStorageInterface::class);
$this->entityType = $this->prophesize(EntityTypeInterface::class);
$this->entityType->getSingularLabel()->willReturn('foo');
$this->entityType->getPluralLabel()->willReturn('bar');
$this->storage->getEntityType()->willReturn($this->entityType->reveal());
$this->storage->getEntityTypeId()->willReturn('foo');
$this->entityManager = $this->prophesize(EntityManagerInterface::class);
}
/**
* Tests that revision destination fails for unrevisionable entities.
*/
public function testUnrevisionable() {
$this->entityType->getKey('id')->willReturn('id');
$this->entityType->getKey('revision')->willReturn('');
$this->entityManager->getBaseFieldDefinitions('foo')
->willReturn([
'id' => BaseFieldDefinitionTest::create('integer'),
]);
$destination = new EntityRevisionTestDestination(
[],
'',
[],
$this->migration->reveal(),
$this->storage->reveal(),
[],
$this->entityManager->reveal(),
$this->prophesize(FieldTypePluginManagerInterface::class)->reveal()
);
$this->setExpectedException(MigrateException::class, 'The "foo" entity type does not support revisions.');
$destination->getIds();
}
/**
* Tests that translation destination fails for untranslatable entities.
*/
public function testUntranslatable() {
$this->entityType->getKey('id')->willReturn('id');
$this->entityType->getKey('revision')->willReturn('vid');
$this->entityType->getKey('langcode')->willReturn('');
$this->entityManager->getBaseFieldDefinitions('foo')
->willReturn([
'id' => BaseFieldDefinitionTest::create('integer'),
'vid' => BaseFieldDefinitionTest::create('integer'),
]);
$destination = new EntityRevisionTestDestination(
['translations' => TRUE],
'',
[],
$this->migration->reveal(),
$this->storage->reveal(),
[],
$this->entityManager->reveal(),
$this->prophesize(FieldTypePluginManagerInterface::class)->reveal()
);
$this->setExpectedException(MigrateException::class, 'The "foo" entity type does not support translations.');
$destination->getIds();
}
}
/**
* Stub class for testing EntityRevision methods.
*/
class EntityRevisionTestDestination extends EntityRevision {
private $entity = NULL;
public function setEntity($entity) {
$this->entity = $entity;
}
protected function getEntity(Row $row, array $old_destination_id_values) {
return $this->entity;
}
public static function getEntityTypeId($plugin_id) {
return 'foo';
}
}
/**
* Stub class for BaseFieldDefinition.
*/
class BaseFieldDefinitionTest extends BaseFieldDefinition {
public static function create($type) {
return new static([]);
}
public function getSettings() {
return [];
}
public function getType() {
return 'integer';
}
}
......@@ -229,6 +229,8 @@ public function save(ContentEntityInterface $entity, array $old_destination_id_v
* workings of its implementation which would trickle into mock assertions. An
* empty implementation avoids this.
*/
protected function updateEntity(EntityInterface $entity, Row $row) {}
protected function updateEntity(EntityInterface $entity, Row $row) {
return $entity;
}
}
......@@ -115,7 +115,7 @@ public function testUntranslatable() {
// Match the expected message. Can't use default argument types, because
// we need to convert to string from TranslatableMarkup.
$argument = Argument::that(function ($msg) {
return strpos((string) $msg, "This entity type does not support translation") !== FALSE;
return strpos((string) $msg, htmlentities('The "no_language_entity_test" entity type does not support translations.')) !== FALSE;
});
$message->display($argument, Argument::any())
->shouldBeCalled();
......
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