Commit 69347cf0 authored by Rodrigo  Aguilera's avatar Rodrigo Aguilera Committed by Ivan Doroshenko
Browse files

Issue #3121204 by rodrigoaguilera, Matroskeen: Add ability to choose the...

Issue #3121204 by rodrigoaguilera, Matroskeen: Add ability to choose the operator when performing an entity lookup
parent 875f8539
Loading
Loading
Loading
Loading
+19 −7
Changes for src/Plugin/migrate/process/EntityLookup.php: 19 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -27,13 +27,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].
@@ -65,10 +69,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
@@ -286,17 +293,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) {
@@ -312,8 +324,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);
+87 −14
Changes for tests/src/Kernel/Plugin/migrate/process/EntityLookupTest.php: 87 added lines, 14 removed lines.
Original line number Diff line number Diff line
@@ -3,10 +3,8 @@
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;

/**
@@ -18,6 +16,7 @@ use Drupal\Tests\user\Traits\UserCreationTrait;
class EntityLookupTest extends KernelTestBase {

  use UserCreationTrait;
  use NodeCreationTrait;

  /**
   * The migrate executable mock object.
@@ -34,6 +33,7 @@ class EntityLookupTest extends KernelTestBase {
    'migrate',
    'user',
    'system',
    'node',
    'filter',
  ];

@@ -44,11 +44,22 @@ 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);
    }
  }

  /**
@@ -60,29 +71,33 @@ 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);
  }

@@ -111,4 +126,62 @@ 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) {
    $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[]
   */
  public function providerTestLookupOperators() {
    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',
      ],
    ];
  }

}