Updated all the contrib modules to their latest versions.
[yaffs-website] / web / modules / contrib / migrate_tools / migrate_tools.drush.inc
1 <?php
2
3 /**
4  * @file
5  * Command-line tools to aid performing and developing migrations.
6  */
7
8 use Drupal\Component\Utility\Unicode;
9 use Drupal\migrate\Exception\RequirementsException;
10 use Drupal\migrate\Plugin\MigrationInterface;
11 use Drupal\migrate\Plugin\RequirementsInterface;
12 use Drupal\migrate_plus\Entity\MigrationGroup;
13 use Drupal\migrate_tools\DrushLogMigrateMessage;
14 use Drupal\migrate_tools\MigrateExecutable;
15
16 /**
17  * Implements hook_drush_command().
18  */
19 function migrate_tools_drush_command() {
20   $items['migrate-status'] = [
21     'description' => 'List all migrations with current status.',
22     'options' => [
23       'group' => 'A comma-separated list of migration groups to list',
24       'tag' => 'Name of the migration tag to list',
25       'names-only' => 'Only return names, not all the details (faster)',
26     ],
27     'arguments' => [
28       'migration' => 'Restrict to a comma-separated list of migrations. Optional',
29     ],
30     'examples' => [
31       'migrate-status' => 'Retrieve status for all migrations',
32       'migrate-status --group=beer' => 'Retrieve status for all migrations in a given group',
33       'migrate-status --tag=user' => 'Retrieve status for all migrations with a given tag',
34       'migrate-status --group=beer --tag=user' => 'Retrieve status for all migrations in the beer group and with the user tag',
35       'migrate-status beer_term,beer_node' => 'Retrieve status for specific migrations',
36     ],
37     'drupal dependencies' => ['migrate_tools'],
38     'aliases' => ['ms'],
39   ];
40
41   $items['migrate-import'] = [
42     'description' => 'Perform one or more migration processes.',
43     'options' => [
44       'all' => 'Process all migrations.',
45       'group' => 'A comma-separated list of migration groups to import',
46       'tag' => 'Name of the migration tag to import',
47       'limit' => 'Limit on the number of items to process in each migration',
48       'feedback' => 'Frequency of progress messages, in items processed',
49       'idlist' => 'Comma-separated list of IDs to import',
50       'update' => ' In addition to processing unprocessed items from the source, update previously-imported items with the current data',
51       'force' => 'Force an operation to run, even if all dependencies are not satisfied',
52       'execute-dependencies' => 'Execute all dependent migrations first.',
53     ],
54     'arguments' => [
55       'migration' => 'ID of migration(s) to import. Delimit multiple using commas.',
56     ],
57     'examples' => [
58       'migrate-import --all' => 'Perform all migrations',
59       'migrate-import --group=beer' => 'Import all migrations in the beer group',
60       'migrate-import --tag=user' => 'Import all migrations with the user tag',
61       'migrate-import --group=beer --tag=user' => 'Import all migrations in the beer group and with the user tag',
62       'migrate-import beer_term,beer_node' => 'Import new terms and nodes',
63       'migrate-import beer_user --limit=2' => 'Import no more than 2 users',
64       'migrate-import beer_user --idlist=5' => 'Import the user record with source ID 5',
65     ],
66     'drupal dependencies' => ['migrate_tools'],
67     'aliases' => ['mi', 'mim'],
68   ];
69
70   $items['migrate-rollback'] = [
71     'description' => 'Rollback one or more migrations.',
72     'options' => [
73       'all' => 'Process all migrations.',
74       'group' => 'A comma-separated list of migration groups to rollback',
75       'tag' => 'ID of the migration tag to rollback',
76       'feedback' => 'Frequency of progress messages, in items processed',
77     ],
78     'arguments' => [
79       'migration' => 'Name of migration(s) to rollback. Delimit multiple using commas.',
80     ],
81     'examples' => [
82       'migrate-rollback --all' => 'Perform all migrations',
83       'migrate-rollback --group=beer' => 'Rollback all migrations in the beer group',
84       'migrate-rollback --tag=user' => 'Rollback all migrations with the user tag',
85       'migrate-rollback --group=beer --tag=user' => 'Rollback all migrations in the beer group and with the user tag',
86       'migrate-rollback beer_term,beer_node' => 'Rollback imported terms and nodes',
87     ],
88     'drupal dependencies' => ['migrate_tools'],
89     'aliases' => ['mr'],
90   ];
91
92   $items['migrate-stop'] = [
93     'description' => 'Stop an active migration operation.',
94     'arguments' => [
95       'migration' => 'ID of migration to stop',
96     ],
97     'drupal dependencies' => ['migrate_tools'],
98     'aliases' => ['mst'],
99   ];
100
101   $items['migrate-reset-status'] = [
102     'description' => 'Reset a active migration\'s status to idle.',
103     'arguments' => [
104       'migration' => 'ID of migration to reset',
105     ],
106     'drupal dependencies' => ['migrate_tools'],
107     'aliases' => ['mrs'],
108   ];
109
110   $items['migrate-messages'] = [
111     'description' => 'View any messages associated with a migration.',
112     'arguments' => [
113       'migration' => 'ID of the migration',
114     ],
115     'options' => [
116       'csv' => 'Export messages as a CSV',
117     ],
118     'examples' => [
119       'migrate-messages MyNode' => 'Show all messages for the MyNode migration',
120     ],
121     'drupal dependencies' => ['migrate_tools'],
122     'aliases' => ['mmsg'],
123   ];
124
125   $items['migrate-fields-source'] = [
126     'description' => 'List the fields available for mapping in a source.',
127     'arguments' => [
128       'migration' => 'ID of the migration',
129     ],
130     'examples' => [
131       'migrate-fields-source my_node' => 'List fields for the source in the my_node migration',
132     ],
133     'drupal dependencies' => ['migrate_tools'],
134     'aliases' => ['mfs'],
135   ];
136
137   return $items;
138 }
139
140 /**
141  * Display migration status.
142  *
143  * @param string $migration_names
144  *   The migration names.
145  */
146 function drush_migrate_tools_migrate_status($migration_names = '') {
147   $names_only = drush_get_option('names-only');
148
149   $migrations = drush_migrate_tools_migration_list($migration_names);
150
151   $table = [];
152   // Take it one group at a time, listing the migrations within each group.
153   foreach ($migrations as $group_id => $migration_list) {
154     $group = MigrationGroup::load($group_id);
155     $group_name = !empty($group) ? "{$group->label()} ({$group->id()})" : $group_id;
156     if ($names_only) {
157       $table[] = [
158         dt('Group: @name', ['@name' => $group_name]),
159       ];
160     }
161     else {
162       $table[] = [
163         dt('Group: @name', ['@name' => $group_name]),
164         dt('Status'),
165         dt('Total'),
166         dt('Imported'),
167         dt('Unprocessed'),
168         dt('Last imported'),
169       ];
170     }
171     foreach ($migration_list as $migration_id => $migration) {
172       try {
173         $map = $migration->getIdMap();
174         $imported = $map->importedCount();
175         $source_plugin = $migration->getSourcePlugin();
176       }
177       catch (Exception $e) {
178         drush_log(dt('Failure retrieving information on @migration: @message',
179           ['@migration' => $migration_id, '@message' => $e->getMessage()]));
180         continue;
181       }
182       if ($names_only) {
183         $table[] = [$migration_id];
184       }
185       else {
186         try {
187           $source_rows = $source_plugin->count();
188           // -1 indicates uncountable sources.
189           if ($source_rows == -1) {
190             $source_rows = dt('N/A');
191             $unprocessed = dt('N/A');
192           }
193           else {
194             $unprocessed = $source_rows - $map->processedCount();
195           }
196         }
197         catch (Exception $e) {
198           drush_print($e->getMessage());
199           drush_log(dt('Could not retrieve source count from @migration: @message',
200             ['@migration' => $migration_id, '@message' => $e->getMessage()]));
201           $source_rows = dt('N/A');
202           $unprocessed = dt('N/A');
203         }
204
205         $status = $migration->getStatusLabel();
206         $migrate_last_imported_store = \Drupal::keyValue('migrate_last_imported');
207         $last_imported = $migrate_last_imported_store->get($migration->id(), FALSE);
208         if ($last_imported) {
209           /** @var \Drupal\Core\Datetime\DateFormatter $date_formatter */
210           $date_formatter = \Drupal::service('date.formatter');
211           $last_imported = $date_formatter->format($last_imported / 1000,
212             'custom', 'Y-m-d H:i:s');
213         }
214         else {
215           $last_imported = '';
216         }
217         $table[] = [
218           $migration_id,
219           $status,
220           $source_rows,
221           $imported,
222           $unprocessed,
223           $last_imported,
224         ];
225       }
226     }
227   }
228   drush_print_table($table);
229 }
230
231 /**
232  * Import a migration.
233  *
234  * @param string $migration_names
235  *   The migration names.
236  */
237 function drush_migrate_tools_migrate_import($migration_names = '') {
238   $group_names = drush_get_option('group');
239   $tag_names = drush_get_option('tag');
240   $all = drush_get_option('all');
241
242   // Display a depreciation message if "mi" alias is used.
243   $args = drush_get_arguments();
244   if ($args[0] === 'mi') {
245     drush_log('The \'mi\' alias is deprecated and will no longer work with Drush 9. Consider the use of \'mim\' alias instead.', 'warning');
246   }
247
248   $options = [];
249   if (!$all && !$group_names && !$migration_names && !$tag_names) {
250     drush_set_error('MIGRATE_ERROR', dt('You must specify --all, --group, --tag or one or more migration names separated by commas'));
251     return;
252   }
253
254   foreach (['limit', 'feedback', 'idlist', 'update', 'force'] as $option) {
255     if (drush_get_option($option)) {
256       $options[$option] = drush_get_option($option);
257     }
258   }
259
260   $migrations = drush_migrate_tools_migration_list($migration_names);
261   if (empty($migrations)) {
262     drush_log(dt('No migrations found.'), 'error');
263   }
264
265   // Take it one group at a time, importing the migrations within each group.
266   foreach ($migrations as $group_id => $migration_list) {
267     array_walk($migration_list, '_drush_migrate_tools_execute_migration', $options);
268   }
269 }
270
271 /**
272  * Executes a single migration.
273  *
274  * If the --execute-dependencies option was given, the migration's dependencies
275  * will also be executed first.
276  *
277  * @param \Drupal\migrate\Plugin\MigrationInterface $migration
278  *   The migration to execute.
279  * @param string $migration_id
280  *   The migration ID (not used, just an artifact of array_walk()).
281  * @param array $options
282  *   Additional options for the migration.
283  */
284 function _drush_migrate_tools_execute_migration(MigrationInterface $migration, $migration_id, array $options = []) {
285   $log = new DrushLogMigrateMessage();
286
287   if (drush_get_option('execute-dependencies')) {
288     if ($required_IDS = $migration->get('requirements')) {
289       $manager = \Drupal::service('plugin.manager.migration');
290       $required_migrations = $manager->createInstances($required_IDS);
291       $dependency_options = array_merge($options, ['is_dependency' => TRUE]);
292       array_walk($required_migrations, __FUNCTION__, $dependency_options);
293     }
294   }
295   if (!empty($options['force'])) {
296     $migration->set('requirements', []);
297   }
298   if (!empty($options['update'])) {
299     $migration->getIdMap()->prepareUpdate();
300   }
301   $executable = new MigrateExecutable($migration, $log, $options);
302   // Function drush_op() provides --simulate support.
303   drush_op([$executable, 'import']);
304   if ($count = $executable->getFailedCount()) {
305     // Nudge Drush to use a non-zero exit code.
306     drush_set_error('MIGRATE_ERROR', dt('!name Migration - !count failed.', [
307       '!name' => $migration_id,
308       '!count' => $count,
309     ]));
310   }
311 }
312
313 /**
314  * Rollback migrations.
315  *
316  * @param string $migration_names
317  *   The migration names.
318  */
319 function drush_migrate_tools_migrate_rollback($migration_names = '') {
320   $group_names = drush_get_option('group');
321   $tag_names = drush_get_option('tag');
322   $all = drush_get_option('all');
323   $options = [];
324   if (!$all && !$group_names && !$migration_names && !$tag_names) {
325     drush_set_error('MIGRATE_ERROR', dt('You must specify --all, --group, --tag, or one or more migration names separated by commas'));
326     return;
327   }
328
329   if (drush_get_option('feedback')) {
330     $options['feedback'] = drush_get_option('feedback');
331   }
332
333   $log = new DrushLogMigrateMessage();
334
335   $migrations = drush_migrate_tools_migration_list($migration_names);
336   if (empty($migrations)) {
337     drush_log(dt('No migrations found.'), 'error');
338   }
339
340   // Take it one group at a time, rolling back the migrations within each group.
341   foreach ($migrations as $group_id => $migration_list) {
342     // Roll back in reverse order.
343     $migration_list = array_reverse($migration_list);
344     foreach ($migration_list as $migration_id => $migration) {
345       $executable = new MigrateExecutable($migration, $log, $options);
346       // drush_op() provides --simulate support.
347       drush_op([$executable, 'rollback']);
348     }
349   }
350 }
351
352 /**
353  * Stop a migration.
354  *
355  * @param string $migration_id
356  *   The migration id.
357  */
358 function drush_migrate_tools_migrate_stop($migration_id = '') {
359   /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
360   $migration = \Drupal::service('plugin.manager.migration')
361     ->createInstance($migration_id);
362   if ($migration) {
363     $status = $migration->getStatus();
364     switch ($status) {
365       case MigrationInterface::STATUS_IDLE:
366         drush_log(dt('Migration @id is idle', ['@id' => $migration_id]), 'warning');
367         break;
368       case MigrationInterface::STATUS_DISABLED:
369         drush_log(dt('Migration @id is disabled', ['@id' => $migration_id]), 'warning');
370         break;
371       case MigrationInterface::STATUS_STOPPING:
372         drush_log(dt('Migration @id is already stopping', ['@id' => $migration_id]), 'warning');
373         break;
374       default:
375         $migration->interruptMigration(MigrationInterface::RESULT_STOPPED);
376         drush_log(dt('Migration @id requested to stop', ['@id' => $migration_id]), 'success');
377         break;
378     }
379   }
380   else {
381     drush_log(dt('Migration @id does not exist', ['@id' => $migration_id]), 'error');
382   }
383 }
384
385 /**
386  * Reset status.
387  *
388  * @param string $migration_id
389  *   The migration id.
390  */
391 function drush_migrate_tools_migrate_reset_status($migration_id = '') {
392   /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
393   $migration = \Drupal::service('plugin.manager.migration')
394     ->createInstance($migration_id);
395   if ($migration) {
396     $status = $migration->getStatus();
397     if ($status == MigrationInterface::STATUS_IDLE) {
398       drush_log(dt('Migration @id is already Idle', ['@id' => $migration_id]), 'warning');
399     }
400     else {
401       $migration->setStatus(MigrationInterface::STATUS_IDLE);
402       drush_log(dt('Migration @id reset to Idle', ['@id' => $migration_id]), 'status');
403     }
404   }
405   else {
406     drush_log(dt('Migration @id does not exist', ['@id' => $migration_id]), 'error');
407   }
408 }
409
410 /**
411  * Print messages.
412  *
413  * @param string $migration_id
414  *   The migration id.
415  */
416 function drush_migrate_tools_migrate_messages($migration_id) {
417   /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
418   $migration = \Drupal::service('plugin.manager.migration')
419     ->createInstance($migration_id);
420   if ($migration) {
421     $map = $migration->getIdMap();
422     $first = TRUE;
423     $table = [];
424     foreach ($map->getMessageIterator() as $row) {
425       unset($row->msgid);
426       if ($first) {
427         // @todo: Ideally, replace sourceid* with source key names. Or, should
428         // getMessageIterator() do that?
429         foreach ($row as $column => $value) {
430           $table[0][] = $column;
431         }
432         $first = FALSE;
433       }
434       $table[] = (array) $row;
435     }
436     if (empty($table)) {
437       drush_log(dt('No messages for this migration'), 'status');
438     }
439     else {
440       if (drush_get_option('csv')) {
441         foreach ($table as $row) {
442           fputcsv(STDOUT, $row);
443         }
444       }
445       else {
446         $widths = [];
447         foreach ($table[0] as $header) {
448           $widths[] = strlen($header) + 1;
449         }
450         drush_print_table($table, TRUE, $widths);
451       }
452     }
453   }
454   else {
455     drush_log(dt('Migration @id does not exist', ['@id' => $migration_id]), 'error');
456   }
457 }
458
459 /**
460  * Print source fields.
461  *
462  * @param string $migration_id
463  *   The migration id.
464  */
465 function drush_migrate_tools_migrate_fields_source($migration_id) {
466   /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
467   $migration = \Drupal::service('plugin.manager.migration')
468     ->createInstance($migration_id);
469   if ($migration) {
470     $source = $migration->getSourcePlugin();
471     $table = [];
472     foreach ($source->fields() as $machine_name => $description) {
473       $table[] = [strip_tags($description), $machine_name];
474     }
475     drush_print_table($table);
476   }
477   else {
478     drush_log(dt('Migration @id does not exist', ['@id' => $migration_id]), 'error');
479   }
480 }
481
482 /**
483  * Retrieve a list of active migrations.
484  *
485  * @param string $migration_ids
486  *   Comma-separated list of migrations - if present, return only these
487  *   migrations.
488  *
489  * @return \Drupal\migrate\Plugin\MigrationInterface[][]
490  *   An array keyed by migration group, each value containing an array of
491  *   migrations or an empty array if no migrations match the input criteria.
492  */
493 function drush_migrate_tools_migration_list($migration_ids = '') {
494   // Filter keys must match the migration configuration property name.
495   $filter['migration_group'] = drush_get_option('group') ? explode(',', drush_get_option('group')) : [];
496   $filter['migration_tags'] = drush_get_option('tag') ? explode(',', drush_get_option('tag')) : [];
497
498   $manager = \Drupal::service('plugin.manager.migration');
499   $plugins = $manager->createInstances([]);
500   $matched_migrations = [];
501
502   // Get the set of migrations that may be filtered.
503   if (empty($migration_ids)) {
504     $matched_migrations = $plugins;
505   }
506   else {
507     // Get the requested migrations.
508     $migration_ids = explode(',', Unicode::strtolower($migration_ids));
509     foreach ($plugins as $id => $migration) {
510       if (in_array(Unicode::strtolower($id), $migration_ids)) {
511         $matched_migrations[$id] = $migration;
512       }
513     }
514   }
515
516   // Do not return any migrations which fail to meet requirements.
517   /** @var \Drupal\migrate\Plugin\Migration $migration */
518   foreach ($matched_migrations as $id => $migration) {
519     if ($migration->getSourcePlugin() instanceof RequirementsInterface) {
520       try {
521         $migration->getSourcePlugin()->checkRequirements();
522       }
523       catch (RequirementsException $e) {
524         unset($matched_migrations[$id]);
525       }
526     }
527   }
528
529   // Filters the matched migrations if a group or a tag has been input.
530   if (!empty($filter['migration_group']) || !empty($filter['migration_tags'])) {
531     // Get migrations in any of the specified groups and with any of the
532     // specified tags.
533     foreach ($filter as $property => $values) {
534       if (!empty($values)) {
535         $filtered_migrations = [];
536         foreach ($values as $search_value) {
537           foreach ($matched_migrations as $id => $migration) {
538             // Cast to array because migration_tags can be an array.
539             $configured_values = (array) $migration->get($property);
540             $configured_id = (in_array($search_value, $configured_values)) ? $search_value : 'default';
541             if (empty($search_value) || $search_value == $configured_id) {
542               if (empty($migration_ids) || in_array(Unicode::strtolower($id), $migration_ids)) {
543                 $filtered_migrations[$id] = $migration;
544               }
545             }
546           }
547         }
548         $matched_migrations = $filtered_migrations;
549       }
550     }
551   }
552
553   // Sort the matched migrations by group.
554   if (!empty($matched_migrations)) {
555     foreach ($matched_migrations as $id => $migration) {
556       $configured_group_id = empty($migration->get('migration_group')) ? 'default' : $migration->get('migration_group');
557       $migrations[$configured_group_id][$id] = $migration;
558     }
559   }
560   return isset($migrations) ? $migrations : [];
561 }