annotate modules/contrib/migrate_plus/src/Plugin/migrate/process/EntityLookup.php @ 5:12f9dff5fda9 tip

Update to Drupal core 8.7.1
author Chris Cannam
date Thu, 09 May 2019 15:34:47 +0100
parents a9cd425dd02b
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\migrate_plus\Plugin\migrate\process;
Chris@0 4
Chris@4 5 use Drupal\Core\Config\Entity\ConfigEntityInterface;
Chris@0 6 use Drupal\Core\Entity\EntityManagerInterface;
Chris@0 7 use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface;
Chris@0 8 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
Chris@0 9 use Drupal\migrate\Plugin\MigrationInterface;
Chris@0 10 use Drupal\migrate\MigrateException;
Chris@0 11 use Drupal\migrate\MigrateExecutableInterface;
Chris@0 12 use Drupal\migrate\ProcessPluginBase;
Chris@0 13 use Drupal\migrate\Row;
Chris@0 14 use Symfony\Component\DependencyInjection\ContainerInterface;
Chris@0 15
Chris@0 16 /**
Chris@0 17 * This plugin looks for existing entities.
Chris@0 18 *
Chris@0 19 * In its most simple form, this plugin needs no configuration. However, if the
Chris@0 20 * lookup properties cannot be determined through introspection, define them via
Chris@0 21 * configuration.
Chris@0 22 *
Chris@5 23 * Available configuration keys:
Chris@5 24 * - access_check: (optional) Indicates if access to the entity for this user
Chris@5 25 * will be checked. Default is true.
Chris@5 26 *
Chris@5 27 * @codingStandardsIgnoreStart
Chris@5 28 *
Chris@0 29 * Example usage with minimal configuration:
Chris@0 30 * @code
Chris@0 31 * destination:
Chris@0 32 * plugin: 'entity:node'
Chris@0 33 * process:
Chris@0 34 * type:
Chris@0 35 * plugin: default_value
Chris@0 36 * default_value: page
Chris@0 37 * field_tags:
Chris@0 38 * plugin: entity_lookup
Chris@5 39 * access_check: false
Chris@0 40 * source: tags
Chris@0 41 * @endcode
Chris@5 42 * In this example above, the access check is disabled.
Chris@0 43 *
Chris@0 44 * Example usage with full configuration:
Chris@0 45 * @code
Chris@0 46 * field_tags:
Chris@0 47 * plugin: entity_lookup
Chris@0 48 * source: tags
Chris@0 49 * value_key: name
Chris@0 50 * bundle_key: vid
Chris@0 51 * bundle: tags
Chris@0 52 * entity_type: taxonomy_term
Chris@0 53 * ignore_case: true
Chris@0 54 * @endcode
Chris@5 55 *
Chris@5 56 * @codingStandardsIgnoreEnd
Chris@5 57 *
Chris@5 58 * @MigrateProcessPlugin(
Chris@5 59 * id = "entity_lookup",
Chris@5 60 * handle_multiples = TRUE
Chris@5 61 * )
Chris@0 62 */
Chris@0 63 class EntityLookup extends ProcessPluginBase implements ContainerFactoryPluginInterface {
Chris@0 64
Chris@4 65 /**
Chris@4 66 * The entity manager.
Chris@4 67 *
Chris@4 68 * @var \Drupal\Core\Entity\EntityManagerInterface
Chris@4 69 */
Chris@0 70 protected $entityManager;
Chris@0 71
Chris@4 72 /**
Chris@4 73 * The migration.
Chris@4 74 *
Chris@4 75 * @var \Drupal\migrate\Plugin\MigrationInterface
Chris@4 76 */
Chris@0 77 protected $migration;
Chris@0 78
Chris@4 79 /**
Chris@4 80 * The selection plugin.
Chris@4 81 *
Chris@4 82 * @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface
Chris@4 83 */
Chris@0 84 protected $selectionPluginManager;
Chris@0 85
Chris@4 86 /**
Chris@4 87 * The destination type.
Chris@4 88 *
Chris@4 89 * @var string
Chris@4 90 */
Chris@0 91 protected $destinationEntityType;
Chris@0 92
Chris@4 93 /**
Chris@4 94 * The destination bundle.
Chris@4 95 *
Chris@4 96 * @var string|bool
Chris@4 97 */
Chris@0 98 protected $destinationBundleKey;
Chris@0 99
Chris@4 100 /**
Chris@4 101 * The lookup value's key.
Chris@4 102 *
Chris@4 103 * @var string
Chris@4 104 */
Chris@0 105 protected $lookupValueKey;
Chris@0 106
Chris@4 107 /**
Chris@4 108 * The lookup bundle's key.
Chris@4 109 *
Chris@4 110 * @var string
Chris@4 111 */
Chris@0 112 protected $lookupBundleKey;
Chris@0 113
Chris@4 114 /**
Chris@4 115 * The lookup bundle.
Chris@4 116 *
Chris@4 117 * @var string
Chris@4 118 */
Chris@0 119 protected $lookupBundle;
Chris@0 120
Chris@4 121 /**
Chris@4 122 * The lookup entity type.
Chris@4 123 *
Chris@4 124 * @var string
Chris@4 125 */
Chris@0 126 protected $lookupEntityType;
Chris@0 127
Chris@4 128 /**
Chris@4 129 * The destination property or field.
Chris@4 130 *
Chris@4 131 * @var string
Chris@4 132 */
Chris@0 133 protected $destinationProperty;
Chris@0 134
Chris@0 135 /**
Chris@5 136 * The access check flag.
Chris@5 137 *
Chris@5 138 * @var string
Chris@5 139 */
Chris@5 140 protected $accessCheck = TRUE;
Chris@5 141
Chris@5 142 /**
Chris@0 143 * {@inheritdoc}
Chris@0 144 */
Chris@0 145 public function __construct(array $configuration, $pluginId, $pluginDefinition, MigrationInterface $migration, EntityManagerInterface $entityManager, SelectionPluginManagerInterface $selectionPluginManager) {
Chris@0 146 parent::__construct($configuration, $pluginId, $pluginDefinition);
Chris@0 147 $this->migration = $migration;
Chris@0 148 $this->entityManager = $entityManager;
Chris@0 149 $this->selectionPluginManager = $selectionPluginManager;
Chris@0 150 $pluginIdParts = explode(':', $this->migration->getDestinationPlugin()->getPluginId());
Chris@4 151 $this->destinationEntityType = empty($pluginIdParts[1]) ? NULL : $pluginIdParts[1];
Chris@4 152 $this->destinationBundleKey = $this->destinationEntityType ? $this->entityManager->getDefinition($this->destinationEntityType)->getKey('bundle') : NULL;
Chris@0 153 }
Chris@0 154
Chris@0 155 /**
Chris@0 156 * {@inheritdoc}
Chris@0 157 */
Chris@0 158 public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition, MigrationInterface $migration = NULL) {
Chris@0 159 return new static(
Chris@0 160 $configuration,
Chris@0 161 $pluginId,
Chris@0 162 $pluginDefinition,
Chris@0 163 $migration,
Chris@0 164 $container->get('entity.manager'),
Chris@0 165 $container->get('plugin.manager.entity_reference_selection')
Chris@0 166 );
Chris@0 167 }
Chris@0 168
Chris@0 169 /**
Chris@0 170 * {@inheritdoc}
Chris@0 171 */
Chris@0 172 public function transform($value, MigrateExecutableInterface $migrateExecutable, Row $row, $destinationProperty) {
Chris@4 173 // If the source data is an empty array, return the same.
Chris@4 174 if (gettype($value) === 'array' && count($value) === 0) {
Chris@4 175 return [];
Chris@4 176 }
Chris@4 177
Chris@4 178 // In case of subfields ('field_reference/target_id'), extract the field
Chris@4 179 // name only.
Chris@4 180 $parts = explode('/', $destinationProperty);
Chris@4 181 $destinationProperty = reset($parts);
Chris@0 182 $this->determineLookupProperties($destinationProperty);
Chris@0 183
Chris@4 184 $this->destinationProperty = isset($this->configuration['destination_field']) ? $this->configuration['destination_field'] : NULL;
Chris@0 185
Chris@0 186 return $this->query($value);
Chris@0 187 }
Chris@0 188
Chris@0 189 /**
Chris@0 190 * Determine the lookup properties from config or target field configuration.
Chris@0 191 *
Chris@0 192 * @param string $destinationProperty
Chris@0 193 * The destination property currently worked on. This is only used together
Chris@0 194 * with the $row above.
Chris@0 195 */
Chris@0 196 protected function determineLookupProperties($destinationProperty) {
Chris@5 197 if (isset($this->configuration['access_check'])) {
Chris@5 198 $this->accessCheck = $this->configuration['access_check'];
Chris@5 199 }
Chris@0 200 if (!empty($this->configuration['value_key'])) {
Chris@0 201 $this->lookupValueKey = $this->configuration['value_key'];
Chris@0 202 }
Chris@0 203 if (!empty($this->configuration['bundle_key'])) {
Chris@0 204 $this->lookupBundleKey = $this->configuration['bundle_key'];
Chris@0 205 }
Chris@0 206 if (!empty($this->configuration['bundle'])) {
Chris@0 207 $this->lookupBundle = $this->configuration['bundle'];
Chris@0 208 }
Chris@0 209 if (!empty($this->configuration['entity_type'])) {
Chris@0 210 $this->lookupEntityType = $this->configuration['entity_type'];
Chris@0 211 }
Chris@0 212
Chris@0 213 if (empty($this->lookupValueKey) || empty($this->lookupBundleKey) || empty($this->lookupBundle) || empty($this->lookupEntityType)) {
Chris@4 214 // See if we can introspect the lookup properties from destination field.
Chris@0 215 if (!empty($this->migration->getProcess()[$this->destinationBundleKey][0]['default_value'])) {
Chris@0 216 $destinationEntityBundle = $this->migration->getProcess()[$this->destinationBundleKey][0]['default_value'];
Chris@0 217 $fieldConfig = $this->entityManager->getFieldDefinitions($this->destinationEntityType, $destinationEntityBundle)[$destinationProperty]->getConfig($destinationEntityBundle);
Chris@4 218 switch ($fieldConfig->getType()) {
Chris@4 219 case 'entity_reference':
Chris@4 220 if (empty($this->lookupBundle)) {
Chris@4 221 $handlerSettings = $fieldConfig->getSetting('handler_settings');
Chris@4 222 $bundles = array_filter((array) $handlerSettings['target_bundles']);
Chris@4 223 if (count($bundles) == 1) {
Chris@4 224 $this->lookupBundle = reset($bundles);
Chris@4 225 }
Chris@4 226 // This was added in 8.1.x is not supported in 8.0.x.
Chris@4 227 elseif (!empty($handlerSettings['auto_create']) && !empty($handlerSettings['auto_create_bundle'])) {
Chris@4 228 $this->lookupBundle = reset($handlerSettings['auto_create_bundle']);
Chris@4 229 }
Chris@4 230 }
Chris@4 231
Chris@4 232 // Make an assumption that if the selection handler can target more
Chris@4 233 // than one type of entity that we will use the first entity type.
Chris@4 234 $this->lookupEntityType = $this->lookupEntityType ?: reset($this->selectionPluginManager->createInstance($fieldConfig->getSetting('handler'))->getPluginDefinition()['entity_types']);
Chris@4 235 $this->lookupValueKey = $this->lookupValueKey ?: $this->entityManager->getDefinition($this->lookupEntityType)->getKey('label');
Chris@4 236 $this->lookupBundleKey = $this->lookupBundleKey ?: $this->entityManager->getDefinition($this->lookupEntityType)->getKey('bundle');
Chris@4 237 break;
Chris@4 238
Chris@4 239 case 'file':
Chris@4 240 case 'image':
Chris@4 241 $this->lookupEntityType = 'file';
Chris@4 242 $this->lookupValueKey = $this->lookupValueKey ?: 'uri';
Chris@4 243 break;
Chris@4 244
Chris@4 245 default:
Chris@4 246 throw new MigrateException(sprintf('Destination field type %s is not a recognized reference type.', $fieldConfig->getType()));
Chris@0 247 }
Chris@0 248 }
Chris@0 249 }
Chris@0 250
Chris@0 251 // If there aren't enough lookup properties available by now, then bail.
Chris@0 252 if (empty($this->lookupValueKey)) {
Chris@0 253 throw new MigrateException('The entity_lookup plugin requires a value_key, none located.');
Chris@0 254 }
Chris@0 255 if (!empty($this->lookupBundleKey) && empty($this->lookupBundle)) {
Chris@0 256 throw new MigrateException('The entity_lookup plugin found no bundle but destination entity requires one.');
Chris@0 257 }
Chris@0 258 if (empty($this->lookupEntityType)) {
Chris@0 259 throw new MigrateException('The entity_lookup plugin requires a entity_type, none located.');
Chris@0 260 }
Chris@0 261 }
Chris@0 262
Chris@0 263 /**
Chris@0 264 * Checks for the existence of some value.
Chris@0 265 *
Chris@4 266 * @param mixed $value
Chris@4 267 * The value to query.
Chris@0 268 *
Chris@0 269 * @return mixed|null
Chris@0 270 * Entity id if the queried entity exists. Otherwise NULL.
Chris@0 271 */
Chris@0 272 protected function query($value) {
Chris@0 273 // Entity queries typically are case-insensitive. Therefore, we need to
Chris@0 274 // handle case sensitive filtering as a post-query step. By default, it
Chris@0 275 // filters case insensitive. Change to true if that is not the desired
Chris@0 276 // outcome.
Chris@0 277 $ignoreCase = !empty($this->configuration['ignore_case']) ?: FALSE;
Chris@0 278
Chris@0 279 $multiple = is_array($value);
Chris@0 280
Chris@0 281 $query = $this->entityManager->getStorage($this->lookupEntityType)
Chris@0 282 ->getQuery()
Chris@5 283 ->accessCheck($this->accessCheck)
Chris@0 284 ->condition($this->lookupValueKey, $value, $multiple ? 'IN' : NULL);
Chris@4 285 // Sqlite and possibly others returns data in a non-deterministic order.
Chris@4 286 // Make it deterministic.
Chris@4 287 if ($multiple) {
Chris@4 288 $query->sort($this->lookupValueKey, 'DESC');
Chris@4 289 }
Chris@0 290
Chris@0 291 if ($this->lookupBundleKey) {
Chris@0 292 $query->condition($this->lookupBundleKey, $this->lookupBundle);
Chris@0 293 }
Chris@0 294 $results = $query->execute();
Chris@0 295
Chris@0 296 if (empty($results)) {
Chris@0 297 return NULL;
Chris@0 298 }
Chris@0 299
Chris@4 300 // By default do a case-sensitive comparison.
Chris@4 301 if (!$ignoreCase) {
Chris@4 302 // Returns the entity's identifier.
Chris@4 303 foreach ($results as $k => $identifier) {
Chris@4 304 $entity = $this->entityManager->getStorage($this->lookupEntityType)->load($identifier);
Chris@4 305 $result_value = $entity instanceof ConfigEntityInterface ? $entity->get($this->lookupValueKey) : $entity->get($this->lookupValueKey)->value;
Chris@4 306 if (($multiple && !in_array($result_value, $value, TRUE)) || (!$multiple && $result_value !== $value)) {
Chris@4 307 unset($results[$k]);
Chris@4 308 }
Chris@4 309 }
Chris@4 310 }
Chris@4 311
Chris@0 312 if ($multiple && !empty($this->destinationProperty)) {
Chris@0 313 array_walk($results, function (&$value) {
Chris@0 314 $value = [$this->destinationProperty => $value];
Chris@0 315 });
Chris@0 316 }
Chris@0 317
Chris@4 318 return $multiple ? array_values($results) : reset($results);
Chris@0 319 }
Chris@0 320
Chris@0 321 }