diff --git a/core/modules/aggregator/src/Tests/Migrate/MigrateAggregatorStubTest.php b/core/modules/aggregator/src/Tests/Migrate/MigrateAggregatorStubTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d620259a52bdc649e28bc38072851b4c851dfcd2 --- /dev/null +++ b/core/modules/aggregator/src/Tests/Migrate/MigrateAggregatorStubTest.php @@ -0,0 +1,63 @@ +installEntitySchema('aggregator_feed'); + $this->installEntitySchema('aggregator_item'); + } + + /** + * Tests creation of aggregator feed stubs. + */ + public function testFeedStub() { + $this->performStubTest('aggregator_feed'); + } + + /** + * Tests creation of aggregator feed items. + */ + public function testItemStub() { + try { + // We expect an exception, because there's no feed to reference. + $this->performStubTest('aggregator_item'); + $this->fail('Expected exception has not been thrown.'); + } + catch (MigrateException $e) { + $this->assertIdentical($e->getMessage(), + 'Stubbing failed, unable to generate value for field fid'); + } + + // The stub should pass when there's a feed to point to. + $this->createStub('aggregator_feed'); + $this->performStubTest('aggregator_item'); + } + +} diff --git a/core/modules/block_content/src/Tests/Migrate/MigrateBlockContentStubTest.php b/core/modules/block_content/src/Tests/Migrate/MigrateBlockContentStubTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1971a9da057411dc9593f99bddb09efcb97d5356 --- /dev/null +++ b/core/modules/block_content/src/Tests/Migrate/MigrateBlockContentStubTest.php @@ -0,0 +1,60 @@ +installEntitySchema('block_content'); + } + + /** + * Tests creation of block content stubs with no block_content_type available. + */ + public function testStubFailure() { + $entity_id = $this->createStub('block_content'); + $violations = $this->validateStub('block_content', $entity_id); + $this->assertIdentical(count($violations), 1); + $this->assertEqual($violations[0]->getMessage(), t('The referenced entity (%type: %id) does not exist.', [ + '%type' => 'block_content_type', + '%id' => 'block_content', + ])); + } + + /** + * Tests creation of block content stubs when there is a block_content_type. + */ + public function testStubSuccess() { + BlockContentType::create([ + 'id' => 'test_block_content_type', + 'label' => 'Test block content type', + ])->save(); + $this->performStubTest('block_content'); + } + +} diff --git a/core/modules/comment/src/Plugin/migrate/destination/EntityComment.php b/core/modules/comment/src/Plugin/migrate/destination/EntityComment.php index 477cdcd0ca3ed8fb5775e695a2eb7a6953f0f086..7ebbc83388e77c5c2b5a64554d05d2ea1b1e77bf 100644 --- a/core/modules/comment/src/Plugin/migrate/destination/EntityComment.php +++ b/core/modules/comment/src/Plugin/migrate/destination/EntityComment.php @@ -10,6 +10,7 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\Query\QueryFactory; +use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\State\StateInterface; use Drupal\migrate\Entity\MigrationInterface; use Drupal\migrate\MigrateException; @@ -62,13 +63,15 @@ class EntityComment extends EntityContentBase { * The list of bundles this entity type has. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager service. + * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager + * The field type plugin manager service. * @param \Drupal\Core\State\StateInterface $state * The state storage object. * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query * The query object that can query the given entity type. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, StateInterface $state, QueryFactory $entity_query) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager); + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager, StateInterface $state, QueryFactory $entity_query) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager, $field_type_manager); $this->state = $state; $this->entityQuery = $entity_query; } @@ -86,6 +89,7 @@ public static function create(ContainerInterface $container, array $configuratio $container->get('entity.manager')->getStorage($entity_type), array_keys($container->get('entity.manager')->getBundleInfo($entity_type)), $container->get('entity.manager'), + $container->get('plugin.manager.field.field_type'), $container->get('state'), $container->get('entity.query') ); @@ -110,32 +114,9 @@ public function import(Row $row, array $old_destination_id_values = array()) { */ protected function processStubRow(Row $row) { parent::processStubRow($row); - $stub_commented_entity_type = $row->getDestinationProperty('entity_type'); - - // While parent::getEntity() fills the bundle property for stub entities - // if it's still empty, here we must also make sure entity_id/entity_type - // are filled (so $comment->getCommentedEntity() always returns a value). - if (empty($this->stubCommentedEntityIds[$stub_commented_entity_type])) { - // Fill stub entity id. Any id will do, as long as it exists. - $entity_type = $this->entityManager->getDefinition($stub_commented_entity_type); - $id_key = $entity_type->getKey('id'); - $result = $this->entityQuery - ->get($stub_commented_entity_type) - ->range(0, 1) - ->execute(); - if ($result) { - $this->stubCommentedEntityIds[$stub_commented_entity_type] = array_pop($result); - $row->setSourceProperty($id_key, $this->stubCommentedEntityIds[$stub_commented_entity_type]); - } - else { - throw new MigrateException(t('Could not find parent entity to use for comment %id', ['%id' => implode(':', $row->getSourceIdValues())]), MigrationInterface::MESSAGE_ERROR); - } - } - - $row->setDestinationProperty('entity_id', $this->stubCommentedEntityIds[$stub_commented_entity_type]); - $row->setDestinationProperty('entity_type', $stub_commented_entity_type); - $row->setDestinationProperty('created', REQUEST_TIME); - $row->setDestinationProperty('changed', REQUEST_TIME); + // Neither uid nor name is required in itself, but it is required to set one + // of them. + $row->setDestinationProperty('name', 'anonymous_stub'); } } diff --git a/core/modules/comment/src/Tests/Migrate/MigrateCommentStubTest.php b/core/modules/comment/src/Tests/Migrate/MigrateCommentStubTest.php new file mode 100644 index 0000000000000000000000000000000000000000..22818f585a99f88212da8e719d2e6b57414e93a5 --- /dev/null +++ b/core/modules/comment/src/Tests/Migrate/MigrateCommentStubTest.php @@ -0,0 +1,78 @@ +installEntitySchema('comment'); + $this->installEntitySchema('node'); + // Make sure uid 0 is created (default uid for comments is 0). + $storage = \Drupal::entityManager()->getStorage('user'); + // Insert a row for the anonymous user. + $storage + ->create(array( + 'uid' => 0, + 'status' => 0, + 'name' => '', + )) + ->save(); + // Need at least one node type and comment type present. + NodeType::create([ + 'type' => 'testnodetype', + 'name' => 'Test node type', + ])->save(); + CommentType::create([ + 'id' => 'testcommenttype', + 'label' => 'Test comment type', + 'target_entity_type_id' => 'node', + ])->save(); + } + + /** + * Tests creation of comment stubs. + */ + public function testStub() { + try { + // We expect an exception, because there's no node to reference. + $this->performStubTest('comment'); + $this->fail('Expected exception has not been thrown.'); + } + catch (MigrateException $e) { + $this->assertIdentical($e->getMessage(), + 'Stubbing failed, unable to generate value for field entity_id'); + } + + // The stub should pass when there's a node to point to. + $this->createStub('node'); + $this->performStubTest('comment'); + } + +} diff --git a/core/modules/comment/src/Tests/Migrate/d6/MigrateCommentTest.php b/core/modules/comment/src/Tests/Migrate/d6/MigrateCommentTest.php index 4545e4626886dff4d7fe62e2d25a0cac850c3191..7219d9548890fcdc7261edcbfcca51c170e3dc03 100644 --- a/core/modules/comment/src/Tests/Migrate/d6/MigrateCommentTest.php +++ b/core/modules/comment/src/Tests/Migrate/d6/MigrateCommentTest.php @@ -22,13 +22,7 @@ class MigrateCommentTest extends MigrateDrupal6TestBase { /** * {@inheritdoc} */ - public static $modules = [ - 'comment', - // Directly testing that a stub comment's entity_id is populated upon - // importing is not straightforward, but RDF module serves as an implicit - // test - its hook_comment_storage_load() references a stubbed comment. - 'rdf', - ]; + public static $modules = ['comment']; /** * {@inheritdoc} diff --git a/core/modules/file/src/Plugin/migrate/destination/EntityFile.php b/core/modules/file/src/Plugin/migrate/destination/EntityFile.php index c5e898948bca0a0b208d5f5d7b7175b5c8399bca..576b38b2643e75c19a430278bb7b09e1fda512d4 100644 --- a/core/modules/file/src/Plugin/migrate/destination/EntityFile.php +++ b/core/modules/file/src/Plugin/migrate/destination/EntityFile.php @@ -9,6 +9,8 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Field\FieldTypePluginManagerInterface; +use Drupal\Core\Field\Plugin\Field\FieldType\UriItem; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\StreamWrapper\LocalStream; use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; @@ -41,7 +43,7 @@ class EntityFile extends EntityContentBase { /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system) { $configuration += array( 'source_base_path' => '', 'source_path_property' => 'filepath', @@ -49,7 +51,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition 'move' => FALSE, 'urlencode' => FALSE, ); - parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager); + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager, $field_type_manager); $this->streamWrapperManager = $stream_wrappers; $this->fileSystem = $file_system; @@ -68,6 +70,7 @@ public static function create(ContainerInterface $container, array $configuratio $container->get('entity.manager')->getStorage($entity_type), array_keys($container->get('entity.manager')->getBundleInfo($entity_type)), $container->get('entity.manager'), + $container->get('plugin.manager.field.field_type'), $container->get('stream_wrapper_manager'), $container->get('file_system') ); @@ -77,6 +80,12 @@ public static function create(ContainerInterface $container, array $configuratio * {@inheritdoc} */ protected function getEntity(Row $row, array $old_destination_id_values) { + // For stub rows, there is no real file to deal with, let the stubbing + // process take its default path. + if ($row->isStub()) { + return parent::getEntity($row, $old_destination_id_values); + } + $destination = $row->getDestinationProperty($this->configuration['destination_path_property']); $entity = $this->storage->loadByProperties(['uri' => $destination]); if ($entity) { @@ -91,6 +100,12 @@ protected function getEntity(Row $row, array $old_destination_id_values) { * {@inheritdoc} */ public function import(Row $row, array $old_destination_id_values = array()) { + // For stub rows, there is no real file to deal with, let the stubbing + // process create the stub entity. + if ($row->isStub()) { + return parent::import($row, $old_destination_id_values); + } + $file = $row->getSourceProperty($this->configuration['source_path_property']); $destination = $row->getDestinationProperty($this->configuration['destination_path_property']); $source = $this->configuration['source_base_path'] . $file; @@ -256,4 +271,29 @@ protected function urlencode($filename) { return $filename; } + /** + * {@inheritdoc} + */ + protected function processStubRow(Row $row) { + // We stub the uri value ourselves so we can create a real stub file for it. + if (!$row->getDestinationProperty('uri')) { + $field_definitions = $this->entityManager + ->getFieldDefinitions($this->storage->getEntityTypeId(), + $this->getKey('bundle')); + $value = UriItem::generateSampleValue($field_definitions['uri']); + if (empty($value)) { + throw new MigrateException('Stubbing failed, unable to generate value for field uri'); + } + // generateSampleValue() wraps the value in an array. + $value = reset($value); + // Make it into a proper public file uri, stripping off the existing + // scheme if present. + $value = 'public://' . preg_replace('|^[a-z]+://|i', '', $value); + // Create a real file, so File::preSave() can do filesize() on it. + touch($value); + $row->setDestinationProperty('uri', $value); + } + parent::processStubRow($row); + } + } diff --git a/core/modules/file/src/Plugin/migrate/process/d6/FileUri.php b/core/modules/file/src/Plugin/migrate/process/d6/FileUri.php index b464ee00425316abb00ea087fdf33ec0b7ad45d0..912371c25edd0a43333d5c3a01f2401ff866e338 100644 --- a/core/modules/file/src/Plugin/migrate/process/d6/FileUri.php +++ b/core/modules/file/src/Plugin/migrate/process/d6/FileUri.php @@ -24,6 +24,11 @@ class FileUri extends ProcessPluginBase { * {@inheritdoc} */ public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { + // If we're stubbing a file entity, return a uri of NULL so it will get + // stubbed by the general process. + if ($row->isStub()) { + return NULL; + } list($filepath, $file_directory_path, $temp_directory_path, $is_public) = $value; // Specific handling using $temp_directory_path for temporary files. diff --git a/core/modules/file/src/Tests/Migrate/MigrateFileStubTest.php b/core/modules/file/src/Tests/Migrate/MigrateFileStubTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ae7446c877a22aa5c53b5d0d519ebbe422f03b01 --- /dev/null +++ b/core/modules/file/src/Tests/Migrate/MigrateFileStubTest.php @@ -0,0 +1,42 @@ +installEntitySchema('file'); + } + + /** + * Tests creation of file stubs. + */ + public function testStub() { + $this->performStubTest('file'); + } + +} diff --git a/core/modules/menu_link_content/src/Tests/Migrate/MigrateMenuLinkContentStubTest.php b/core/modules/menu_link_content/src/Tests/Migrate/MigrateMenuLinkContentStubTest.php new file mode 100644 index 0000000000000000000000000000000000000000..47e94d13409ad58141bb439d5827d82297c3162c --- /dev/null +++ b/core/modules/menu_link_content/src/Tests/Migrate/MigrateMenuLinkContentStubTest.php @@ -0,0 +1,42 @@ +installEntitySchema('menu_link_content'); + } + + /** + * Tests creation of menu link content stubs. + */ + public function testStub() { + $this->performStubTest('menu_link_content'); + } + +} diff --git a/core/modules/migrate/src/MigrateExecutable.php b/core/modules/migrate/src/MigrateExecutable.php index 2749313d32542f3b56602eee96a75efbd02f4d1f..a3706239e757e80fccd4a3fe2681f8b34b54b59c 100644 --- a/core/modules/migrate/src/MigrateExecutable.php +++ b/core/modules/migrate/src/MigrateExecutable.php @@ -401,8 +401,8 @@ public function processRow(Row $row, array $process = NULL, $value = NULL) { $multiple = $multiple || $plugin->multiple(); } } - // No plugins means do not set. - if ($plugins) { + // No plugins or no value means do not set. + if ($plugins && !is_null($value)) { $row->setDestinationProperty($destination, $value); } // Reset the value. diff --git a/core/modules/migrate/src/Plugin/migrate/destination/Entity.php b/core/modules/migrate/src/Plugin/migrate/destination/Entity.php index 64123d6d448b9be6b14003a093647dd665cec67c..f7a6a1e93ff3e776e5ab56ed9afb4a4f5b38fa2f 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/Entity.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/Entity.php @@ -136,19 +136,6 @@ protected function getEntityId(Row $row) { return $row->getDestinationProperty($this->getKey('id')); } - /** - * Process the stub values. - * - * @param \Drupal\migrate\Row $row - * The row of data. - */ - protected function processStubRow(Row $row) { - $bundle_key = $this->getKey('bundle'); - if ($bundle_key && empty($row->getDestinationProperty($bundle_key))) { - $row->setDestinationProperty($bundle_key, reset($this->bundles)); - } - } - /** * Returns a specific entity key. * diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php index ae3030a11b2cf616dfcb1649be96d8ff579c0bca..4dfd6c6e0ab37a9929698879ff6a50877d21b0db 100644 --- a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php @@ -7,12 +7,17 @@ namespace Drupal\migrate\Plugin\migrate\destination; +use Drupal\Component\Utility\Random; +use Drupal\Component\Utility\Unicode; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\link\LinkItemInterface; use Drupal\migrate\Entity\MigrationInterface; +use Drupal\migrate\MigrateException; use Drupal\migrate\Plugin\MigrateIdMapInterface; use Drupal\migrate\Row; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -29,6 +34,13 @@ class EntityContentBase extends Entity { */ protected $entityManager; + /** + * Field type plugin manager. + * + * @var \Drupal\Core\Field\FieldTypePluginManagerInterface + */ + protected $fieldTypeManager; + /** * Constructs a content entity. * @@ -46,10 +58,13 @@ class EntityContentBase extends Entity { * The list of bundles this entity type has. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager service. + * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager + * The field type plugin manager service. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager) { parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles); $this->entityManager = $entity_manager; + $this->fieldTypeManager = $field_type_manager; } /** @@ -64,7 +79,8 @@ public static function create(ContainerInterface $container, array $configuratio $migration, $container->get('entity.manager')->getStorage($entity_type), array_keys($container->get('entity.manager')->getBundleInfo($entity_type)), - $container->get('entity.manager') + $container->get('entity.manager'), + $container->get('plugin.manager.field.field_type') ); } @@ -132,4 +148,44 @@ protected function updateEntity(EntityInterface $entity, Row $row) { $this->setRollbackAction($row->getIdMap()); } + /** + * Do as much population of the stub row as we can. + * + * @param \Drupal\migrate\Row $row + * The row of data. + */ + protected function processStubRow(Row $row) { + $bundle_key = $this->getKey('bundle'); + if ($bundle_key && empty($row->getDestinationProperty($bundle_key))) { + $row->setDestinationProperty($bundle_key, reset($this->bundles)); + } + + // Populate any required fields not already populated. + $fields = $this->entityManager + ->getFieldDefinitions($this->storage->getEntityTypeId(), $bundle_key); + foreach ($fields as $field_name => $field_definition) { + if ($field_definition->isRequired() && is_null($row->getDestinationProperty($field_name))) { + // Use the configured default value for this specific field, if any. + if ($default_value = $field_definition->getDefaultValueLiteral()) { + $values[] = $default_value; + } + else { + // Otherwise, ask the field type to generate a sample value. + $field_type = $field_definition->getType(); + /** @var \Drupal\Core\Field\FieldItemInterface $field_type_class */ + $field_type_class = $this->fieldTypeManager + ->getPluginClass($field_definition->getType()); + $values = $field_type_class::generateSampleValue($field_definition); + if (is_null($values)) { + // Handle failure to generate a sample value. + throw new MigrateException('Stubbing failed, unable to generate value for field ' . $field_name); + break; + } + } + + $row->setDestinationProperty($field_name, $values); + } + } + } + } diff --git a/core/modules/migrate/src/Row.php b/core/modules/migrate/src/Row.php index a509da479317498f50b4544f1742cbddcf42eeed..4e788afecccfbdf1f9ffb2073426f2e1d7d0a04d 100644 --- a/core/modules/migrate/src/Row.php +++ b/core/modules/migrate/src/Row.php @@ -73,7 +73,7 @@ class Row { * * @see getRawDestination() */ - protected $rawDestination; + protected $rawDestination = []; /** * TRUE when this row is a stub. diff --git a/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php b/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php index d5f71a072c376cadb4f1c95b20f4e5789dee70df..2db5d9e8e3aa06b1c07238fdf8e13529cb584f34 100644 --- a/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php +++ b/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php @@ -36,6 +36,11 @@ class EntityRevisionTest extends UnitTestCase { */ protected $entityManager; + /** + * @var \Drupal\Core\Field\FieldTypePluginManagerInterface + */ + protected $fieldTypeManager; + public function setUp() { parent::setUp(); @@ -43,6 +48,7 @@ public function setUp() { $this->migration = $this->prophesize('\Drupal\migrate\Entity\MigrationInterface'); $this->storage = $this->prophesize('\Drupal\Core\Entity\EntityStorageInterface'); $this->entityManager = $this->prophesize('\Drupal\Core\Entity\EntityManagerInterface'); + $this->fieldTypeManager = $this->prophesize('\Drupal\Core\Field\FieldTypePluginManagerInterface'); } /** @@ -183,7 +189,9 @@ protected function getEntityRevisionDestination(array $configuration = [], $plug $this->migration->reveal(), $this->storage->reveal(), [], - $this->entityManager->reveal()); + $this->entityManager->reveal(), + $this->fieldTypeManager->reveal() + ); } } diff --git a/core/modules/migrate_drupal/src/Tests/StubTestTrait.php b/core/modules/migrate_drupal/src/Tests/StubTestTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..1e028bfff542251800d94ef12aa7986b466e6f38 --- /dev/null +++ b/core/modules/migrate_drupal/src/Tests/StubTestTrait.php @@ -0,0 +1,80 @@ +createStub($entity_type_id); + $this->assertTrue($entity_id, 'Stub successfully created'); + if ($entity_id) { + $violations = $this->validateStub($entity_type_id, $entity_id); + if (!$this->assertIdentical(count($violations), 0, 'Stub is a valid entity')) { + foreach ($violations as $violation) { + $this->fail((string) $violation->getMessage()); + } + } + } + } + + /** + * Create a stub of the given entity type. + * + * @param string $entity_type_id + * The entity type we are stubbing. + * + * @return int + * ID of the created entity. + */ + protected function createStub($entity_type_id) { + // Create a dummy migration to pass to the destination plugin. + $config = [ + 'id' => 'dummy', + 'migration_tags' => ['Stub test'], + 'source' => ['plugin' => 'empty'], + 'process' => [], + 'destination' => ['plugin' => 'entity:' . $entity_type_id], + ]; + $migration = Migration::create($config); + $destination_plugin = $migration->getDestinationPlugin(TRUE); + $stub_row = new Row([], [], TRUE); + $destination_ids = $destination_plugin->import($stub_row); + return reset($destination_ids); + } + + /** + * Perform validation on a stub entity. + * + * @param string $entity_type_id + * The entity type we are stubbing. + * @param string $entity_id + * ID of the stubbed entity to validate. + * + * @return \Drupal\Core\Entity\EntityConstraintViolationListInterface + * List of constraint violations identified. + */ + protected function validateStub($entity_type_id, $entity_id) { + $controller = \Drupal::entityManager()->getStorage($entity_type_id); + /** @var \Drupal\Core\Entity\ContentEntityInterface $stub_entity */ + $stub_entity = $controller->load($entity_id); + return $stub_entity->validate(); + } + +} diff --git a/core/modules/node/src/Tests/Migrate/MigrateNodeStubTest.php b/core/modules/node/src/Tests/Migrate/MigrateNodeStubTest.php new file mode 100644 index 0000000000000000000000000000000000000000..22d140bf3e568ce064147af5abd5552e008819b6 --- /dev/null +++ b/core/modules/node/src/Tests/Migrate/MigrateNodeStubTest.php @@ -0,0 +1,48 @@ +installEntitySchema('node'); + // Need at least one node type present. + NodeType::create([ + 'type' => 'testnodetype', + 'name' => 'Test node type', + ])->save(); + } + + /** + * Tests creation of node stubs. + */ + public function testStub() { + $this->performStubTest('node'); + } + +} diff --git a/core/modules/shortcut/src/Tests/Migrate/MigrateShortcutStubTest.php b/core/modules/shortcut/src/Tests/Migrate/MigrateShortcutStubTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ba9a0002e56f01bd63e7d0bfa4686388741525d5 --- /dev/null +++ b/core/modules/shortcut/src/Tests/Migrate/MigrateShortcutStubTest.php @@ -0,0 +1,44 @@ +installEntitySchema('shortcut'); + // Make sure the 'default' shortcut_set is installed. + $this->installConfig(['shortcut']); + } + + /** + * Tests creation of shortcut stubs. + */ + public function testStub() { + $this->performStubTest('shortcut'); + } + +} diff --git a/core/modules/taxonomy/src/Plugin/migrate/destination/EntityTaxonomyTerm.php b/core/modules/taxonomy/src/Plugin/migrate/destination/EntityTaxonomyTerm.php deleted file mode 100644 index 0a28f5ba7118d7e7c6c6b5846d8b291d48650295..0000000000000000000000000000000000000000 --- a/core/modules/taxonomy/src/Plugin/migrate/destination/EntityTaxonomyTerm.php +++ /dev/null @@ -1,30 +0,0 @@ -isStub()) { - $row->setDestinationProperty('name', $this->t('Stub name for source tid:') . $row->getSourceProperty('tid')); - } - return parent::getEntity($row, $old_destination_id_values); - } - -} diff --git a/core/modules/taxonomy/src/Tests/Migrate/MigrateTaxonomyTermStubTest.php b/core/modules/taxonomy/src/Tests/Migrate/MigrateTaxonomyTermStubTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a7cd9ca7fcb8e07ac03cc678028427418cdb4159 --- /dev/null +++ b/core/modules/taxonomy/src/Tests/Migrate/MigrateTaxonomyTermStubTest.php @@ -0,0 +1,118 @@ +installEntitySchema('taxonomy_term'); + } + + /** + * Tests creation of taxonomy term stubs. + */ + public function testStub() { + Vocabulary::create([ + 'vid' => 'test_vocabulary', + 'name' => 'Test vocabulary', + ])->save(); + $this->performStubTest('taxonomy_term'); + } + + /** + * Tests creation of stubs when weight is mapped. + */ + public function testStubWithWeightMapping() { + // Create a vocabulary via migration for the terms to reference. + $vocabulary_data_rows = [ + ['id' => '1', 'name' => 'tags'], + ]; + $ids = ['id' => ['type' => 'integer']]; + $config = [ + 'id' => 'vocabularies', + 'migration_tags' => ['Stub test'], + 'source' => [ + 'plugin' => 'embedded_data', + 'data_rows' => $vocabulary_data_rows, + 'ids' => $ids, + ], + 'process' => [ + 'vid' => 'id', + 'name' => 'name', + ], + 'destination' => ['plugin' => 'entity:taxonomy_vocabulary'], + ]; + $vocabulary_migration = Migration::create($config); + $vocabulary_executable = new MigrateExecutable($vocabulary_migration, $this); + $vocabulary_executable->import(); + + // We have a term referencing an unmigrated parent, forcing a stub to be + // created. + $term_data_rows = [ + ['id' => '1', 'vocab' => '1', 'name' => 'music', 'parent' => '2'], + ]; + $ids = ['id' => ['type' => 'integer']]; + $config = [ + 'id' => 'terms', + 'migration_tags' => ['Import and rollback test'], + 'source' => [ + 'plugin' => 'embedded_data', + 'data_rows' => $term_data_rows, + 'ids' => $ids, + ], + 'process' => [ + 'tid' => 'id', + 'vid' => 'vocab', + 'name' => 'name', + 'weight' => 'weight', + 'parent' => [ + 'plugin' => 'migration', + 'migration' => 'terms', + 'source' => 'parent', + ], + ], + 'destination' => ['plugin' => 'entity:taxonomy_term'], + 'migration_dependencies' => ['required' => ['vocabularies']], + ]; + + $term_migration = Migration::create($config); + $term_migration->save(); + $term_executable = new MigrateExecutable($term_migration, $this); + $term_executable->import(); + // Load the referenced term, which should exist as a stub. + /** @var \Drupal\Core\Entity\ContentEntityBase $stub_entity */ + $stub_entity = Term::load(2); + $this->assertTrue($stub_entity, 'Stub successfully created'); + if ($stub_entity) { + $this->assertIdentical(count($stub_entity->validate()), 0, 'Stub is a valid entity'); + } + } +} diff --git a/core/modules/user/src/Plugin/migrate/destination/EntityUser.php b/core/modules/user/src/Plugin/migrate/destination/EntityUser.php index 785a3b53044ac2824cae72d4d865532387bcb7be..beda7c806f08e66150a37cb3c017bb9e1bc49524 100644 --- a/core/modules/user/src/Plugin/migrate/destination/EntityUser.php +++ b/core/modules/user/src/Plugin/migrate/destination/EntityUser.php @@ -7,8 +7,11 @@ namespace Drupal\user\Plugin\migrate\destination; +use Drupal\Component\Utility\Unicode; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Field\FieldTypePluginManagerInterface; +use Drupal\Core\Field\Plugin\Field\FieldType\EmailItem; use Drupal\Core\Password\PasswordInterface; use Drupal\migrate\Entity\MigrationInterface; use Drupal\migrate\MigrateException; @@ -50,11 +53,13 @@ class EntityUser extends EntityContentBase { * The migrate plugin manager. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager service. + * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager + * The field type plugin manager service. * @param \Drupal\Core\Password\PasswordInterface $password * The password service. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, PasswordInterface $password) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager); + public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager, PasswordInterface $password) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager, $field_type_manager); if (isset($configuration['md5_passwords'])) { $this->password = $password; } @@ -73,6 +78,7 @@ public static function create(ContainerInterface $container, array $configuratio $container->get('entity.manager')->getStorage($entity_type), array_keys($container->get('entity.manager')->getBundleInfo($entity_type)), $container->get('entity.manager'), + $container->get('plugin.manager.field.field_type'), $container->get('password') ); } @@ -102,4 +108,28 @@ public function import(Row $row, array $old_destination_id_values = array()) { return $ids; } + /** + * {@inheritdoc} + */ + protected function processStubRow(Row $row) { + parent::processStubRow($row); + // Email address is not defined as required in the base field definition but + // is effectively required by the UserMailRequired constraint. This means + // that Entity::processStubRow() did not populate it - we do it here. + $field_definitions = $this->entityManager + ->getFieldDefinitions($this->storage->getEntityTypeId(), + $this->getKey('bundle')); + $mail = EmailItem::generateSampleValue($field_definitions['mail']); + $row->setDestinationProperty('mail', reset($mail)); + + // @todo Work-around for https://www.drupal.org/node/2602066. + $name = $row->getDestinationProperty('name'); + if (is_array($name)) { + $name = reset($name); + } + if (Unicode::strlen($name) > USERNAME_MAX_LENGTH) { + $row->setDestinationProperty('name', Unicode::substr($name, 0, USERNAME_MAX_LENGTH)); + } + } + } diff --git a/core/modules/user/src/Tests/Migrate/MigrateUserStubTest.php b/core/modules/user/src/Tests/Migrate/MigrateUserStubTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0a65ddf2d2b1a2265dce991642f00a9a362e3e0a --- /dev/null +++ b/core/modules/user/src/Tests/Migrate/MigrateUserStubTest.php @@ -0,0 +1,43 @@ +installEntitySchema('user'); + $this->installSchema('system', ['sequences']); + } + + /** + * Tests creation of user stubs. + */ + public function testStub() { + $this->performStubTest('user'); + } + +}