<?php namespace Drupal\migrate_plus\Plugin\migrate\process; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\migrate\MigrateException; use Drupal\migrate\MigrateExecutableInterface; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\ProcessPluginBase; use Drupal\migrate\Row; use Symfony\Component\DependencyInjection\ContainerInterface; /** * This plugin looks for existing entities. * * In its most simple form, this plugin needs no configuration. However, if the * lookup properties cannot be determined through introspection, define them via * configuration. * * Available configuration keys: * - access_check: (optional) Indicates if access to the entity for this user * will be checked. Default is true. * * @codingStandardsIgnoreStart * * Example usage with minimal configuration: * @code * destination: * plugin: 'entity:node' * process: * type: * plugin: default_value * default_value: page * field_tags: * plugin: entity_lookup * access_check: false * source: tags * @endcode * In this example above, the access check is disabled. * * Example usage with full configuration: * @code * field_tags: * plugin: entity_lookup * source: tags * value_key: name * bundle_key: vid * bundle: tags * entity_type: taxonomy_term * ignore_case: true * @endcode * * @codingStandardsIgnoreEnd * * @MigrateProcessPlugin( * id = "entity_lookup", * handle_multiples = TRUE * ) */ class EntityLookup extends ProcessPluginBase implements ContainerFactoryPluginInterface { /** * The entity type manager. * * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ protected $entityTypeManager; /** * The field manager. * * @var \Drupal\Core\Entity\EntityFieldManagerInterface */ protected $entityFieldManager; /** * The migration. * * @var \Drupal\migrate\Plugin\MigrationInterface */ protected $migration; /** * The selection plugin. * * @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface */ protected $selectionPluginManager; /** * The destination type. * * @var string */ protected $destinationEntityType; /** * The destination bundle. * * @var string|bool */ protected $destinationBundleKey; /** * The lookup value's key. * * @var string */ protected $lookupValueKey; /** * The lookup bundle's key. * * @var string */ protected $lookupBundleKey; /** * The lookup bundle. * * @var string */ protected $lookupBundle; /** * The lookup entity type. * * @var string */ protected $lookupEntityType; /** * The destination property or field. * * @var string */ protected $destinationProperty; /** * The access check flag. * * @var string */ protected $accessCheck = TRUE; /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition, MigrationInterface $migration = NULL) { $instance = new static( $configuration, $pluginId, $pluginDefinition ); $instance->migration = $migration; $instance->entityTypeManager = $container->get('entity_type.manager'); $instance->entityFieldManager = $container->get('entity_field.manager'); $instance->selectionPluginManager = $container->get('plugin.manager.entity_reference_selection'); $pluginIdParts = explode(':', $instance->migration->getDestinationPlugin()->getPluginId()); $instance->destinationEntityType = empty($pluginIdParts[1]) ? NULL : $pluginIdParts[1]; $instance->destinationBundleKey = $instance->destinationEntityType ? $instance->entityTypeManager->getDefinition($instance->destinationEntityType)->getKey('bundle') : NULL; return $instance; } /** * {@inheritdoc} */ public function transform($value, MigrateExecutableInterface $migrateExecutable, Row $row, $destinationProperty) { // If the source data is an empty array, return the same. if (gettype($value) === 'array' && count($value) === 0) { return []; } // In case of subfields ('field_reference/target_id'), extract the field // name only. $parts = explode('/', $destinationProperty); $destinationProperty = reset($parts); $this->determineLookupProperties($destinationProperty); $this->destinationProperty = isset($this->configuration['destination_field']) ? $this->configuration['destination_field'] : NULL; return $this->query($value); } /** * Determine the lookup properties from config or target field configuration. * * @param string $destinationProperty * The destination property currently worked on. This is only used together * with the $row above. */ protected function determineLookupProperties($destinationProperty) { if (isset($this->configuration['access_check'])) { $this->accessCheck = $this->configuration['access_check']; } if (!empty($this->configuration['value_key'])) { $this->lookupValueKey = $this->configuration['value_key']; } if (!empty($this->configuration['bundle_key'])) { $this->lookupBundleKey = $this->configuration['bundle_key']; } if (!empty($this->configuration['bundle'])) { $this->lookupBundle = $this->configuration['bundle']; } if (!empty($this->configuration['entity_type'])) { $this->lookupEntityType = $this->configuration['entity_type']; } if (empty($this->lookupValueKey) || empty($this->lookupBundleKey) || empty($this->lookupBundle) || empty($this->lookupEntityType)) { // See if we can introspect the lookup properties from destination field. if (!empty($this->migration->getProcess()[$this->destinationBundleKey][0]['default_value'])) { $destinationEntityBundle = $this->migration->getProcess()[$this->destinationBundleKey][0]['default_value']; $fieldConfig = $this->entityFieldManager->getFieldDefinitions($this->destinationEntityType, $destinationEntityBundle)[$destinationProperty]->getConfig($destinationEntityBundle); switch ($fieldConfig->getType()) { case 'entity_reference': if (empty($this->lookupBundle)) { $handlerSettings = $fieldConfig->getSetting('handler_settings'); $bundles = array_filter((array) $handlerSettings['target_bundles']); if (count($bundles) == 1) { $this->lookupBundle = reset($bundles); } // This was added in 8.1.x is not supported in 8.0.x. elseif (!empty($handlerSettings['auto_create']) && !empty($handlerSettings['auto_create_bundle'])) { $this->lookupBundle = reset($handlerSettings['auto_create_bundle']); } } // Make an assumption that if the selection handler can target more // than one type of entity that we will use the first entity type. $fieldHandler = $fieldConfig->getSetting('handler'); $selection = $this->selectionPluginManager->createInstance($fieldHandler); $this->lookupEntityType = $this->lookupEntityType ?: reset($selection->getPluginDefinition()['entity_types']); $this->lookupValueKey = $this->lookupValueKey ?: $this->entityTypeManager->getDefinition($this->lookupEntityType)->getKey('label'); $this->lookupBundleKey = $this->lookupBundleKey ?: $this->entityTypeManager->getDefinition($this->lookupEntityType)->getKey('bundle'); break; case 'file': case 'image': $this->lookupEntityType = 'file'; $this->lookupValueKey = $this->lookupValueKey ?: 'uri'; break; default: throw new MigrateException(sprintf('Destination field type %s is not a recognized reference type.', $fieldConfig->getType())); } } } // If there aren't enough lookup properties available by now, then bail. if (empty($this->lookupValueKey)) { throw new MigrateException('The entity_lookup plugin requires a value_key, none located.'); } if (!empty($this->lookupBundleKey) && empty($this->lookupBundle)) { throw new MigrateException('The entity_lookup plugin found no bundle but destination entity requires one.'); } if (empty($this->lookupEntityType)) { throw new MigrateException('The entity_lookup plugin requires a entity_type, none located.'); } } /** * Checks for the existence of some value. * * @param mixed $value * The value to query. * * @return mixed|null * Entity id if the queried entity exists. Otherwise NULL. */ 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 // outcome. $ignoreCase = !empty($this->configuration['ignore_case']) ?: FALSE; $multiple = is_array($value); $query = $this->entityTypeManager->getStorage($this->lookupEntityType) ->getQuery() ->accessCheck($this->accessCheck) ->condition($this->lookupValueKey, $value, $multiple ? 'IN' : NULL); // Sqlite and possibly others returns data in a non-deterministic order. // Make it deterministic. if ($multiple) { $query->sort($this->lookupValueKey, 'DESC'); } if ($this->lookupBundleKey) { $query->condition($this->lookupBundleKey, $this->lookupBundle); } $results = $query->execute(); if (empty($results)) { return NULL; } // By default do a case-sensitive comparison. if (!$ignoreCase) { // Returns the entity's identifier. foreach ($results as $k => $identifier) { $entity = $this->entityTypeManager->getStorage($this->lookupEntityType)->load($identifier); $result_value = $entity instanceof ConfigEntityInterface ? $entity->get($this->lookupValueKey) : $entity->get($this->lookupValueKey)->value; if (($multiple && !in_array($result_value, $value, TRUE)) || (!$multiple && $result_value !== $value)) { unset($results[$k]); } } } if ($multiple && !empty($this->destinationProperty)) { array_walk($results, function (&$value) { $value = [$this->destinationProperty => $value]; }); } return $multiple ? array_values($results) : reset($results); } }