.migrate_drupal.yml file. * * The .migrate_drupal.yml file uses the following structure: * * # (optional) List of the source_module/destination_module(s) for the * # migration sets that this module provides and are complete. * finished: * # One or more Drupal legacy version number mappings (i.e. 6 and/or 7). * 6: * # A mapping of legacy module machine names to either an array of modules * # or a single destination module machine name to define this migration * # set. * : * : * - * - * 7: * : * : * - * - * # (optional) List of the migration sets that this module provides, or will be * # providing, that are incomplete or do not yet exist. * not_finished: * 6: * : * : * - * - * * Examples: * * @code * finished: * 6: * node: node * 7: * node: node * entity_translation: node * not_finished: * 7: * commerce_product: commerce_product * other_module: * - other_module * - further_module * @endcode * * In this example the module has completed the upgrade path for data from the * Drupal 6 and Drupal 7 Node modules to the Drupal 8 Node module and for data * from the Drupal 7 Entity Translation module to the Drupal 8 Node module. * * @code * finished: * 6: * pirate: pirate * 7: * pirate: pirate * @endcode * * The Pirate module does not require an upgrade path. By declaring the upgrade * finished the Pirate module will be included in the finished list. That is, * as long as no other module has an entry "pirate: ' in its * not_finished section. */ class MigrationState { use MessengerTrait; use StringTranslationTrait; /** * Source module upgrade state when all its migrations are complete. * * @var string */ const FINISHED = 'finished'; /** * Source module upgrade state when all its migrations are not complete. * * @var string */ const NOT_FINISHED = 'not_finished'; /** * The field plugin manager service. * * @var \Drupal\Core\Extension\ModuleHandler */ protected $moduleHandler; /** * The field plugin manager service. * * @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface */ protected $fieldPluginManager; /** * An array of migration states declared for each source migration. * * States are keyed by version. Each value is an array keyed by name of the * source module and the value is an array of all the states declared for this * source module. * * @var array */ protected $stateBySource; /** * An array of destinations declared for each source migration. * * Destinations are keyed by version. Each value is an array keyed by the name * of the source module and the value is an array of the destination modules. * * @var array */ protected $declaredBySource; /** * An array of migration source and destinations derived from migrations. * * The key is the source version and the value is an array where the key is * the source module and the value is an array of destinations derived from * migration plugins. * * @var array */ protected $discoveredBySource; /** * An array of migration source and destinations. * * Values are derived from migration plugins and declared states. The key is * the source version and the value is an array where the key is the source * module and the value is an array of declared or derived destinations. * * @var array */ protected $destinations = []; /** * Array of enabled modules. * * @var array */ protected $enabledModules = []; /** * Construct a new MigrationState object. * * @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $fieldPluginManager * Field plugin manager. * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler * Module handler. * @param \Drupal\Core\Messenger\MessengerInterface $messenger * Messenger service. * @param \Drupal\Core\StringTranslation\TranslationInterface $stringTranslation * String translation service. */ public function __construct(MigrateFieldPluginManagerInterface $fieldPluginManager, ModuleHandlerInterface $moduleHandler, MessengerInterface $messenger, TranslationInterface $stringTranslation) { $this->fieldPluginManager = $fieldPluginManager; $this->moduleHandler = $moduleHandler; $this->enabledModules = array_keys($this->moduleHandler->getModuleList()); $this->enabledModules[] = 'core'; $this->messenger = $messenger; $this->stringTranslation = $stringTranslation; } /** * Gets the upgrade states for all enabled source modules. * * @param string $version * The legacy drupal version. * @param array $source_system_data * The data from the source site system table. * @param array $migrations * An array of migrations. * * @return array * An associative array of data with keys of state, source modules and a * value which is a comma separated list of destination modules. */ public function getUpgradeStates($version, array $source_system_data, array $migrations) { return $this->buildUpgradeState($version, $source_system_data, $migrations); } /** * Gets migration state information from *.migrate_drupal.yml. * * @return array * An association array keyed by module of the finished and not_finished * migrations for each module. * */ protected function getMigrationStates() { // Always instantiate a new YamlDiscovery object so that we always search on // the up-to-date list of modules. $discovery = new YamlDiscovery('migrate_drupal', array_map(function ($value) { return $value . '/migrations/state'; }, $this->moduleHandler->getModuleDirectories())); return $discovery->findAll(); } /** * Determines migration state for each source module enabled on the source. * * If there are no migrations for a module and no declared state the state is * set to NOT_FINISHED. When a module does not need any migrations, such as * Overlay, a state of finished is declared in system.migrate_drupal.yml. * * If there are migrations for a module the following happens. If the * destination module is 'core' the state is set to FINISHED. If there are * any occurrences of 'not_finished' in the *.migrate_drupal.yml information * for this source module then the state is set to NOT_FINISHED. And finally, * if there is an occurrence of 'finished' the state is set to FINISHED. * * @param string $version * The legacy drupal version. * @param array $source_system_data * The data from the source site system table. * @param array $migrations * An array of migrations. * * @return array * An associative array of data with keys of state, source modules and a * value which is a comma separated list of destination modules. * Example. * * @code * [ * 'finished' => [ * 'menu' => [ * 'menu_link_content','menu_ui','system' * ] * ], * ] * @endcode */ protected function buildUpgradeState($version, array $source_system_data, array $migrations) { // Remove core profiles from the system data. unset($source_system_data['module']['standard'], $source_system_data['module']['minimal']); $this->buildDiscoveredDestinationsBySource($version, $migrations, $source_system_data); $this->buildDeclaredStateBySource($version); $upgrade_state = []; // Loop through every source module that is enabled on the source site. foreach ($source_system_data['module'] as $module) { // The source plugins check requirements requires that all // source_modules are enabled so do the same here. if ($module['status']) { $source_module = $module['name']; $upgrade_state[$this->getSourceState($version, $source_module)][$source_module] = implode(', ', $this->getDestinationsForSource($version, $source_module)); } } foreach ($upgrade_state as $key => $value) { ksort($upgrade_state[$key]); } return $upgrade_state; } /** * Builds migration source and destination module information. * * @param string $version * The legacy Drupal version. * @param array $migrations * The discovered migrations. * @param array $source_system_data * The data from the source site system table. */ protected function buildDiscoveredDestinationsBySource($version, array $migrations, array $source_system_data) { $discovered_upgrade_paths = []; $table_data = []; foreach ($migrations as $migration) { $migration_id = $migration->getPluginId(); $source_module = $migration->getSourcePlugin()->getSourceModule(); if (!$source_module) { $this->messenger() ->addError($this->t('Source module not found for @migration_id.', ['@migration_id' => $migration_id])); } $destination_module = $migration->getDestinationPlugin() ->getDestinationModule(); if (!$destination_module) { $this->messenger() ->addError($this->t('Destination module not found for @migration_id.', ['@migration_id' => $migration_id])); } if ($source_module && $destination_module) { $discovered_upgrade_paths[$source_module][] = $destination_module; $table_data[$source_module][$destination_module][$migration_id] = $migration->label(); } } // Add entries for the field plugins to discovered_upgrade_paths. $definitions = $this->fieldPluginManager->getDefinitions(); foreach ($definitions as $definition) { // This is not strict so that we find field plugins with an annotation // where the Drupal core version is an integer and when it is a string. if (in_array($version, $definition['core'])) { $source_module = $definition['source_module']; $destination_module = $definition['destination_module']; $discovered_upgrade_paths[$source_module][] = $destination_module; $table_data[$source_module][$destination_module][$definition['id']] = $definition['id']; } } ksort($table_data); foreach ($table_data as $source_module => $destination_module_info) { ksort($table_data[$source_module]); } $this->discoveredBySource[$version] = array_map('array_unique', $discovered_upgrade_paths); } /** * Gets migration data from *.migrate_drupal.yml sorted by source module. * * @param string $version * The legacy Drupal version. */ protected function buildDeclaredStateBySource($version) { $migration_states = $this->getMigrationStates(); $state_by_source = []; $dest_by_source = []; $states = [static::FINISHED, static::NOT_FINISHED]; foreach ($migration_states as $info) { foreach ($states as $state) { if (isset($info[$state][$version])) { foreach ($info[$state][$version] as $source => $destination) { // Add the state. $state_by_source[$source][] = $state; // Add the destination modules. $dest_by_source += [$source => []]; $dest_by_source[$source] = array_merge($dest_by_source[$source], (array) $destination); } } } } $this->stateBySource[$version] = array_map('array_unique', $state_by_source); $this->declaredBySource[$version] = array_map('array_unique', $dest_by_source); } /** * Tests if a destination exists for the given source module. * * @param string $version * Source version of Drupal. * @param string $source_module * Source module. * * @return string * Migration state, either 'finished' or 'not_finished'. */ protected function getSourceState($version, $source_module) { // The state is finished only when no declarations of 'not_finished' // were found and each destination module is enabled. if (!$destinations = $this->getDestinationsForSource($version, $source_module)) { // No discovered or declared state. return MigrationState::NOT_FINISHED; } if (!isset($this->stateBySource[$version][$source_module])) { // No declared state. return MigrationState::NOT_FINISHED; } if (in_array(MigrationState::NOT_FINISHED, $this->stateBySource[$version][$source_module], TRUE) || !in_array(MigrationState::FINISHED, $this->stateBySource[$version][$source_module], TRUE)) { return MigrationState::NOT_FINISHED; } if (array_diff($destinations, $this->enabledModules)) { return MigrationState::NOT_FINISHED; } return MigrationState::FINISHED; } /** * Get net destinations for source module. * * @param string $version * Source version. * @param string $source_module * Source module. * * @return array * Destination modules either declared by {modulename}.migrate_drupal.yml * files or discovered from migration plugins. */ protected function getDestinationsForSource($version, $source_module) { if (!isset($this->destinations[$version][$source_module])) { $this->discoveredBySource[$version] += [$source_module => []]; $this->declaredBySource[$version] += [$source_module => []]; $destination = array_unique(array_merge($this->discoveredBySource[$version][$source_module], $this->declaredBySource[$version][$source_module])); sort($destination); $this->destinations[$version][$source_module] = $destination; } return $this->destinations[$version][$source_module]; } }