Chris@18
|
1 <?php
|
Chris@18
|
2
|
Chris@18
|
3 namespace Drupal\migrate_drupal;
|
Chris@18
|
4
|
Chris@18
|
5 use Drupal\Component\Plugin\Exception\PluginNotFoundException;
|
Chris@18
|
6 use Drupal\Core\Logger\LoggerChannelInterface;
|
Chris@18
|
7 use Drupal\migrate\Exception\RequirementsException;
|
Chris@18
|
8 use Drupal\migrate\Plugin\MigrationInterface;
|
Chris@18
|
9 use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
|
Chris@18
|
10 use Drupal\migrate\Plugin\RequirementsInterface;
|
Chris@18
|
11 use Drupal\migrate_drupal\Plugin\MigrateCckFieldInterface;
|
Chris@18
|
12 use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface;
|
Chris@18
|
13
|
Chris@18
|
14 /**
|
Chris@18
|
15 * Provides field discovery for Drupal 6 & 7 migrations.
|
Chris@18
|
16 */
|
Chris@18
|
17 class FieldDiscovery implements FieldDiscoveryInterface {
|
Chris@18
|
18
|
Chris@18
|
19 /**
|
Chris@18
|
20 * The CCK plugin manager.
|
Chris@18
|
21 *
|
Chris@18
|
22 * @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
|
Chris@18
|
23 */
|
Chris@18
|
24 protected $cckPluginManager;
|
Chris@18
|
25
|
Chris@18
|
26 /**
|
Chris@18
|
27 * An array of already discovered field plugin information.
|
Chris@18
|
28 *
|
Chris@18
|
29 * @var array
|
Chris@18
|
30 */
|
Chris@18
|
31 protected $fieldPluginCache;
|
Chris@18
|
32
|
Chris@18
|
33 /**
|
Chris@18
|
34 * The field plugin manager.
|
Chris@18
|
35 *
|
Chris@18
|
36 * @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface
|
Chris@18
|
37 */
|
Chris@18
|
38 protected $fieldPluginManager;
|
Chris@18
|
39
|
Chris@18
|
40 /**
|
Chris@18
|
41 * The migration plugin manager.
|
Chris@18
|
42 *
|
Chris@18
|
43 * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
|
Chris@18
|
44 */
|
Chris@18
|
45 protected $migrationPluginManager;
|
Chris@18
|
46
|
Chris@18
|
47 /**
|
Chris@18
|
48 * The logger channel service.
|
Chris@18
|
49 *
|
Chris@18
|
50 * @var \Drupal\Core\Logger\LoggerChannelInterface
|
Chris@18
|
51 */
|
Chris@18
|
52 protected $logger;
|
Chris@18
|
53
|
Chris@18
|
54 /**
|
Chris@18
|
55 * A cache of discovered fields.
|
Chris@18
|
56 *
|
Chris@18
|
57 * It is an array of arrays. If the entity type is bundleable, a third level
|
Chris@18
|
58 * of arrays is added to account for fields discovered at the bundle level.
|
Chris@18
|
59 *
|
Chris@18
|
60 * [{core}][{entity_type}][{bundle}]
|
Chris@18
|
61 *
|
Chris@18
|
62 * @var array
|
Chris@18
|
63 */
|
Chris@18
|
64 protected $discoveredFieldsCache = [];
|
Chris@18
|
65
|
Chris@18
|
66 /**
|
Chris@18
|
67 * An array of bundle keys, keyed by drupal core version.
|
Chris@18
|
68 *
|
Chris@18
|
69 * In Drupal 6, only nodes were fieldable, and the bundles were called
|
Chris@18
|
70 * 'type_name'. In Drupal 7, everything became entities, and the more
|
Chris@18
|
71 * generic 'bundle' was used.
|
Chris@18
|
72 *
|
Chris@18
|
73 * @var array
|
Chris@18
|
74 */
|
Chris@18
|
75 protected $bundleKeys = [
|
Chris@18
|
76 FieldDiscoveryInterface::DRUPAL_6 => 'type_name',
|
Chris@18
|
77 FieldDiscoveryInterface::DRUPAL_7 => 'bundle',
|
Chris@18
|
78 ];
|
Chris@18
|
79
|
Chris@18
|
80 /**
|
Chris@18
|
81 * An array of source plugin ids, keyed by Drupal core version.
|
Chris@18
|
82 *
|
Chris@18
|
83 * @var array
|
Chris@18
|
84 */
|
Chris@18
|
85 protected $sourcePluginIds = [
|
Chris@18
|
86 FieldDiscoveryInterface::DRUPAL_6 => 'd6_field_instance',
|
Chris@18
|
87 FieldDiscoveryInterface::DRUPAL_7 => 'd7_field_instance',
|
Chris@18
|
88 ];
|
Chris@18
|
89
|
Chris@18
|
90 /**
|
Chris@18
|
91 * An array of supported Drupal core versions.
|
Chris@18
|
92 *
|
Chris@18
|
93 * @var array
|
Chris@18
|
94 */
|
Chris@18
|
95 protected $supportedCoreVersions = [
|
Chris@18
|
96 FieldDiscoveryInterface::DRUPAL_6,
|
Chris@18
|
97 FieldDiscoveryInterface::DRUPAL_7,
|
Chris@18
|
98 ];
|
Chris@18
|
99
|
Chris@18
|
100 /**
|
Chris@18
|
101 * Constructs a FieldDiscovery object.
|
Chris@18
|
102 *
|
Chris@18
|
103 * @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $field_plugin_manager
|
Chris@18
|
104 * The field plugin manager.
|
Chris@18
|
105 * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
|
Chris@18
|
106 * The migration plugin manager.
|
Chris@18
|
107 * @param \Drupal\Core\Logger\LoggerChannelInterface $logger
|
Chris@18
|
108 * The logger channel service.
|
Chris@18
|
109 */
|
Chris@18
|
110 public function __construct(MigrateFieldPluginManagerInterface $field_plugin_manager, MigrationPluginManagerInterface $migration_plugin_manager, LoggerChannelInterface $logger) {
|
Chris@18
|
111 $this->fieldPluginManager = $field_plugin_manager;
|
Chris@18
|
112 $this->migrationPluginManager = $migration_plugin_manager;
|
Chris@18
|
113 $this->logger = $logger;
|
Chris@18
|
114 }
|
Chris@18
|
115
|
Chris@18
|
116 /**
|
Chris@18
|
117 * {@inheritdoc}
|
Chris@18
|
118 */
|
Chris@18
|
119 public function addAllFieldProcesses(MigrationInterface $migration) {
|
Chris@18
|
120 $core = $this->getCoreVersion($migration);
|
Chris@18
|
121 $fields = $this->getAllFields($core);
|
Chris@18
|
122 foreach ($fields as $entity_type_id => $bundle) {
|
Chris@18
|
123 $this->addEntityFieldProcesses($migration, $entity_type_id);
|
Chris@18
|
124 }
|
Chris@18
|
125 }
|
Chris@18
|
126
|
Chris@18
|
127 /**
|
Chris@18
|
128 * {@inheritdoc}
|
Chris@18
|
129 */
|
Chris@18
|
130 public function addEntityFieldProcesses(MigrationInterface $migration, $entity_type_id) {
|
Chris@18
|
131 $core = $this->getCoreVersion($migration);
|
Chris@18
|
132 $fields = $this->getAllFields($core);
|
Chris@18
|
133 if (!empty($fields[$entity_type_id]) && is_array($fields[$entity_type_id])) {
|
Chris@18
|
134 foreach ($fields[$entity_type_id] as $bundle => $fields) {
|
Chris@18
|
135 $this->addBundleFieldProcesses($migration, $entity_type_id, $bundle);
|
Chris@18
|
136 }
|
Chris@18
|
137 }
|
Chris@18
|
138 }
|
Chris@18
|
139
|
Chris@18
|
140 /**
|
Chris@18
|
141 * {@inheritdoc}
|
Chris@18
|
142 */
|
Chris@18
|
143 public function addBundleFieldProcesses(MigrationInterface $migration, $entity_type_id, $bundle) {
|
Chris@18
|
144 $core = $this->getCoreVersion($migration);
|
Chris@18
|
145 $fields = $this->getAllFields($core);
|
Chris@18
|
146 $plugin_definition = $migration->getPluginDefinition();
|
Chris@18
|
147 if (empty($fields[$entity_type_id][$bundle])) {
|
Chris@18
|
148 return;
|
Chris@18
|
149 }
|
Chris@18
|
150 $bundle_fields = $fields[$entity_type_id][$bundle];
|
Chris@18
|
151 foreach ($bundle_fields as $field_name => $field_info) {
|
Chris@18
|
152 $plugin = $this->getFieldPlugin($field_info['type'], $migration);
|
Chris@18
|
153 if ($plugin) {
|
Chris@18
|
154 $method = isset($plugin_definition['field_plugin_method']) ? $plugin_definition['field_plugin_method'] : 'defineValueProcessPipeline';
|
Chris@18
|
155
|
Chris@18
|
156 // @todo Remove the following 3 lines of code prior to Drupal 9.0.0.
|
Chris@18
|
157 // https://www.drupal.org/node/3032317
|
Chris@18
|
158 if ($plugin instanceof MigrateCckFieldInterface) {
|
Chris@18
|
159 $method = isset($plugin_definition['cck_plugin_method']) ? $plugin_definition['cck_plugin_method'] : 'processCckFieldValues';
|
Chris@18
|
160 }
|
Chris@18
|
161
|
Chris@18
|
162 call_user_func_array([
|
Chris@18
|
163 $plugin,
|
Chris@18
|
164 $method,
|
Chris@18
|
165 ], [
|
Chris@18
|
166 $migration,
|
Chris@18
|
167 $field_name,
|
Chris@18
|
168 $field_info,
|
Chris@18
|
169 ]);
|
Chris@18
|
170 }
|
Chris@18
|
171 else {
|
Chris@18
|
172 // Default to a get process plugin if this is a value migration.
|
Chris@18
|
173 if ((empty($plugin_definition['field_plugin_method']) || $plugin_definition['field_plugin_method'] === 'defineValueProcessPipeline') && (empty($plugin_definition['cck_plugin_method']) || $plugin_definition['cck_plugin_method'] === 'processCckFieldValues')) {
|
Chris@18
|
174 $migration->setProcessOfProperty($field_name, $field_name);
|
Chris@18
|
175 }
|
Chris@18
|
176 }
|
Chris@18
|
177 }
|
Chris@18
|
178 }
|
Chris@18
|
179
|
Chris@18
|
180 /**
|
Chris@18
|
181 * Returns the appropriate field plugin for a given field type.
|
Chris@18
|
182 *
|
Chris@18
|
183 * @param string $field_type
|
Chris@18
|
184 * The field type.
|
Chris@18
|
185 * @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
Chris@18
|
186 * The migration to retrieve the plugin for.
|
Chris@18
|
187 *
|
Chris@18
|
188 * @return \Drupal\migrate_drupal\Plugin\MigrateCckFieldInterface|\Drupal\migrate_drupal\Plugin\MigrateFieldInterface|bool
|
Chris@18
|
189 * The appropriate field or cck plugin to process this field type.
|
Chris@18
|
190 *
|
Chris@18
|
191 * @throws \Drupal\Component\Plugin\Exception\PluginException
|
Chris@18
|
192 * @throws \InvalidArgumentException
|
Chris@18
|
193 */
|
Chris@18
|
194 protected function getFieldPlugin($field_type, MigrationInterface $migration) {
|
Chris@18
|
195 $core = $this->getCoreVersion($migration);
|
Chris@18
|
196 if (!isset($this->fieldPluginCache[$core][$field_type])) {
|
Chris@18
|
197 try {
|
Chris@18
|
198 $plugin_id = $this->fieldPluginManager->getPluginIdFromFieldType($field_type, ['core' => $core], $migration);
|
Chris@18
|
199 $plugin = $this->fieldPluginManager->createInstance($plugin_id, ['core' => $core], $migration);
|
Chris@18
|
200 }
|
Chris@18
|
201 catch (PluginNotFoundException $ex) {
|
Chris@18
|
202 // @todo Replace try/catch block with $plugin = FALSE for Drupal 9.
|
Chris@18
|
203 // https://www.drupal.org/project/drupal/issues/3033733
|
Chris@18
|
204 try {
|
Chris@18
|
205 /** @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManager $cck_plugin_manager */
|
Chris@18
|
206 $cck_plugin_manager = $this->getCckPluginManager();
|
Chris@18
|
207 $plugin_id = $cck_plugin_manager->getPluginIdFromFieldType($field_type, ['core' => $core], $migration);
|
Chris@18
|
208 $plugin = $cck_plugin_manager->createInstance($plugin_id, ['core' => $core], $migration);
|
Chris@18
|
209 }
|
Chris@18
|
210 catch (PluginNotFoundException $ex) {
|
Chris@18
|
211 $plugin = FALSE;
|
Chris@18
|
212 }
|
Chris@18
|
213 }
|
Chris@18
|
214 $this->fieldPluginCache[$core][$field_type] = $plugin;
|
Chris@18
|
215 }
|
Chris@18
|
216 return $this->fieldPluginCache[$core][$field_type];
|
Chris@18
|
217 }
|
Chris@18
|
218
|
Chris@18
|
219 /**
|
Chris@18
|
220 * Gets all field information related to this migration.
|
Chris@18
|
221 *
|
Chris@18
|
222 * @param string $core
|
Chris@18
|
223 * The Drupal core version to get fields for.
|
Chris@18
|
224 *
|
Chris@18
|
225 * @return array
|
Chris@18
|
226 * A multidimensional array of source data from the relevant field instance
|
Chris@18
|
227 * migration, keyed first by entity type, then by bundle and finally by
|
Chris@18
|
228 * field name.
|
Chris@18
|
229 */
|
Chris@18
|
230 protected function getAllFields($core) {
|
Chris@18
|
231 if (empty($this->discoveredFieldsCache[$core])) {
|
Chris@18
|
232 $this->discoveredFieldsCache[$core] = [];
|
Chris@18
|
233 $source_plugin = $this->getSourcePlugin($core);
|
Chris@18
|
234 foreach ($source_plugin as $row) {
|
Chris@18
|
235 /** @var \Drupal\migrate\Row $row */
|
Chris@18
|
236 if ($core === FieldDiscoveryInterface::DRUPAL_7) {
|
Chris@18
|
237 $entity_type_id = $row->get('entity_type');
|
Chris@18
|
238 }
|
Chris@18
|
239 else {
|
Chris@18
|
240 $entity_type_id = 'node';
|
Chris@18
|
241 }
|
Chris@18
|
242 $bundle = $row->getSourceProperty($this->bundleKeys[$core]);
|
Chris@18
|
243 $this->discoveredFieldsCache[$core][$entity_type_id][$bundle][$row->getSourceProperty('field_name')] = $row->getSource();
|
Chris@18
|
244 }
|
Chris@18
|
245 }
|
Chris@18
|
246 return $this->discoveredFieldsCache[$core];
|
Chris@18
|
247 }
|
Chris@18
|
248
|
Chris@18
|
249 /**
|
Chris@18
|
250 * Gets all field information for a particular entity type.
|
Chris@18
|
251 *
|
Chris@18
|
252 * @param string $core
|
Chris@18
|
253 * The Drupal core version.
|
Chris@18
|
254 * @param string $entity_type_id
|
Chris@18
|
255 * The legacy entity type ID.
|
Chris@18
|
256 *
|
Chris@18
|
257 * @return array
|
Chris@18
|
258 * A multidimensional array of source data from the relevant field instance
|
Chris@18
|
259 * migration for the entity type, keyed first by bundle and then by field
|
Chris@18
|
260 * name.
|
Chris@18
|
261 */
|
Chris@18
|
262 protected function getEntityFields($core, $entity_type_id) {
|
Chris@18
|
263 $fields = $this->getAllFields($core);
|
Chris@18
|
264 if (!empty($fields[$entity_type_id])) {
|
Chris@18
|
265 return $fields[$entity_type_id];
|
Chris@18
|
266 }
|
Chris@18
|
267 return [];
|
Chris@18
|
268 }
|
Chris@18
|
269
|
Chris@18
|
270 /**
|
Chris@18
|
271 * Gets all field information for a particular entity type and bundle.
|
Chris@18
|
272 *
|
Chris@18
|
273 * @param string $core
|
Chris@18
|
274 * The Drupal core version.
|
Chris@18
|
275 * @param string $entity_type_id
|
Chris@18
|
276 * The legacy entity type ID.
|
Chris@18
|
277 * @param string $bundle
|
Chris@18
|
278 * The legacy bundle (or content_type).
|
Chris@18
|
279 *
|
Chris@18
|
280 * @return array
|
Chris@18
|
281 * An array of source data from the relevant field instance migration for
|
Chris@18
|
282 * the bundle, keyed by field name.
|
Chris@18
|
283 */
|
Chris@18
|
284 protected function getBundleFields($core, $entity_type_id, $bundle) {
|
Chris@18
|
285 $fields = $this->getEntityFields($core, $entity_type_id);
|
Chris@18
|
286 if (!empty($fields[$bundle])) {
|
Chris@18
|
287 return $fields[$bundle];
|
Chris@18
|
288 }
|
Chris@18
|
289 return [];
|
Chris@18
|
290 }
|
Chris@18
|
291
|
Chris@18
|
292 /**
|
Chris@18
|
293 * Gets the deprecated CCK Plugin Manager service as a BC shim.
|
Chris@18
|
294 *
|
Chris@18
|
295 * We don't inject this service directly because it is deprecated, and we
|
Chris@18
|
296 * don't want to instantiate the plugin manager unless we have to, to avoid
|
Chris@18
|
297 * triggering deprecation errors.
|
Chris@18
|
298 *
|
Chris@18
|
299 * @return \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
|
Chris@18
|
300 * The CCK Plugin Manager.
|
Chris@18
|
301 */
|
Chris@18
|
302 protected function getCckPluginManager() {
|
Chris@18
|
303 if (!$this->cckPluginManager) {
|
Chris@18
|
304 $this->cckPluginManager = \Drupal::service('plugin.manager.migrate.cckfield');
|
Chris@18
|
305 }
|
Chris@18
|
306 return $this->cckPluginManager;
|
Chris@18
|
307 }
|
Chris@18
|
308
|
Chris@18
|
309 /**
|
Chris@18
|
310 * Gets the source plugin to use to gather field information.
|
Chris@18
|
311 *
|
Chris@18
|
312 * @param string $core
|
Chris@18
|
313 * The Drupal core version.
|
Chris@18
|
314 *
|
Chris@18
|
315 * @return array|\Drupal\migrate\Plugin\MigrateSourceInterface
|
Chris@18
|
316 * The source plugin, or an empty array if none can be found that meets
|
Chris@18
|
317 * requirements.
|
Chris@18
|
318 */
|
Chris@18
|
319 protected function getSourcePlugin($core) {
|
Chris@18
|
320 $definition = $this->getFieldInstanceStubMigrationDefinition($core);
|
Chris@18
|
321 $source_plugin = $this->migrationPluginManager
|
Chris@18
|
322 ->createStubMigration($definition)
|
Chris@18
|
323 ->getSourcePlugin();
|
Chris@18
|
324 if ($source_plugin instanceof RequirementsInterface) {
|
Chris@18
|
325 try {
|
Chris@18
|
326 $source_plugin->checkRequirements();
|
Chris@18
|
327 }
|
Chris@18
|
328 catch (RequirementsException $e) {
|
Chris@18
|
329 // If checkRequirements() failed, the source database did not support
|
Chris@18
|
330 // fields (i.e., CCK is not installed in D6 or Field is not installed in
|
Chris@18
|
331 // D7). Therefore, $fields will be empty and below we'll return an empty
|
Chris@18
|
332 // array. The migration will proceed without adding fields.
|
Chris@18
|
333 $this->logger->notice('Field discovery failed for Drupal core version @core. Did this site have the CCK or Field module installed? Error: @message', [
|
Chris@18
|
334 '@core' => $core,
|
Chris@18
|
335 '@message' => $e->getMessage(),
|
Chris@18
|
336 ]);
|
Chris@18
|
337 return [];
|
Chris@18
|
338 }
|
Chris@18
|
339 }
|
Chris@18
|
340 return $source_plugin;
|
Chris@18
|
341 }
|
Chris@18
|
342
|
Chris@18
|
343 /**
|
Chris@18
|
344 * Provides the stub migration definition for a given Drupal core version.
|
Chris@18
|
345 *
|
Chris@18
|
346 * @param string $core
|
Chris@18
|
347 * The Drupal core version.
|
Chris@18
|
348 *
|
Chris@18
|
349 * @return array
|
Chris@18
|
350 * The stub migration definition.
|
Chris@18
|
351 */
|
Chris@18
|
352 protected function getFieldInstanceStubMigrationDefinition($core) {
|
Chris@18
|
353 return [
|
Chris@18
|
354 'destination' => ['plugin' => 'null'],
|
Chris@18
|
355 'idMap' => ['plugin' => 'null'],
|
Chris@18
|
356 'source' => [
|
Chris@18
|
357 'ignore_map' => TRUE,
|
Chris@18
|
358 'plugin' => $this->sourcePluginIds[$core],
|
Chris@18
|
359 ],
|
Chris@18
|
360 ];
|
Chris@18
|
361 }
|
Chris@18
|
362
|
Chris@18
|
363 /**
|
Chris@18
|
364 * Finds the core version of a Drupal migration.
|
Chris@18
|
365 *
|
Chris@18
|
366 * @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
Chris@18
|
367 * The migration.
|
Chris@18
|
368 *
|
Chris@18
|
369 * @return string|bool
|
Chris@18
|
370 * A string representation of the Drupal version, or FALSE.
|
Chris@18
|
371 *
|
Chris@18
|
372 * @throws \InvalidArgumentException
|
Chris@18
|
373 */
|
Chris@18
|
374 protected function getCoreVersion(MigrationInterface $migration) {
|
Chris@18
|
375 $tags = $migration->getMigrationTags();
|
Chris@18
|
376 if (in_array('Drupal 7', $tags, TRUE)) {
|
Chris@18
|
377 return FieldDiscoveryInterface::DRUPAL_7;
|
Chris@18
|
378 }
|
Chris@18
|
379 elseif (in_array('Drupal 6', $tags, TRUE)) {
|
Chris@18
|
380 return FieldDiscoveryInterface::DRUPAL_6;
|
Chris@18
|
381 }
|
Chris@18
|
382 throw new \InvalidArgumentException("Drupal Core version not found for this migration");
|
Chris@18
|
383 }
|
Chris@18
|
384
|
Chris@18
|
385 }
|