comparison core/modules/migrate_drupal/src/FieldDiscovery.php @ 18:af1871eacc83

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