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 }
|