3 namespace Drupal\migrate_upgrade;
5 use Drupal\migrate\Plugin\MigrationInterface;
6 use Drupal\migrate\Event\MigrateEvents;
7 use Drupal\migrate\Event\MigrateIdMapMessageEvent;
8 use Drupal\migrate\MigrateExecutable;
9 use Drupal\Core\StringTranslation\StringTranslationTrait;
10 use Drupal\migrate_drupal\MigrationConfigurationTrait;
11 use Drupal\migrate_plus\Entity\Migration;
12 use Drupal\migrate_plus\Entity\MigrationGroup;
13 use Drupal\Core\Database\Database;
15 class MigrateUpgradeDrushRunner {
17 use MigrationConfigurationTrait;
18 use StringTranslationTrait;
21 * The list of migrations to run and their configuration.
23 * @var \Drupal\migrate\Plugin\Migration[]
25 protected $migrationList;
28 * MigrateMessage instance to display messages during the migration process.
30 * @var \Drupal\migrate_upgrade\DrushLogMigrateMessage
32 protected static $messages;
35 * The Drupal version being imported.
42 * The state key used to store database configuration.
46 protected $databaseStateKey;
49 * List of D6 node migration IDs we've seen.
53 protected $nodeMigrations = [];
56 * List of process plugin IDs used to lookup migrations.
60 protected $migrationLookupPluginIds = [
66 * From the provided source information, instantiate the appropriate migrations
67 * in the active configuration.
71 public function configure() {
72 $legacy_db_key = drush_get_option('legacy-db-key');
73 if (!empty($legacy_db_key)) {
74 $connection = Database::getConnection('default', drush_get_option('legacy-db-key'));
75 $this->version = $this->getLegacyDrupalVersion($connection);
76 $database_state['key'] = drush_get_option('legacy-db-key');
77 $database_state_key = 'migrate_drupal_' . $this->version;
78 \Drupal::state()->set($database_state_key, $database_state);
79 \Drupal::state()->set('migrate.fallback_state_key', $database_state_key);
82 $db_url = drush_get_option('legacy-db-url');
83 $db_spec = drush_convert_db_from_db_url($db_url);
84 $db_prefix = drush_get_option('legacy-db-prefix');
85 $db_spec['prefix'] = $db_prefix;
86 $connection = $this->getConnection($db_spec);
87 $this->version = $this->getLegacyDrupalVersion($connection);
88 $this->createDatabaseStateSettings($db_spec, $this->version);
91 $this->databaseStateKey = 'migrate_drupal_' . $this->version;
92 $migrations = $this->getMigrations($this->databaseStateKey, $this->version);
93 $this->migrationList = [];
94 foreach ($migrations as $migration) {
95 $this->applyFilePath($migration);
96 $this->expandNodeMigrations($migration);
97 $this->migrationList[$migration->id()] = $migration;
102 * Adds the source base path configuration to relevant migrations.
104 * @param \Drupal\migrate\Plugin\MigrationInterface $migration
105 * Migration to alter with the provided path.
107 protected function applyFilePath(MigrationInterface $migration) {
108 $destination = $migration->getDestinationConfiguration();
109 if ($destination['plugin'] === 'entity:file') {
110 // Make sure we have a single trailing slash.
111 $source_base_path = rtrim(drush_get_option('legacy-root'), '/') . '/';
112 $source = $migration->getSourceConfiguration();
113 $source['constants']['source_base_path'] = $source_base_path;
114 $migration->set('source', $source);
119 * For D6 term_node migrations, make sure the nid reference is expanded.
121 * @param \Drupal\migrate\Plugin\MigrationInterface $migration
122 * Migration to alter with the list of node migrations.
124 protected function expandNodeMigrations(MigrationInterface $migration) {
125 $source = $migration->getSourceConfiguration();
126 // Track the node migrations as we see them.
127 if ($source['plugin'] == 'd6_node') {
128 $this->nodeMigrations[] = $migration->id();
130 elseif ($source['plugin'] == 'd6_term_node' || $source['plugin'] == 'd6_term_node_revision') {
131 if ($source['plugin'] == 'd6_term_node') {
132 $id_property = 'nid';
135 $id_property = 'vid';
137 // If the ID mapping is to the underived d6_node migration, replace
138 // it with an expanded list of node migrations.
139 $process = $migration->getProcess();
140 $new_nid_process = [];
141 foreach ($process[$id_property] as $delta => $plugin_configuration) {
142 if (in_array($plugin_configuration['plugin'], $this->migrationLookupPluginIds) &&
143 is_string($plugin_configuration['migration']) &&
144 substr($plugin_configuration['migration'], -7) == 'd6_node') {
145 $plugin_configuration['migration'] = $this->nodeMigrations;
147 $new_nid_process[$delta] = $plugin_configuration;
149 $migration->setProcessOfProperty($id_property, $new_nid_process);
154 * Run the configured migrations.
156 public function import() {
157 static::$messages = new DrushLogMigrateMessage();
158 if (drush_get_option('debug')) {
159 \Drupal::service('event_dispatcher')->addListener(MigrateEvents::IDMAP_MESSAGE,
160 [get_class(), 'onIdMapMessage']);
162 foreach ($this->migrationList as $migration_id => $migration) {
163 drush_print(dt('Upgrading @migration', ['@migration' => $migration_id]));
164 $executable = new MigrateExecutable($migration, static::$messages);
165 // drush_op() provides --simulate support.
166 drush_op([$executable, 'import']);
171 * Export the configured migration plugins as configuration entities.
173 public function export() {
174 $db_info = \Drupal::state()->get($this->databaseStateKey);
176 // Create a group to hold the database configuration.
178 'id' => $this->databaseStateKey,
179 'label' => 'Import from Drupal ' . $this->version,
180 'description' => 'Migrations originally generated from drush migrate-upgrade --configure-only',
181 'source_type' => 'Drupal ' . $this->version,
182 'shared_configuration' => [
184 'key' => 'drupal_' . $this->version,
189 // Only add the database connection info to the configuration entity
190 // if it was passed in as a parameter.
191 if (!empty(drush_get_option('legacy-db-url'))) {
192 $group['shared_configuration']['source']['database'] = $db_info['database'];
195 // Ditto for the key.
196 if (!empty(drush_get_option('legacy-db-key'))) {
197 $group['shared_configuration']['source']['key'] = drush_get_option('legacy-db-key');
200 $group = MigrationGroup::create($group);
202 foreach ($this->migrationList as $migration_id => $migration) {
203 drush_print(dt('Exporting @migration as @new_migration',
204 ['@migration' => $migration_id, '@new_migration' => $this->modifyId($migration_id)]));
205 $entity_array['id'] = $migration_id;
206 $entity_array['class'] = $migration->get('class');
207 $entity_array['cck_plugin_method'] = $migration->get('cck_plugin_method');
208 $entity_array['field_plugin_method'] = $migration->get('field_plugin_method');
209 $entity_array['migration_group'] = $this->databaseStateKey;
210 $entity_array['migration_tags'] = $migration->get('migration_tags');
211 $entity_array['label'] = $migration->get('label');
212 $entity_array['source'] = $migration->getSourceConfiguration();
213 $entity_array['destination'] = $migration->getDestinationConfiguration();
214 $entity_array['process'] = $migration->get('process');
215 $entity_array['migration_dependencies'] = $migration->getMigrationDependencies();
216 $migration_entity = Migration::create($this->substituteIds($entity_array));
217 $migration_entity->save();
222 * Rewrite any migration plugin IDs so they won't conflict with the core
225 * @param $entity_array
226 * A configuration array for a migration.
229 * The migration configuration array modified with new IDs.
231 protected function substituteIds($entity_array) {
232 $entity_array['id'] = $this->modifyId($entity_array['id']);
233 foreach ($entity_array['migration_dependencies'] as $type => $dependencies) {
234 foreach ($dependencies as $key => $dependency) {
235 $entity_array['migration_dependencies'][$type][$key] = $this->modifyId($dependency);
238 $this->substituteMigrationIds($entity_array['process']);
239 return $entity_array;
243 * Recursively substitute IDs for migration plugins.
245 * @param mixed $process
247 protected function substituteMigrationIds(&$process) {
248 if (is_array($process)) {
249 // We found a migration plugin, change the ID.
250 if (isset($process['plugin']) && in_array($process['plugin'], $this->migrationLookupPluginIds)) {
251 if (is_array($process['migration'])) {
253 foreach ($process['migration'] as $migration) {
254 $new_migration[] = $this->modifyId($migration);
256 $process['migration'] = $new_migration;
259 $process['migration'] = $this->modifyId($process['migration']);
261 // The source_ids configuration for migrate_lookup is keyed by
262 // migration id. If it is there, we need to rekey to the new ids.
263 if (isset($process['source_ids']) && is_array($process['source_ids'])) {
264 $new_source_ids = [];
265 foreach ($process['source_ids'] as $migration_id => $source_ids) {
266 $new_migration_id = $this->modifyId($migration_id);
267 $new_source_ids[$new_migration_id] = $source_ids;
269 $process['source_ids'] = $new_source_ids;
273 // Recurse on each array member.
274 foreach ($process as &$subprocess) {
275 $this->substituteMigrationIds($subprocess);
283 * The original core plugin ID.
286 * The ID modified to serve as a configuration entity ID.
288 protected function modifyId($id) {
289 return drush_get_option('migration-prefix', 'upgrade_') . str_replace(':', '_', $id);
293 * Rolls back the configured migrations.
295 public function rollback() {
296 static::$messages = new DrushLogMigrateMessage();
297 $database_state_key = \Drupal::state()->get('migrate.fallback_state_key');
298 $database_state = \Drupal::state()->get($database_state_key);
299 $db_spec = $database_state['database'];
300 $connection = $this->getConnection($db_spec);
301 $version = $this->getLegacyDrupalVersion($connection);
302 $migrations = $this->getMigrations('migrate_drupal_' . $version, $version);
304 // Roll back in reverse order.
305 $this->migrationList = array_reverse($migrations);
307 foreach ($migrations as $migration) {
308 drush_print(dt('Rolling back @migration', ['@migration' => $migration->id()]));
309 $executable = new MigrateExecutable($migration, static::$messages);
310 // drush_op() provides --simulate support.
311 drush_op([$executable, 'rollback']);
316 * Display any messages being logged to the ID map.
318 * @param \Drupal\migrate\Event\MigrateIdMapMessageEvent $event
321 public static function onIdMapMessage(MigrateIdMapMessageEvent $event) {
322 if ($event->getLevel() == MigrationInterface::MESSAGE_NOTICE ||
323 $event->getLevel() == MigrationInterface::MESSAGE_INFORMATIONAL) {
329 $source_id_string = implode(',', $event->getSourceIdValues());
330 $message = t('Source ID @source_id: @message',
331 ['@source_id' => $source_id_string, '@message' => $event->getMessage()]);
332 static::$messages->display($message, $type);