Chris@18: 'type_name', Chris@18: FieldDiscoveryInterface::DRUPAL_7 => 'bundle', Chris@18: ]; Chris@18: Chris@18: /** Chris@18: * An array of source plugin ids, keyed by Drupal core version. Chris@18: * Chris@18: * @var array Chris@18: */ Chris@18: protected $sourcePluginIds = [ Chris@18: FieldDiscoveryInterface::DRUPAL_6 => 'd6_field_instance', Chris@18: FieldDiscoveryInterface::DRUPAL_7 => 'd7_field_instance', Chris@18: ]; Chris@18: Chris@18: /** Chris@18: * An array of supported Drupal core versions. Chris@18: * Chris@18: * @var array Chris@18: */ Chris@18: protected $supportedCoreVersions = [ Chris@18: FieldDiscoveryInterface::DRUPAL_6, Chris@18: FieldDiscoveryInterface::DRUPAL_7, Chris@18: ]; Chris@18: Chris@18: /** Chris@18: * Constructs a FieldDiscovery object. Chris@18: * Chris@18: * @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $field_plugin_manager Chris@18: * The field plugin manager. Chris@18: * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager Chris@18: * The migration plugin manager. Chris@18: * @param \Drupal\Core\Logger\LoggerChannelInterface $logger Chris@18: * The logger channel service. Chris@18: */ Chris@18: public function __construct(MigrateFieldPluginManagerInterface $field_plugin_manager, MigrationPluginManagerInterface $migration_plugin_manager, LoggerChannelInterface $logger) { Chris@18: $this->fieldPluginManager = $field_plugin_manager; Chris@18: $this->migrationPluginManager = $migration_plugin_manager; Chris@18: $this->logger = $logger; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: public function addAllFieldProcesses(MigrationInterface $migration) { Chris@18: $core = $this->getCoreVersion($migration); Chris@18: $fields = $this->getAllFields($core); Chris@18: foreach ($fields as $entity_type_id => $bundle) { Chris@18: $this->addEntityFieldProcesses($migration, $entity_type_id); Chris@18: } Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: public function addEntityFieldProcesses(MigrationInterface $migration, $entity_type_id) { Chris@18: $core = $this->getCoreVersion($migration); Chris@18: $fields = $this->getAllFields($core); Chris@18: if (!empty($fields[$entity_type_id]) && is_array($fields[$entity_type_id])) { Chris@18: foreach ($fields[$entity_type_id] as $bundle => $fields) { Chris@18: $this->addBundleFieldProcesses($migration, $entity_type_id, $bundle); Chris@18: } Chris@18: } Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: public function addBundleFieldProcesses(MigrationInterface $migration, $entity_type_id, $bundle) { Chris@18: $core = $this->getCoreVersion($migration); Chris@18: $fields = $this->getAllFields($core); Chris@18: $plugin_definition = $migration->getPluginDefinition(); Chris@18: if (empty($fields[$entity_type_id][$bundle])) { Chris@18: return; Chris@18: } Chris@18: $bundle_fields = $fields[$entity_type_id][$bundle]; Chris@18: foreach ($bundle_fields as $field_name => $field_info) { Chris@18: $plugin = $this->getFieldPlugin($field_info['type'], $migration); Chris@18: if ($plugin) { Chris@18: $method = isset($plugin_definition['field_plugin_method']) ? $plugin_definition['field_plugin_method'] : 'defineValueProcessPipeline'; Chris@18: Chris@18: // @todo Remove the following 3 lines of code prior to Drupal 9.0.0. Chris@18: // https://www.drupal.org/node/3032317 Chris@18: if ($plugin instanceof MigrateCckFieldInterface) { Chris@18: $method = isset($plugin_definition['cck_plugin_method']) ? $plugin_definition['cck_plugin_method'] : 'processCckFieldValues'; Chris@18: } Chris@18: Chris@18: call_user_func_array([ Chris@18: $plugin, Chris@18: $method, Chris@18: ], [ Chris@18: $migration, Chris@18: $field_name, Chris@18: $field_info, Chris@18: ]); Chris@18: } Chris@18: else { Chris@18: // Default to a get process plugin if this is a value migration. Chris@18: 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: $migration->setProcessOfProperty($field_name, $field_name); Chris@18: } Chris@18: } Chris@18: } Chris@18: } Chris@18: Chris@18: /** Chris@18: * Returns the appropriate field plugin for a given field type. Chris@18: * Chris@18: * @param string $field_type Chris@18: * The field type. Chris@18: * @param \Drupal\migrate\Plugin\MigrationInterface $migration Chris@18: * The migration to retrieve the plugin for. Chris@18: * Chris@18: * @return \Drupal\migrate_drupal\Plugin\MigrateCckFieldInterface|\Drupal\migrate_drupal\Plugin\MigrateFieldInterface|bool Chris@18: * The appropriate field or cck plugin to process this field type. Chris@18: * Chris@18: * @throws \Drupal\Component\Plugin\Exception\PluginException Chris@18: * @throws \InvalidArgumentException Chris@18: */ Chris@18: protected function getFieldPlugin($field_type, MigrationInterface $migration) { Chris@18: $core = $this->getCoreVersion($migration); Chris@18: if (!isset($this->fieldPluginCache[$core][$field_type])) { Chris@18: try { Chris@18: $plugin_id = $this->fieldPluginManager->getPluginIdFromFieldType($field_type, ['core' => $core], $migration); Chris@18: $plugin = $this->fieldPluginManager->createInstance($plugin_id, ['core' => $core], $migration); Chris@18: } Chris@18: catch (PluginNotFoundException $ex) { Chris@18: // @todo Replace try/catch block with $plugin = FALSE for Drupal 9. Chris@18: // https://www.drupal.org/project/drupal/issues/3033733 Chris@18: try { Chris@18: /** @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManager $cck_plugin_manager */ Chris@18: $cck_plugin_manager = $this->getCckPluginManager(); Chris@18: $plugin_id = $cck_plugin_manager->getPluginIdFromFieldType($field_type, ['core' => $core], $migration); Chris@18: $plugin = $cck_plugin_manager->createInstance($plugin_id, ['core' => $core], $migration); Chris@18: } Chris@18: catch (PluginNotFoundException $ex) { Chris@18: $plugin = FALSE; Chris@18: } Chris@18: } Chris@18: $this->fieldPluginCache[$core][$field_type] = $plugin; Chris@18: } Chris@18: return $this->fieldPluginCache[$core][$field_type]; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Gets all field information related to this migration. Chris@18: * Chris@18: * @param string $core Chris@18: * The Drupal core version to get fields for. Chris@18: * Chris@18: * @return array Chris@18: * A multidimensional array of source data from the relevant field instance Chris@18: * migration, keyed first by entity type, then by bundle and finally by Chris@18: * field name. Chris@18: */ Chris@18: protected function getAllFields($core) { Chris@18: if (empty($this->discoveredFieldsCache[$core])) { Chris@18: $this->discoveredFieldsCache[$core] = []; Chris@18: $source_plugin = $this->getSourcePlugin($core); Chris@18: foreach ($source_plugin as $row) { Chris@18: /** @var \Drupal\migrate\Row $row */ Chris@18: if ($core === FieldDiscoveryInterface::DRUPAL_7) { Chris@18: $entity_type_id = $row->get('entity_type'); Chris@18: } Chris@18: else { Chris@18: $entity_type_id = 'node'; Chris@18: } Chris@18: $bundle = $row->getSourceProperty($this->bundleKeys[$core]); Chris@18: $this->discoveredFieldsCache[$core][$entity_type_id][$bundle][$row->getSourceProperty('field_name')] = $row->getSource(); Chris@18: } Chris@18: } Chris@18: return $this->discoveredFieldsCache[$core]; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Gets all field information for a particular entity type. Chris@18: * Chris@18: * @param string $core Chris@18: * The Drupal core version. Chris@18: * @param string $entity_type_id Chris@18: * The legacy entity type ID. Chris@18: * Chris@18: * @return array Chris@18: * A multidimensional array of source data from the relevant field instance Chris@18: * migration for the entity type, keyed first by bundle and then by field Chris@18: * name. Chris@18: */ Chris@18: protected function getEntityFields($core, $entity_type_id) { Chris@18: $fields = $this->getAllFields($core); Chris@18: if (!empty($fields[$entity_type_id])) { Chris@18: return $fields[$entity_type_id]; Chris@18: } Chris@18: return []; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Gets all field information for a particular entity type and bundle. Chris@18: * Chris@18: * @param string $core Chris@18: * The Drupal core version. Chris@18: * @param string $entity_type_id Chris@18: * The legacy entity type ID. Chris@18: * @param string $bundle Chris@18: * The legacy bundle (or content_type). Chris@18: * Chris@18: * @return array Chris@18: * An array of source data from the relevant field instance migration for Chris@18: * the bundle, keyed by field name. Chris@18: */ Chris@18: protected function getBundleFields($core, $entity_type_id, $bundle) { Chris@18: $fields = $this->getEntityFields($core, $entity_type_id); Chris@18: if (!empty($fields[$bundle])) { Chris@18: return $fields[$bundle]; Chris@18: } Chris@18: return []; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Gets the deprecated CCK Plugin Manager service as a BC shim. Chris@18: * Chris@18: * We don't inject this service directly because it is deprecated, and we Chris@18: * don't want to instantiate the plugin manager unless we have to, to avoid Chris@18: * triggering deprecation errors. Chris@18: * Chris@18: * @return \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface Chris@18: * The CCK Plugin Manager. Chris@18: */ Chris@18: protected function getCckPluginManager() { Chris@18: if (!$this->cckPluginManager) { Chris@18: $this->cckPluginManager = \Drupal::service('plugin.manager.migrate.cckfield'); Chris@18: } Chris@18: return $this->cckPluginManager; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Gets the source plugin to use to gather field information. Chris@18: * Chris@18: * @param string $core Chris@18: * The Drupal core version. Chris@18: * Chris@18: * @return array|\Drupal\migrate\Plugin\MigrateSourceInterface Chris@18: * The source plugin, or an empty array if none can be found that meets Chris@18: * requirements. Chris@18: */ Chris@18: protected function getSourcePlugin($core) { Chris@18: $definition = $this->getFieldInstanceStubMigrationDefinition($core); Chris@18: $source_plugin = $this->migrationPluginManager Chris@18: ->createStubMigration($definition) Chris@18: ->getSourcePlugin(); Chris@18: if ($source_plugin instanceof RequirementsInterface) { Chris@18: try { Chris@18: $source_plugin->checkRequirements(); Chris@18: } Chris@18: catch (RequirementsException $e) { Chris@18: // If checkRequirements() failed, the source database did not support Chris@18: // fields (i.e., CCK is not installed in D6 or Field is not installed in Chris@18: // D7). Therefore, $fields will be empty and below we'll return an empty Chris@18: // array. The migration will proceed without adding fields. Chris@18: $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: '@core' => $core, Chris@18: '@message' => $e->getMessage(), Chris@18: ]); Chris@18: return []; Chris@18: } Chris@18: } Chris@18: return $source_plugin; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Provides the stub migration definition for a given Drupal core version. Chris@18: * Chris@18: * @param string $core Chris@18: * The Drupal core version. Chris@18: * Chris@18: * @return array Chris@18: * The stub migration definition. Chris@18: */ Chris@18: protected function getFieldInstanceStubMigrationDefinition($core) { Chris@18: return [ Chris@18: 'destination' => ['plugin' => 'null'], Chris@18: 'idMap' => ['plugin' => 'null'], Chris@18: 'source' => [ Chris@18: 'ignore_map' => TRUE, Chris@18: 'plugin' => $this->sourcePluginIds[$core], Chris@18: ], Chris@18: ]; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Finds the core version of a Drupal migration. Chris@18: * Chris@18: * @param \Drupal\migrate\Plugin\MigrationInterface $migration Chris@18: * The migration. Chris@18: * Chris@18: * @return string|bool Chris@18: * A string representation of the Drupal version, or FALSE. Chris@18: * Chris@18: * @throws \InvalidArgumentException Chris@18: */ Chris@18: protected function getCoreVersion(MigrationInterface $migration) { Chris@18: $tags = $migration->getMigrationTags(); Chris@18: if (in_array('Drupal 7', $tags, TRUE)) { Chris@18: return FieldDiscoveryInterface::DRUPAL_7; Chris@18: } Chris@18: elseif (in_array('Drupal 6', $tags, TRUE)) { Chris@18: return FieldDiscoveryInterface::DRUPAL_6; Chris@18: } Chris@18: throw new \InvalidArgumentException("Drupal Core version not found for this migration"); Chris@18: } Chris@18: Chris@18: }