annotate core/modules/migrate_drupal/src/FieldDiscovery.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
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 }