diff --git a/src/Plugin/migrate/process/EntityLookup.php b/src/Plugin/migrate/process/EntityLookup.php index f79e9400b1623a4b2d5a00fa8042479aafc39073..b196a291b5b9febca750f5bb424bf3ee47f410a5 100644 --- a/src/Plugin/migrate/process/EntityLookup.php +++ b/src/Plugin/migrate/process/EntityLookup.php @@ -32,13 +32,17 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * - entity_reference: The entity label key. * - file: The uri field. * - image: The uri field. + * - operator: (optional) The comparison operator supported by entity query: + * See \Drupal\Core\Entity\Query\QueryInterface::condition() for available + * values. Defaults to '=' for scalar values and 'IN' for arrays. * - bundle_key: (optional) The name of the bundle field on the entity type * being queried. * - bundle: (optional) The value to query for the bundle. * - access_check: (optional) Indicates if access to the entity for this user * will be checked. Default is true. * - ignore_case: (optional) Whether to ignore case in the query. Defaults to - * true. + * false, meaning the query is case-sensitive by default. Works only with + * strict operators: '=' and 'IN'. * - destination_field: (optional) If specified, and if the plugin's source * value is an array, the result array's items will be themselves arrays of * the form [destination_field => ENTITY_ID]. @@ -70,10 +74,13 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * bundle: tags * entity_type: taxonomy_term * ignore_case: true + * operator: STARTS_WITH * @endcode * * @codingStandardsIgnoreEnd * + * @see \Drupal\Core\Entity\Query\QueryInterface::condition() + * * @MigrateProcessPlugin( * id = "entity_lookup", * handle_multiples = TRUE @@ -220,17 +227,22 @@ class EntityLookup extends ProcessPluginBase implements ContainerFactoryPluginIn */ protected function query($value) { // Entity queries typically are case-insensitive. Therefore, we need to - // handle case sensitive filtering as a post-query step. By default, it - // filters case insensitive. Change to true if that is not the desired + // handle case-sensitive filtering as a post-query step. By default, it + // filters case-insensitive. Change to true if that is not the desired // outcome. $ignoreCase = !empty($this->configuration['ignore_case']) ?: FALSE; - + $operator = !empty($this->configuration['operator']) ? $this->configuration['operator'] : '='; $multiple = is_array($value); + // Apply correct operator for multiple values. + if ($multiple && $operator === '=') { + $operator = 'IN'; + } + $query = $this->entityTypeManager->getStorage($this->lookupEntityType) ->getQuery() ->accessCheck($this->accessCheck) - ->condition($this->lookupValueKey, $value, $multiple ? 'IN' : NULL); + ->condition($this->lookupValueKey, $value, $operator); // Sqlite and possibly others returns data in a non-deterministic order. // Make it deterministic. if ($multiple) { @@ -246,8 +258,8 @@ class EntityLookup extends ProcessPluginBase implements ContainerFactoryPluginIn return NULL; } - // By default do a case-sensitive comparison. - if (!$ignoreCase) { + // Do a case-sensitive comparison only for strict operators. + if (!$ignoreCase && in_array($operator, ['=', 'IN'], TRUE)) { // Returns the entity's identifier. foreach ($results as $k => $identifier) { $entity = $this->entityTypeManager->getStorage($this->lookupEntityType)->load($identifier); diff --git a/tests/src/Kernel/Plugin/migrate/process/EntityLookupTest.php b/tests/src/Kernel/Plugin/migrate/process/EntityLookupTest.php index f5cf2011990fb84994bb3296b40be9f2de7ad18a..426e9fc84339625a815f9f3777622e63e15d7ee9 100644 --- a/tests/src/Kernel/Plugin/migrate/process/EntityLookupTest.php +++ b/tests/src/Kernel/Plugin/migrate/process/EntityLookupTest.php @@ -5,10 +5,8 @@ declare(strict_types = 1); namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate\process; use Drupal\KernelTests\KernelTestBase; -use Drupal\migrate\MigrateExecutableInterface; -use Drupal\migrate\Plugin\MigrateDestinationInterface; -use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\Row; +use Drupal\Tests\node\Traits\NodeCreationTrait; use Drupal\Tests\user\Traits\UserCreationTrait; /** @@ -20,6 +18,7 @@ use Drupal\Tests\user\Traits\UserCreationTrait; final class EntityLookupTest extends KernelTestBase { use UserCreationTrait; + use NodeCreationTrait; /** * The migrate executable mock object. @@ -36,6 +35,7 @@ final class EntityLookupTest extends KernelTestBase { 'migrate', 'user', 'system', + 'node', 'filter', ]; @@ -46,11 +46,22 @@ final class EntityLookupTest extends KernelTestBase { parent::setUp(); $this->installSchema('system', ['sequences']); $this->installEntitySchema('user'); + $this->installEntitySchema('node'); $this->installConfig(['filter']); $this->migrateExecutable = $this->getMockBuilder('Drupal\migrate\MigrateExecutable') ->disableOriginalConstructor() ->getMock(); + + $test_nodes = [ + ['title' => 'foo 1'], + ['title' => 'foo 2'], + ['title' => 'bar 1'], + ]; + + foreach ($test_nodes as $test_node) { + $this->createNode($test_node); + } } /** @@ -62,29 +73,33 @@ final class EntityLookupTest extends KernelTestBase { * @covers ::transform */ public function testLookupEntityWithoutBundles(): void { + $migration = \Drupal::service('plugin.manager.migration') + ->createStubMigration([ + 'id' => 'test', + 'source' => [], + 'process' => [], + 'destination' => [ + 'plugin' => 'entity:user', + ], + ]); + // Create a user. $known_user = $this->createUser([], 'lucuma'); - // Setup test migration objects. - $migration_prophecy = $this->prophesize(MigrationInterface::class); - $migrate_destination_prophecy = $this->prophesize(MigrateDestinationInterface::class); - $migrate_destination_prophecy->getPluginId()->willReturn('user'); - $migrate_destination = $migrate_destination_prophecy->reveal(); - $migration_prophecy->getDestinationPlugin()->willReturn($migrate_destination); - $migration_prophecy->getProcess()->willReturn([]); - $migration = $migration_prophecy->reveal(); + $configuration = [ 'entity_type' => 'user', 'value_key' => 'name', ]; $plugin = \Drupal::service('plugin.manager.migrate.process') ->createInstance('entity_lookup', $configuration, $migration); - $executable = $this->prophesize(MigrateExecutableInterface::class)->reveal(); $row = new Row(); + // Check the known user is found. - $value = $plugin->transform('lucuma', $executable, $row, 'name'); + $value = $plugin->transform('lucuma', $this->migrateExecutable, $row, 'name'); $this->assertSame($known_user->id(), $value); + // Check an unknown user is not found. - $value = $plugin->transform('orange', $executable, $row, 'name'); + $value = $plugin->transform('orange', $this->migrateExecutable, $row, 'name'); $this->assertNull($value); } @@ -113,4 +128,63 @@ final class EntityLookupTest extends KernelTestBase { $this->assertEquals('plain_text', $value); } + /** + * Tests lookup with different operators. + * + * @covers ::transform + * @dataProvider providerTestLookupOperators + */ + public function testLookupOperators($configuration, $lookup_value, $expected_value): void { + $migration = \Drupal::service('plugin.manager.migration') + ->createStubMigration([ + 'id' => 'test', + 'source' => [], + 'process' => [], + 'destination' => [ + 'plugin' => 'entity:node', + ], + ]); + + $plugin = \Drupal::service('plugin.manager.migrate.process') + ->createInstance('entity_lookup', $configuration, $migration); + $value = $plugin->transform($lookup_value, $this->migrateExecutable, new Row(), 'destination_property'); + $this->assertEquals($expected_value, $value); + } + + /** + * Data provider for testLookupOperators test. + * + * @return array[] + * The test cases. + */ + public function providerTestLookupOperators(): array { + return [ + 'Default operator' => [ + [ + 'entity_type' => 'node', + 'value_key' => 'title', + ], + 'foo 1', + '1', + ], + 'Multiple values' => [ + [ + 'entity_type' => 'node', + 'value_key' => 'title', + ], + ['foo 1', 'foo 2'], + ['2', '1'], + ], + 'Starts with' => [ + [ + 'entity_type' => 'node', + 'value_key' => 'title', + 'operator' => 'STARTS_WITH', + ], + 'bar', + '3', + ], + ]; + } + }