diff -u b/core/modules/migrate_drupal/src/Plugin/migrate/source/ContentEntity.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/ContentEntity.php --- b/core/modules/migrate_drupal/src/Plugin/migrate/source/ContentEntity.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/ContentEntity.php @@ -39,18 +39,21 @@ protected $entityFieldManager; /** - * Static cache for bundle fields. + * The entity type definition. * - * @var array + * @var \Drupal\Core\Entity\EntityTypeInterface */ - protected $bundleFields = []; + protected $entityType; /** - * The entity definition. + * The plugin's default configuration. * - * @var \Drupal\Core\Entity\EntityTypeInterface + * @var array */ - protected $entityType; + protected $defaultConfiguration = [ + 'bundle' => NULL, + 'exclude_translations' => FALSE, + ]; /** * {@inheritdoc} @@ -68,7 +71,7 @@ if (!empty($this->configuration['bundle']) && !$this->entityType->hasKey('bundle')) { throw new InvalidPluginDefinitionException('A bundle was provided but the entity type is not bundleable.'); } - parent::__construct($configuration + ['bundle' => NULL], $plugin_id, $plugin_definition, $migration); + parent::__construct($configuration + $this->defaultConfiguration, $plugin_id, $plugin_definition, $migration); } /** @@ -89,7 +92,7 @@ * {@inheritdoc} */ public function __toString() { - return (string) sprintf('%s source', $this->entityType->getLabel()); + return (string) $this->entityType->getPluralLabel(); } /** @@ -119,14 +122,16 @@ /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ $entity = $storage->load($id); yield $this->toArray($entity); - foreach ($entity->getTranslationLanguages(FALSE) as $language) { - yield $this->toArray($entity->getTranslation($language->getId())); + if (!$this->configuration['exclude_translations']) { + foreach ($entity->getTranslationLanguages(FALSE) as $language) { + yield $this->toArray($entity->getTranslation($language->getId())); + } } } } /** - * Convert entity to array. + * Converts an entity to an array. * * Makes all IDs into flat values. All other values are returned as per * $entity->toArray(), which is a nested array. @@ -135,7 +140,7 @@ * The entity to convert. * * @return array - * The entity converted to array. + * The entity, represented as an array. */ protected function toArray(ContentEntityInterface $entity) { $return = $entity->toArray(); @@ -145,7 +150,6 @@ /** @var \Drupal\Core\TypedData\Plugin\DataType\ItemList $value */ $value = $entity->get($id); // Force the IDs over top the previous values. - unset($return[$id]); $return[$id] = $value->first()->getString(); } return $return; @@ -167,12 +171,15 @@ return $query; } - /** * {@inheritdoc} */ public function count($refresh = FALSE) { - // @TODO: Figure out a better way to retrieve a valid count. + // If no translations are included, then a simple query is possible. + if ($this->configuration['exclude_translations']) { + return parent::count($refresh); + } + // @TODO: Determine a better way to retrieve a valid count for translations. // https://www.drupal.org/project/drupal/issues/2937166 return -1; } @@ -180,17 +187,22 @@ /** * {@inheritdoc} */ + protected function doCount() { + return $this->query()->count()->execute(); + } + + /** + * {@inheritdoc} + */ public function fields() { $field_definitions = $this->entityFieldManager->getBaseFieldDefinitions($this->entityType->id()); + if (!empty($this->configuration['bundle'])) { + $field_definitions += $this->entityFieldManager->getFieldDefinitions($this->entityType->id(), $this->configuration['bundle']); + } $fields = []; foreach ($field_definitions as $field_name => $definition) { $fields[$field_name] = (string) $definition->getLabel(); } - if (!empty($this->configuration['bundle'])) { - foreach ($this->entityFieldManager->getFieldDefinitions($this->entityType->id(), $this->configuration['bundle']) as $field_name => $definition) { - $fields[$field_name] = (string) $definition->getLabel(); - } - } return $fields; } diff -u b/core/modules/migrate_drupal/src/Plugin/migrate/source/ContentEntityDeriver.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/ContentEntityDeriver.php --- b/core/modules/migrate_drupal/src/Plugin/migrate/source/ContentEntityDeriver.php +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/ContentEntityDeriver.php @@ -36,7 +36,6 @@ * {@inheritdoc} */ public static function create(ContainerInterface $container, $base_plugin_id) { - // Translations don't make sense unless we have content_translation. return new static( $base_plugin_id, $container->get('entity_type.manager') @@ -49,7 +48,6 @@ public function getDerivativeDefinitions($base_plugin_definition) { $this->derivatives = []; foreach ($this->entityTypeManager->getDefinitions() as $id => $definition) { - // The source plugin only supports entities that are a content entity. if ($definition instanceof ContentEntityTypeInterface) { $this->derivatives[$id] = $base_plugin_definition; // Provide this so the source can be used separate from a deriver. diff -u b/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/source/ContentEntityTest.php b/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/source/ContentEntityTest.php --- b/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/source/ContentEntityTest.php +++ b/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/source/ContentEntityTest.php @@ -8,13 +8,13 @@ use Drupal\file\Entity\File; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\media\Entity\Media; -use Drupal\media\Entity\MediaType; use Drupal\migrate\Plugin\MigrateIdMapInterface; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\node\Entity\Node; use Drupal\node\Entity\NodeType; use Drupal\taxonomy\Entity\Term; use Drupal\taxonomy\Entity\Vocabulary; +use Drupal\Tests\media\Functional\MediaFunctionalTestCreateMediaTypeTrait; use Drupal\Tests\migrate\Kernel\MigrateTestBase; use Drupal\user\Entity\User; @@ -26,6 +26,7 @@ class ContentEntityTest extends MigrateTestBase { use EntityReferenceTestTrait; + use MediaFunctionalTestCreateMediaTypeTrait; /** * {@inheritdoc} @@ -77,11 +78,11 @@ protected $user; /** - * The mock migration plugin. + * The migration plugin manager. * - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface */ - protected $migration; + protected $migrationPluginManager; /** * The source plugin manager. @@ -174,20 +175,20 @@ ])->save(); $this->sourcePluginManager = $this->container->get('plugin.manager.migrate.source'); - $this->migration = $this->createMock(MigrationInterface::class); - $idMap = $this->createMock(MigrateIdMapInterface::class); - $idMap->expects(self::any())->method('getRowBySource')->willReturn(NULL); - $this->migration->expects(self::any())->method('getIdMap')->willReturn($idMap); + $this->migrationPluginManager = $this->container->get('plugin.manager.migration'); } /** * Tests user source plugin. */ public function testUserSource() { - $userSource = $this->sourcePluginManager->createInstance('content_entity:user', [], $this->migration); - $this->migration->expects(self::once())->method('getSourcePlugin')->willReturn($userSource); - $this->migration->expects(self::once())->method('getDestinationIds')->willReturn([1]); - $this->assertSame('User source', $userSource->__toString()); + $configuration = [ + 'exclude_translations' => TRUE, + ]; + $migration = $this->migrationPluginManager->createStubMigration($this->migrationDefinition('content_entity:user')); + $userSource = $this->sourcePluginManager->createInstance('content_entity:user', $configuration, $migration); + $this->assertSame('user entities', $userSource->__toString()); + $this->assertEquals(1, $userSource->count()); $ids = $userSource->getIds(); $this->assertArrayHasKey('langcode', $ids); $this->assertArrayHasKey('uid', $ids); @@ -216,10 +217,13 @@ ]); $file->save(); - $fileSource = $this->sourcePluginManager->createInstance('content_entity:file', [], $this->migration); - $this->migration->expects(self::once())->method('getSourcePlugin')->willReturn($fileSource); - $this->migration->expects(self::once())->method('getDestinationIds')->willReturn([1]); - $this->assertSame('File source', $fileSource->__toString()); + $configuration = [ + 'exclude_translations' => TRUE, + ]; + $migration = $this->migrationPluginManager->createStubMigration($this->migrationDefinition('content_entity:file')); + $fileSource = $this->sourcePluginManager->createInstance('content_entity:file', $configuration, $migration); + $this->assertSame('file entities', $fileSource->__toString()); + $this->assertEquals(1, $fileSource->count()); $ids = $fileSource->getIds(); $this->assertArrayHasKey('fid', $ids); $fields = $fileSource->fields(); @@ -240,11 +244,9 @@ * Tests node source plugin. */ public function testNodeSource() { - $nodeSource = $this->sourcePluginManager->createInstance('content_entity:node', ['bundle' => $this->bundle], $this->migration); - $this->migration->expects(self::exactly(2))->method('getSourcePlugin')->willReturn($nodeSource); - $this->migration->expects(self::at(0))->method('getDestinationIds')->willReturn([1, 'en']); - $this->migration->expects(self::at(1))->method('getDestinationIds')->willReturn([2, 'fr']); - $this->assertSame('Content source', $nodeSource->__toString()); + $migration = $this->migrationPluginManager->createStubMigration($this->migrationDefinition('content_entity:node')); + $nodeSource = $this->sourcePluginManager->createInstance('content_entity:node', ['bundle' => $this->bundle], $migration); + $this->assertSame('content items', $nodeSource->__toString()); $ids = $nodeSource->getIds(); $this->assertArrayHasKey('langcode', $ids); $this->assertArrayHasKey('nid', $ids); @@ -278,7 +280,14 @@ * Tests media source plugin. */ public function testMediaSource() { - $mediaType = $this->createMediaType('test'); + $values = [ + 'id' => 'image', + 'bundle' => 'image', + 'label' => 'Image', + 'source' => 'test', + 'new_revision' => FALSE, + ]; + $mediaType = $this->createMediaType($values); $media = Media::create([ 'name' => 'Foo media', 'uid' => $this->user->id(), @@ -286,10 +295,14 @@ ]); $media->save(); - $mediaSource = $this->sourcePluginManager->createInstance('content_entity:media', ['bundle' => 'image'], $this->migration); - $this->migration->expects(self::once())->method('getSourcePlugin')->willReturn($mediaSource); - $this->migration->expects(self::once())->method('getDestinationIds')->willReturn([1]); - $this->assertSame('Media source', $mediaSource->__toString()); + $configuration = [ + 'exclude_translations' => TRUE, + 'bundle' => 'image', + ]; + $migration = $this->migrationPluginManager->createStubMigration($this->migrationDefinition('content_entity:media')); + $mediaSource = $this->sourcePluginManager->createInstance('content_entity:media', $configuration, $migration); + $this->assertSame('media items', $mediaSource->__toString()); + $this->assertEquals(1, $mediaSource->count()); $ids = $mediaSource->getIds(); $this->assertArrayHasKey('langcode', $ids); $this->assertArrayHasKey('mid', $ids); @@ -319,11 +332,14 @@ ]); $term2->save(); - $termSource = $this->sourcePluginManager->createInstance('content_entity:taxonomy_term', ['bundle' => $this->vocabulary], $this->migration); - $this->migration->expects(self::exactly(2))->method('getSourcePlugin')->willReturn($termSource); - $this->migration->expects(self::at(0))->method('getDestinationIds')->willReturn([1]); - $this->migration->expects(self::at(1))->method('getDestinationIds')->willReturn([2]); - $this->assertSame('Taxonomy term source', $termSource->__toString()); + $configuration = [ + 'exclude_translations' => TRUE, + 'bundle' => $this->vocabulary, + ]; + $migration = $this->migrationPluginManager->createStubMigration($this->migrationDefinition('content_entity:taxonomy_term')); + $termSource = $this->sourcePluginManager->createInstance('content_entity:taxonomy_term', $configuration, $migration); + $this->assertSame('taxonomy term entities', $termSource->__toString()); + $this->assertEquals(2, $termSource->count()); $ids = $termSource->getIds(); $this->assertArrayHasKey('langcode', $ids); $this->assertArrayHasKey('tid', $ids); @@ -335,46 +351,35 @@ $values = $termSource->current()->getSource(); $this->assertEquals($this->vocabulary, $values['vid'][0]['target_id']); $this->assertEquals(1, $values['tid']); - // @TODO: add test coverage for parent after - // https://www.drupal.org/project/drupal/issues/2543726 + $this->assertEquals(0, $values['parent'][0]['target_id']); $this->assertEquals('Apples', $values['name'][0]['value']); $termSource->next(); $values = $termSource->current()->getSource(); $this->assertEquals($this->vocabulary, $values['vid'][0]['target_id']); $this->assertEquals(2, $values['tid']); - // @TODO: add test coverage for parent after - // https://www.drupal.org/project/drupal/issues/2543726 + $this->assertEquals(1, $values['parent'][0]['target_id']); $this->assertEquals('Granny Smith', $values['name'][0]['value']); } /** - * Create a media type for a source plugin. + * Get a migration definition. * - * @param string $media_source_name - * The name of the media source. + * @param string + * The plugin id. * - * @return \Drupal\media\MediaTypeInterface - * A media type. + * @return array + * The definition. */ - protected function createMediaType($media_source_name) { - $media_type = MediaType::create([ - 'id' => 'image', - 'label' => 'Image', - 'source' => $media_source_name, - 'new_revision' => FALSE, - ]); - $media_type->save(); - $source_field = $media_type->getSource()->createSourceField($media_type); - // The media type form creates a source field if it does not exist yet. The - // same must be done in a kernel test, since it does not use that form. - // @see \Drupal\media\MediaTypeForm::save() - $source_field->getFieldStorageDefinition()->save(); - // The source field storage has been created, now the field can be saved. - $source_field->save(); - $media_type->set('source_configuration', [ - 'source_field' => $source_field->getName(), - ])->save(); - return $media_type; + protected function migrationDefinition($plugin_id) { + return [ + 'source' => [ + 'plugin' => $plugin_id, + ], + 'process' => [], + 'destination' => [ + 'plugin' => 'null', + ], + ]; } }