You are here

migrate.drush.inc in Migrate 6.2

Same filename and directory in other branches
  1. 6 migrate.drush.inc
  2. 7.2 migrate.drush.inc

Drush support for the migrate module

File

migrate.drush.inc
View source
<?php

/**
 * @file
 * Drush support for the migrate module
 */

/**
 * Implements hook_drush_command().
 */
function migrate_drush_command() {
  $migration_options = array(
    'limit' => 'Limit on the length of each migration process, expressed in seconds or number of items',
    'feedback' => 'Frequency of progress messages, in seconds or items processed',
    'idlist' => 'A comma delimited list of ids to import or rollback. If unspecified, migrate imports all pending items or rolls back all items for the content set.',
    'all' => 'Process all migrations that come after the specified migration. If no value is supplied, all migrations are processed.',
    'instrument' => 'Capture performance information (timer, memory, or all)',
    'force' => 'Force an operation to run, even if all dependencies are not satisfied',
    'group' => 'Name of the migration group to run',
  );
  $items['migrate-status'] = array(
    'description' => 'List all migrations with current status.',
    'options' => array(
      'refresh' => 'Recognize new migrations and update counts',
      'group' => 'Name of the migration group to list',
    ),
    'arguments' => array(
      'migration' => 'Restrict to a single migration. Optional',
    ),
    'examples' => array(
      'migrate-status' => 'Retrieve status for all migrations',
      'migrate-status BeerNode' => 'Retrieve status for just one migration',
    ),
    'drupal dependencies' => array(
      'migrate',
    ),
    'aliases' => array(
      'ms',
    ),
  );
  $items['migrate-fields-destination'] = array(
    'description' => 'List the fields available for mapping in a destination.',
    'options' => array(
      'all' => $migration_options['all'],
      'group' => $migration_options['group'],
    ),
    'arguments' => array(
      'migration' => 'Name of the migration or destination class to query for fields',
    ),
    'examples' => array(
      'migrate-fields-destination MyNode' => 'List fields for the destination in the MyNode migration',
    ),
    'drupal dependencies' => array(
      'migrate',
    ),
    'aliases' => array(
      'mfd',
    ),
  );
  $items['migrate-fields-source'] = array(
    'description' => 'List the fields available for mapping from a source.',
    'arguments' => array(
      'migration' => 'Name of the migration or destination class to query for fields',
    ),
    'options' => array(
      'all' => $migration_options['all'],
      'group' => $migration_options['group'],
    ),
    'examples' => array(
      'migrate-fields-destination MyNode' => 'List fields in the source query for the MyNode migration',
    ),
    'drupal dependencies' => array(
      'migrate',
    ),
    'aliases' => array(
      'mfs',
    ),
  );
  $items['migrate-mappings'] = array(
    'description' => 'View information on all field mappings in a migration.',
    'options' => array(
      'all' => $migration_options['all'],
      'group' => $migration_options['group'],
      'csv' => 'Export information as a CSV',
      'full' => 'Include more information on each mapping',
    ),
    'arguments' => array(
      'migration' => 'Name of the migration',
    ),
    'examples' => array(
      'migrate-mappings MyNode' => 'Show mappings for the MyNode migration',
      'migrate-mappings MyNode --csv --full' => 'Export full mapping information in CSV format',
    ),
    'drupal dependencies' => array(
      'migrate',
    ),
    'aliases' => array(
      'mm',
    ),
  );
  $items['migrate-messages'] = array(
    'description' => 'View any messages associated with a migration.',
    'options' => array(
      'csv' => 'Export messages as a CSV',
    ),
    'arguments' => array(
      'migration' => 'Name of the migration',
    ),
    'examples' => array(
      'migrate-messages MyNode' => 'Show all messages for the MyNode migration',
    ),
    'drupal dependencies' => array(
      'migrate',
    ),
    'aliases' => array(
      'mmsg',
    ),
  );
  $items['migrate-analyze'] = array(
    'description' => 'Analyze the source fields for a migration.',
    'options' => array(
      'all' => $migration_options['all'],
      'group' => $migration_options['group'],
    ),
    'arguments' => array(
      'migration' => 'Name of the migration',
    ),
    'examples' => array(
      'migrate-analyze MyNode' => 'Report on field values for the MyNode migration',
    ),
    'drupal dependencies' => array(
      'migrate',
    ),
    'aliases' => array(
      'maz',
    ),
  );
  $items['migrate-audit'] = array(
    'description' => 'View information on problems in a migration.',
    'options' => array(
      'all' => $migration_options['all'],
      'group' => $migration_options['group'],
    ),
    'arguments' => array(
      'migration' => 'Name of the migration',
    ),
    'examples' => array(
      'migrate-audit MyNode' => 'Report on problems in the MyNode migration',
    ),
    'drupal dependencies' => array(
      'migrate',
    ),
    'aliases' => array(
      'ma',
    ),
  );
  $items['migrate-rollback'] = array(
    'description' => 'Roll back the destination objects from a given migration',
    'options' => $migration_options,
    // We will bootstrap to login from within the command callback.
    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
    'arguments' => array(
      'migration' => 'Name of migration(s) to roll back. Delimit multiple using commas.',
    ),
    'examples' => array(
      'migrate-rollback Article' => 'Roll back the article migration',
      'migrate-rollback Article --idlist=4,9' => 'Roll back two articles. The ids refer to the value of the primary key in base table',
      'migrate-rollback User --limit="50 items"' => 'Roll back up to 50 items from the migration named User',
      'migrate-rollback User --feedback="60 seconds"' => 'Display a progress message every 60 seconds or less',
    ),
    'drupal dependencies' => array(
      'migrate',
    ),
    'aliases' => array(
      'mr',
    ),
  );
  $migration_options['update'] = 'In addition to processing unimported items from the source, update previously-imported items with new data';
  $migration_options['needs-update'] = 'Reimport up to 10K records where needs_update=1. This option is only needed when your Drupal DB is on a different DB server from your source data. Otherwise, these records get migrated with just migrate-import.';
  $migration_options['stop'] = 'Stop specified migration(s) if applicable.';
  $migration_options['rollback'] = 'Rollback specified migration(s) if applicable.';
  $migration_options['file_function'] = 'Override file function to use when migrating images.';
  $items['migrate-import'] = array(
    'description' => 'Perform one or more migration processes',
    'options' => $migration_options,
    'arguments' => array(
      'migration' => 'Name of migration(s) to import. Delimit multiple using commas.',
    ),
    'examples' => array(
      'migrate-import Article' => 'Import new articles',
      'migrate-import Article --update' => 'Import new items, and also update previously-imported items',
      'migrate-import Article --idlist=4,9' => 'Import two specific articles. The ids refer to the value of the primary key in base table',
      'migrate-import Article --limit="60 seconds" --stop --rollback' => 'Import for up to 60 seconds after stopping and rolling back the Article migration.',
      'migrate-import Article --limit="100 items"' => 'Import up to 100 items from the migration named Article.',
      'migrate-import User --feedback="1000 items"' => 'Display a progress message every 1000 processed items or less',
      'migrate-import --all=User' => 'Perform User migrations and all that follow it.',
    ),
    'drupal dependencies' => array(
      'migrate',
    ),
    'aliases' => array(
      'mi',
    ),
  );
  $items['migrate-stop'] = array(
    'description' => 'Stop an active migration operation',
    'options' => array(
      'all' => 'Stop all active migration operations',
    ),
    'arguments' => array(
      'migration' => 'Name of migration to stop',
    ),
    'examples' => array(
      'migrate-stop Article' => 'Stop any active operation on the Article migration',
      'migrate-stop --all' => 'Stop all active migration operations',
    ),
    'drupal dependencies' => array(
      'migrate',
    ),
    'aliases' => array(
      'mst',
    ),
  );
  $items['migrate-reset-status'] = array(
    'description' => 'Reset a active migration\'s status to idle',
    'options' => array(
      'all' => 'Reset all active migration operations',
    ),
    'arguments' => array(
      'migration' => 'Name of migration to reset',
    ),
    'examples' => array(
      'migrate-reset-status Article' => 'Reset any active operation on the Article migration',
      'migrate-reset-status --all' => 'Reset all active migration operations',
    ),
    'drupal dependencies' => array(
      'migrate',
    ),
    'aliases' => array(
      'mrs',
    ),
  );
  $items['migrate-deregister'] = array(
    'description' => 'Remove all tracking of a migration',
    'options' => array(
      'orphans' => 'Remove tracking for any migrations whose implementing class no longer exists',
    ),
    'arguments' => array(
      'migration' => 'Name of migration to deregister',
    ),
    'examples' => array(
      'migrate-deregister Article' => 'Deregister the Article migration',
      'migrate-deregister --orphans' => 'Deregister any no-longer-implemented migrations',
    ),
    'drupal dependencies' => array(
      'migrate',
    ),
  );
  $items['migrate-auto-register'] = array(
    'description' => 'Register any newly-defined migration classes',
    'drupal dependencies' => array(
      'migrate',
    ),
    'aliases' => array(
      'mar',
    ),
  );
  $items['migrate-wipe'] = array(
    'description' => 'Delete all nodes from specified content types.',
    'examples' => array(
      "migrate-wipe story article" => 'Delete all story and article nodes.',
    ),
    'arguments' => array(
      'type' => 'A space delimited list of content type machine readable Ids.',
    ),
    'drupal dependencies' => array(
      'migrate',
    ),
    'aliases' => array(
      'mw',
    ),
  );
  return $items;
}

/**
 * Get the value of all migrate related options. Used when spawning a subshell.
 * Don't pass along all, stop, update, and rollback options.
 *
 * @return
 *   An array of command specific options and their values.
 */
function drush_migrate_get_options() {
  $options = array();
  $blacklist = array(
    'stop',
    'rollback',
    'update',
    'all',
  );
  $command = drush_parse_command();
  foreach ($command['options'] as $key => $value) {

    // Strip leading --
    $key = ltrim($key, '-');
    if (!in_array($key, $blacklist)) {
      $value = drush_get_option($key);
      if (isset($value)) {
        $options[$key] = $value;
      }
    }
  }
  return $options;
}

/*
 * Spawn a subshell which runs the same command we are currently running.
 */
function drush_migrate_invoke_process($migrations = '') {
  $args = drush_get_arguments();
  $options = drush_migrate_get_options();
  if (intval(DRUSH_MAJOR_VERSION) < 4) {

    // @todo: use drush_backend_invoke_args() as per http://drupal.org/node/658420.
    return drush_backend_invoke(implode(' ', $args), $options);
  }
  else {

    // $args[0] is the command name, $args[1] is the list of migrations.
    if (empty($migrations)) {
      $command_args = array(
        $args[1],
      );
    }
    else {
      $command_args = array(
        $migrations,
      );
    }
    $return = drush_invoke_process('@self', $args[0], $command_args, $options);
    return $return;
  }
}

/**
 * A simplified version of the dashboard page.
 */
function drush_migrate_status($name = NULL) {
  try {
    $refresh = drush_get_option('refresh');
    $group_option = drupal_strtolower(drush_get_option('group'));

    // Validate input and load Migration(s).
    if ($name) {
      if ($migration = MigrationBase::getInstance($name)) {
        $migrations = array(
          $migration,
        );
      }
      else {
        return drush_set_error(dt('Unrecognized migration: !cn', array(
          '!cn' => $name,
        )));
      }
    }
    else {
      $migrations = migrate_migrations();
    }
    $groups = MigrateGroup::groups();
    $table = array();
    foreach ($groups as $group) {
      if ($group_option && drupal_strtolower($group
        ->getName()) != $group_option) {
        continue;
      }
      $group_members_count = 0;
      foreach ($migrations as $migration) {
        if ($migration
          ->getGroup() != $group) {

          // This migration is not from this group.
          continue;
        }
        ++$group_members_count;
        if ($group_members_count == 1) {

          // An empty line and the headers.
          $table[] = array(
            '',
          );
          $table[] = array(
            dt('Group: !name', array(
              '!name' => $group
                ->getName(),
            )),
            dt('Total'),
            dt('Imported'),
            dt('Unimported'),
            dt('Status'),
            dt('Last imported'),
          );
        }
        $has_counts = TRUE;
        if (method_exists($migration, 'sourceCount')) {
          $total = $migration
            ->sourceCount($refresh);
          if ($total < 0) {
            $has_counts = FALSE;
            $total = dt('N/A');
          }
        }
        else {
          $has_counts = FALSE;
          $total = dt('N/A');
        }
        if (method_exists($migration, 'importedCount')) {
          $imported = $migration
            ->importedCount();
          $processed = $migration
            ->processedCount();
        }
        else {
          $has_counts = FALSE;
          $imported = dt('N/A');
        }
        if ($has_counts) {
          $unimported = $total - $processed;
        }
        else {
          $unimported = dt('N/A');
        }
        $status = $migration
          ->getStatus();
        switch ($status) {
          case MigrationBase::STATUS_IDLE:
            $status = dt('Idle');
            break;
          case MigrationBase::STATUS_IMPORTING:
            $status = dt('Importing');
            break;
          case MigrationBase::STATUS_ROLLING_BACK:
            $status = dt('Rolling back');
            break;
          case MigrationBase::STATUS_STOPPING:
            $status = dt('Stopping');
            break;
          case MigrationBase::STATUS_DISABLED:
            $status = dt('Disabled');
            break;
          default:
            $status = dt('Unknown');
            break;
        }
        $table[] = array(
          $migration
            ->getMachineName(),
          $total,
          $imported,
          $unimported,
          $status,
          $migration
            ->getLastImported(),
        );
      }
    }
    drush_print_table($table);
  } catch (MigrateException $e) {
    drush_print($e
      ->getMessage());
    exit;
  }
}

// TODO: Use drush_choice for detailed field info
function drush_migrate_fields_destination($args = NULL) {
  try {
    $migrations = drush_migrate_get_migrations($args);
    foreach ($migrations as $name => $migration) {
      drush_print("\n" . dt('@migration Destination Fields', array(
        '@migration' => $name,
      )) . "\n");
      $destination = $migration
        ->getDestination();
      if (method_exists($destination, 'fields')) {
        $table = array();
        foreach ($destination
          ->fields($migration) as $machine_name => $description) {
          $table[] = array(
            $description,
            $machine_name,
          );
        }
        drush_print_table($table);
      }
      else {
        drush_print(dt('No fields were found.'));
      }
    }
  } catch (MigrateException $e) {
    drush_print($e
      ->getMessage());
    exit;
  }
}
function drush_migrate_fields_source($args = NULL) {
  try {
    $migrations = drush_migrate_get_migrations($args);
    foreach ($migrations as $name => $migration) {
      drush_print("\n" . dt('@migration Source Fields', array(
        '@migration' => $name,
      )) . "\n");
      $source = $migration
        ->getSource();
      if (method_exists($source, 'fields')) {
        $table = array();
        foreach ($source
          ->fields() as $machine_name => $description) {
          $table[] = array(
            $description,
            $machine_name,
          );
        }
        drush_print_table($table);
      }
      else {
        drush_print(dt('No fields were found.'));
      }
    }
  } catch (MigrateException $e) {
    drush_print($e
      ->getMessage());
    exit;
  }
}

/**
 * Display field mappings for a migration.
 */
function drush_migrate_mappings($args = NULL) {
  try {
    $full = drush_get_option('full');
    $migrations = drush_migrate_get_migrations($args);
    foreach ($migrations as $name => $migration) {
      drush_print("\n" . dt('@migration Mappings', array(
        '@migration' => $name,
      )) . "\n");

      // In verbose mode, we'll also get source and destination field descriptions
      if ($full) {
        $destination = $migration
          ->getDestination();
        $dest_descriptions = array();
        if (method_exists($destination, 'fields')) {
          foreach ($destination
            ->fields($migration) as $machine_name => $description) {
            $dest_descriptions[$machine_name] = $description;
          }
        }
        $source = $migration
          ->getSource();
        $src_descriptions = array();
        if (method_exists($source, 'fields')) {
          foreach ($source
            ->fields() as $machine_name => $description) {
            $src_descriptions[$machine_name] = $description;
          }
        }
      }
      if (method_exists($migration, 'getFieldMappings')) {

        // First group the mappings. We want "interesting" mappings first, so
        // put the boring Done and DNM mappings last.
        $descriptions = array();
        $done = array();
        $dnm = array();
        foreach ($migration
          ->getFieldMappings() as $mapping) {
          $group = $mapping
            ->getIssueGroup();
          $lowergroup = drupal_strtolower($group);
          if ($lowergroup == dt('done')) {
            $done[$group][] = $mapping;
          }
          elseif ($lowergroup == dt('dnm') || $lowergroup == dt('do not migrate')) {
            $dnm[$group][] = $mapping;
          }
          else {
            $descriptions[$group][] = $mapping;
          }
        }
        $descriptions = array_merge($descriptions, $done, $dnm);

        // Put out each group header
        $table = array();
        if ($full) {
          $table[] = array(
            dt('Destination'),
            dt(''),
            dt('Source'),
            dt(''),
            dt('Default'),
            dt('Description'),
          );
        }
        else {
          $table[] = array(
            dt('Destination'),
            dt('Source'),
            dt('Default'),
            dt('Description'),
          );
        }
        $first = TRUE;
        foreach ($descriptions as $group => $mappings) {
          if ($first) {
            $first = FALSE;
          }
          else {
            $table[] = array(
              ' ',
            );
          }

          // Attempt to highlight the group header a bit so it stands out
          $group_header = '--- ' . drupal_strtoupper($group) . ' ---';
          $table[] = array(
            $group_header,
          );
          foreach ($mappings as $mapping) {
            if (is_array($mapping
              ->getDefaultValue())) {
              $default = implode(',', $mapping
                ->getDefaultValue());
            }
            else {
              $default = $mapping
                ->getDefaultValue();
            }
            $destination = $mapping
              ->getDestinationField();
            $source = $mapping
              ->getSourceField();
            if ($full) {
              if ($destination && $dest_descriptions[$destination]) {
                $dest_description = $dest_descriptions[$destination];
              }
              else {
                $dest_description = '';
              }
              if ($source && $src_descriptions[$source]) {
                $src_description = $src_descriptions[$source];
              }
              else {
                $src_description = '';
              }
              $table[] = array(
                $destination,
                $dest_description,
                $source,
                $src_description,
                $default,
                $mapping
                  ->getDescription(),
              );
            }
            else {
              $table[] = array(
                $destination,
                $source,
                $default,
                $mapping
                  ->getDescription(),
              );
            }
          }
        }
        if (drush_get_option('csv')) {
          foreach ($table as $row) {
            fputcsv(STDOUT, $row);
          }
        }
        else {
          drush_print_table($table, TRUE);
        }
      }
    }
  } catch (MigrateException $e) {
    drush_print($e
      ->getMessage());
    exit;
  }
}

/**
 * Display messages for a migration.
 */
function drush_migrate_messages($migration_name) {
  try {
    $migration = MigrationBase::getInstance($migration_name);
    if (is_a($migration, 'Migration')) {
      $map = $migration
        ->getMap();
      $message_table = $map
        ->getMessageTable();
      $result = db_select($message_table, 'msg', array(
        'fetch' => PDO::FETCH_ASSOC,
      ))
        ->fields('msg')
        ->execute();
      $first = TRUE;
      $table = array();
      foreach ($result as $row) {
        unset($row['msgid']);
        unset($row['level']);
        if ($first) {
          $table[] = array_keys($row);
          $first = FALSE;
        }
        $table[] = $row;
      }
    }
    if (empty($table)) {
      drush_log(dt('No messages for this migration'), 'status');
    }
    else {
      if (drush_get_option('csv')) {
        foreach ($table as $row) {
          fputcsv(STDOUT, $row);
        }
      }
      else {
        $widths = array();
        foreach ($table[0] as $header) {
          $widths[] = strlen($header) + 1;
        }
        drush_print_table($table, TRUE, $widths);
      }
    }
  } catch (MigrateException $e) {
    drush_print($e
      ->getMessage());
    exit;
  }
}

/**
 * Analyze the source fields for any passed migrations.
 */
function drush_migrate_analyze($args = NULL) {
  $migrations = drush_migrate_get_migrations($args);
  foreach ($migrations as $name => $migration) {

    // "Migrations" derived from MigrationBase won't have an analyze method.
    if (method_exists($migration, 'analyze')) {
      drush_print("\n" . dt('Analyzing @migration', array(
        '@migration' => $name,
      )) . "\n");
      $analysis = $migration
        ->analyze();
      if (!empty($analysis)) {
        foreach ($analysis as $field_name => $details) {
          if (!empty($details['description'])) {
            drush_print(dt('@name (@description):', array(
              '@name' => $field_name,
              '@description' => $details['description'],
            )));
          }
          else {
            drush_print(dt('@name:', array(
              '@name' => $field_name,
            )));
          }

          // Special handling in degenerate cases
          if (count($details['distinct_values']) == 1) {
            $value = trim(reset(array_keys($details['distinct_values'])));
            if ($value === '') {
              drush_print('  ' . dt('The field is always empty'));
            }
            else {
              drush_print('  ' . dt('Only one value present: @value', array(
                '@value' => $value,
              )));
            }
          }
          else {
            if ($details['is_numeric']) {
              drush_print('  ' . dt('Numeric field with a range of @min to @max', array(
                '@min' => $details['min_numeric'],
                '@max' => $details['max_numeric'],
              )));
            }
            else {
              drush_print('  ' . dt('String field with a length ranging from @min to @max', array(
                '@min' => $details['min_strlen'],
                '@max' => $details['max_strlen'],
              )));
            }
            $values = array();
            $header = NULL;

            // If the max of 10 tracked distinct values was reached, we assume
            // there are many values and treat them as samples. Under 10 this
            // may be an enumerated field, show all values with their counts.
            if (count($details['distinct_values']) < 10) {
              drush_print('  ' . dt('Distinct values:'));
              $header = array(
                '',
                dt('Value'),
                dt('Count'),
              );
              $values[] = $header;
            }
            else {
              drush_print('  ' . dt('Sample values:'));
            }
            ksort($details['distinct_values']);
            foreach ($details['distinct_values'] as $value => $count) {

              // Truncate long strings
              $value = substr($value, 0, 60);
              if (strlen($value) == 60) {
                $value .= dt('...');
              }
              $row = array(
                '    ',
                $value,
              );
              if (count($details['distinct_values']) < 10) {
                $row[] = $count;
              }
              $values[] = $row;
            }

            // No header for sample values.
            drush_print_table($values, !is_null($header));
          }
        }
      }
    }
  }
}

/**
 * Display field mappings for a migration.
 */
function drush_migrate_audit($args = NULL) {
  try {
    $problem_descriptions = array(
      'noted_issues' => dt("Noted as an issue:"),
      'sources_unmapped' => dt("Source not used in a mapping:"),
      'sources_missing' => dt("Used as source field in mapping but not in source field list:"),
      'destinations_unmapped' => dt("Destination not used in a mapping:"),
      'destinations_missing' => dt("Used as destination field in mapping but not in destination field list:"),
    );
    $migrations = drush_migrate_get_migrations($args);
    foreach ($migrations as $name => $migration) {
      drush_print("\n" . dt('@migration Audit', array(
        '@migration' => $name,
      )) . "\n");
      $source = $migration
        ->getSource();
      $destination = $migration
        ->getDestination();
      if (empty($source)) {
        drush_print(dt('Missing a source.'));
      }
      if (empty($destination)) {
        drush_print(dt('Missing a destination.'));
      }
      $field_mappings = $migration
        ->getFieldMappings();
      $source_fields = $source
        ->fields();
      $destination_fields = $destination
        ->fields();
      $unused_sources = array();
      $used_destinations = array();
      $problems = array(
        'noted_issues' => array(),
        'sources_unmapped' => array(),
        'sources_missing' => array(),
        'destinations_unmapped' => array(),
        'destinations_missing' => array(),
      );
      foreach ($field_mappings as $mapping) {
        $source_field = $mapping
          ->getSourceField();
        $destination_field = $mapping
          ->getDestinationField();
        $used_sources[$source_field] = TRUE;
        $used_destinations[$destination_field] = TRUE;
        $issue_priority = $mapping
          ->getIssuePriority();
        if (!is_null($issue_priority) && $issue_priority != MigrateFieldMapping::ISSUE_PRIORITY_OK) {
          $problems['noted_issues'][] = array(
            dt('Source') => $source_field,
            dt('Destination') => $destination_field,
            dt('Priority') => MigrateFieldMapping::$priorities[$issue_priority],
            dt('Description') => $mapping
              ->getDescription(),
          );
        }

        // Validate source and destination fields actually exist
        if (!is_null($source_field) && !isset($source_fields[$source_field])) {
          $problems['sources_missing'][] = $source_field;
        }
        if (!is_null($destination_field) && !isset($destination_fields[$destination_field])) {
          $problems['destinations_missing'][] = $destination_field;
        }
      }
      $problems['sources_unmapped'] = array_diff_key($source_fields, $used_sources);
      $problems['destinations_unmapped'] = array_diff_key($destination_fields, $used_destinations);
      $problems = array_filter($problems);
      if (empty($problems)) {
        drush_print(dt('No problems found.') . "\n", 1);
      }
      else {
        foreach ($problems as $type => $some_problems) {
          drush_print($problem_descriptions[$type]);

          // Print arrays as tables.
          if (is_array($some_problems[0])) {
            $table = array_merge(array(
              array_keys($some_problems[0]),
            ), $some_problems);
            drush_print_table($table, TRUE);
          }
          else {
            foreach ($some_problems as $problem) {
              drush_print($problem, 1);
            }
            drush_print('');
          }
        }
      }
    }
  } catch (MigrateException $e) {
    drush_print($e
      ->getMessage());
    exit;
  }
}

/**
 * Roll back one specified migration
 */
function drush_migrate_rollback($args = NULL) {
  try {
    $migrations = drush_migrate_get_migrations($args);

    // Rollback in reverse order
    $migrations = array_reverse($migrations, TRUE);
    $options = array();
    if ($idlist = drush_get_option('idlist', FALSE)) {
      $options['idlist'] = $idlist;
    }
    if (drush_get_option('force', FALSE) == 1) {
      $options['force'] = TRUE;
    }
    $limit = drush_get_option('limit');
    if ($limit) {
      $parts = explode(' ', $limit);
      $options['limit']['value'] = $parts[0];
      $options['limit']['unit'] = $parts[1];
      if (!$options['limit']['unit']) {
        $options['limit']['unit'] = 'items';
      }
      elseif ($options['limit']['unit'] != 'seconds' && $options['limit']['unit'] != 'second' && $options['limit']['unit'] != 'items' && $options['limit']['unit'] != 'item') {
        drush_set_error(NULL, dt("Invalid limit unit '!unit'", array(
          '!unit' => $options['limit']['unit'],
        )));
        return;
      }
    }
    $feedback = drush_get_option('feedback');
    if ($feedback) {
      $parts = explode(' ', $feedback);
      $options['feedback']['value'] = $parts[0];
      $options['feedback']['unit'] = $parts[1];
      if ($options['feedback']['unit'] != 'seconds' && $options['feedback']['unit'] != 'second' && $options['feedback']['unit'] != 'items' && $options['feedback']['unit'] != 'item') {
        drush_set_error(NULL, dt("Invalid feedback frequency unit '!unit'", array(
          '!unit' => $options['feedback']['unit'],
        )));
        return;
      }
    }
    $instrument = drush_get_option('instrument');
    global $_migrate_track_memory, $_migrate_track_timer;
    switch ($instrument) {
      case 'timer':
        $_migrate_track_timer = TRUE;
        break;
      case 'memory':
        $_migrate_track_memory = TRUE;
        break;
      case 'all':
        $_migrate_track_timer = TRUE;
        $_migrate_track_memory = TRUE;
        break;
    }
    foreach ($migrations as $migration) {
      drush_log(dt("Rolling back '!description' migration", array(
        '!description' => $migration
          ->getMachineName(),
      )));
      $return = $migration
        ->processRollback($options);

      // If it couldn't finish (presumably because it was appraoching memory_limit),
      // continue in a subprocess
      if ($return == MigrationBase::RESULT_INCOMPLETE) {
        drush_migrate_invoke_process();
      }
      elseif ($return == MigrationBase::RESULT_STOPPED) {
        break;
      }
      elseif ($return == MigrationBase::RESULT_SKIPPED) {
        drush_log(dt("Skipping migration !name due to unfulfilled dependencies, use the --force option to run it anyway.", array(
          '!name' => $migration
            ->getMachineName(),
        )), 'warning');
      }
    }
  } catch (MigrateException $e) {
    drush_print($e
      ->getMessage());
    exit;
  }
  if ($_migrate_track_memory) {
    drush_migrate_print_memory();
  }
  if ($_migrate_track_timer && !drush_get_context('DRUSH_DEBUG')) {
    drush_print_timers();
  }
}
function drush_migrate_get_migrations($args) {
  $migration_objects = migrate_migrations();
  if ($start = drush_get_option('all')) {

    // Handle custom first migration when --all=foo is supplied.
    $seen = $start === TRUE ? TRUE : FALSE;
    foreach ($migration_objects as $name => $migration) {
      if (!$seen && drupal_strtolower($start) . 'migration' == drupal_strtolower($name)) {

        // We found our starting migration. $seen is always TRUE now.
        $seen = TRUE;
      }
      if (!$migration
        ->getEnabled() || !$seen) {

        // This migration is disabled or is before our starting migration.
        unset($migration_objects[$name]);
      }
    }
  }
  elseif ($group = drush_get_option('group')) {
    foreach ($migration_objects as $name => $migration) {
      if (drupal_strtolower($group) != drupal_strtolower($migration
        ->getGroup()
        ->getName()) || !$migration
        ->getEnabled()) {
        unset($migration_objects[$name]);
      }
    }
  }
  else {
    $named_migrations = array();
    foreach (explode(',', $args) as $name) {
      $found = FALSE;
      foreach ($migration_objects as $machine_name => $migration) {
        if (drupal_strtolower($name) == drupal_strtolower($machine_name)) {
          if ($migration
            ->getEnabled()) {
            $named_migrations[$name] = $migration;
            $found = TRUE;
            break;
          }
          else {
            drush_log(dt('Migration !name is disabled', array(
              '!name' => $name,
            )), 'warning');
          }
        }
      }
      if (!$found) {
        drush_log(dt('No migration with machine name !name found', array(
          '!name' => $name,
        )), 'error');
      }
    }
    $migration_objects = $named_migrations;
  }
  return $migration_objects;
}

// Implement drush_hook_COMMAND_validate().
function drush_migrate_fields_destination_validate($args = NULL) {
  return drush_migrate_validate_common($args);
}

// Implement drush_hook_COMMAND_validate().
function drush_migrate_fields_source_validate($args = NULL) {
  return drush_migrate_validate_common($args);
}

// Implement drush_hook_COMMAND_validate().
function drush_migrate_mappings_validate($args = NULL) {
  return drush_migrate_validate_common($args);
}

// Implement drush_hook_COMMAND_validate().
function drush_migrate_analyze_validate($args = NULL) {
  return drush_migrate_validate_common($args);
}

// Implement drush_hook_COMMAND_validate().
function drush_migrate_audit_validate($args = NULL) {
  return drush_migrate_validate_common($args);
}

// Implement drush_hook_COMMAND_validate().
function drush_migrate_import_validate($args = NULL) {
  return drush_migrate_validate_common($args);
}

// Implement drush_hook_COMMAND_validate().
function drush_migrate_stop_validate($args = NULL) {
  return drush_migrate_validate_common($args);
}

// Implement drush_hook_COMMAND_validate().
function drush_migrate_reset_status_validate($args = NULL) {
  return drush_migrate_validate_common($args);
}

// Implement drush_hook_COMMAND_validate().
function drush_migrate_rollback_validate($args = NULL) {
  return drush_migrate_validate_common($args);
}
function drush_migrate_validate_common($args) {

  // Let's load user 1, seems to be needed for creating path aliases and deleting content.
  global $user;
  $user_one = user_load(array(
    'uid' => '1',
  ));
  $user = $user_one;
  session_save_session(FALSE);
  if (drush_get_option('all')) {
    if (!empty($args) || drush_get_option('group')) {
      return drush_set_error(NULL, dt('You must specify exactly one of a migration name, --all, or --group'));
    }
  }
  elseif (drush_get_option('group')) {
    if (!empty($args) || drush_get_option('all')) {
      return drush_set_error(NULL, dt('You must specify exactly one of a migration name, --all, or --group'));
    }
  }
  else {
    if (empty($args)) {
      return drush_set_error(NULL, dt('You must specify exactly one of a migration name, --all, or --group'));
    }
    $machine_names = explode(',', $args);
    foreach ($machine_names as $machine_name) {
      $machine_name = trim($machine_name);
      $class_name = db_select('migrate_status', 'ms')
        ->fields('ms', array(
        'class_name',
      ))
        ->condition('machine_name', $machine_name)
        ->execute()
        ->fetchField();
      if (!$class_name || !class_exists($class_name)) {
        drush_set_error(dt('Unrecognized migration: !name', array(
          '!name' => $machine_name,
        )));
      }
    }
  }
  $feedback = drush_get_option('feedback');
  if ($feedback) {
    $parts = explode(' ', $feedback);
    $options['feedback']['value'] = $parts[0];
    $options['feedback']['unit'] = $parts[1];
    if ($options['feedback']['unit'] != 'seconds' && $options['feedback']['unit'] != 'second' && $options['feedback']['unit'] != 'items' && $options['feedback']['unit'] != 'item') {
      drush_set_error(NULL, dt("Invalid feedback frequency unit '!unit'", array(
        '!unit' => $options['feedback']['unit'],
      )));
      return;
    }
  }
}

/*
 * A 'pre' callback for migrate-import command.
 * Call migrate-stop and migrate-rollback commands if requested.
 */
function drush_migrate_pre_migrate_import($args = NULL) {
  if (drush_get_option('stop')) {
    drush_invoke('migrate-stop', $args);
  }
  if (drush_get_option('rollback')) {
    drush_invoke('migrate-rollback', $args);
  }
}

/**
 * Perform import on one or more migrations.
 *
 * @param $machine_names
 *  A comma delimited list of machine names, or the special name 'all'
 */
function drush_migrate_import($args = NULL) {
  try {
    $migrations = drush_migrate_get_migrations($args);
    $options = array();
    if ($idlist = drush_get_option('idlist', FALSE)) {
      $options['idlist'] = $idlist;
    }
    if ($file_function = drush_get_option('file_function', '')) {
      $options['file_function'] = $file_function;
    }
    if (drush_get_option('force', FALSE) == 1) {
      $options['force'] = TRUE;
    }
    $limit = drush_get_option('limit');
    if ($limit) {
      $parts = explode(' ', $limit);
      $options['limit']['value'] = $parts[0];
      $options['limit']['unit'] = $parts[1];
      if (!$options['limit']['unit']) {
        $options['limit']['unit'] = 'items';
      }
      elseif ($options['limit']['unit'] != 'seconds' && $options['limit']['unit'] != 'second' && $options['limit']['unit'] != 'items' && $options['limit']['unit'] != 'item') {
        drush_set_error(NULL, dt("Invalid limit unit '!unit'", array(
          '!unit' => $options['limit']['unit'],
        )));
        return;
      }
    }
    $feedback = drush_get_option('feedback');
    if ($feedback) {
      $parts = explode(' ', $feedback);
      $options['feedback']['value'] = $parts[0];
      $options['feedback']['unit'] = $parts[1];
      if ($options['feedback']['unit'] != 'seconds' && $options['feedback']['unit'] != 'second' && $options['feedback']['unit'] != 'items' && $options['feedback']['unit'] != 'item') {
        drush_set_error(NULL, dt("Invalid feedback frequency unit '!unit'", array(
          '!unit' => $options['feedback']['unit'],
        )));
        return;
      }
    }
    $instrument = drush_get_option('instrument');
    global $_migrate_track_memory, $_migrate_track_timer;
    switch ($instrument) {
      case 'timer':
        $_migrate_track_timer = TRUE;
        break;
      case 'memory':
        $_migrate_track_memory = TRUE;
        break;
      case 'all':
        $_migrate_track_timer = TRUE;
        $_migrate_track_memory = TRUE;
        break;
    }
    $stop = FALSE;
    foreach ($migrations as $machine_name => $migration) {
      drush_log(dt("Importing '!description' migration", array(
        '!description' => $machine_name,
      )));
      if (drush_get_option('update')) {
        $migration
          ->prepareUpdate();
      }
      if (drush_get_option('needs-update')) {
        $map_rows = $migration
          ->getMap()
          ->getRowsNeedingUpdate(10000);
        $idlist = array();
        foreach ($map_rows as $row) {
          $idlist[] = $row->sourceid1;
        }
        $options['idlist'] = implode(',', $idlist);
      }

      // The goal here is to do one migration in the parent process and then
      // spawn subshells as needed when memory is depleted. We show feedback
      // after each subshell depletes itself. Best we can do in PHP.
      if (!drush_get_context('DRUSH_BACKEND')) {

        // Our first pass and in the parent process. Run a migration right here.
        $status = $migration
          ->processImport($options);
        if ($status == MigrationBase::RESULT_SKIPPED) {
          drush_log(dt("Skipping migration !name due to unfulfilled dependencies:\n  !depends\nUse the --force option to run it anyway.", array(
            '!name' => $machine_name,
            '!depends' => implode("\n  ", $migration
              ->incompleteDependencies()),
          )), 'warning');
        }
        elseif ($status == MigrationBase::RESULT_STOPPED) {
          break;
        }
        elseif ($status == MigrationBase::RESULT_INCOMPLETE) {
          $stop = TRUE;
        }

        // Subsequent run in the parent process. Spawn subshells ad infinitum.
        $migration_string = implode(',', array_keys($migrations));
        while ($status == MigrationBase::RESULT_INCOMPLETE) {
          $return = drush_migrate_invoke_process($migration_string);

          // 'object' holds the return code we care about.
          $status = $return['object']['status'];
          $migration_string = $return['object']['migrations'];
          if ($status == MigrationBase::RESULT_SKIPPED) {
            drush_log(dt("Skipping migration !name due to unfulfilled dependencies:\n  !depends\nUse the --force option to run it anyway.", array(
              '!name' => $machine_name,
              '!depends' => implode("\n  ", $migration
                ->incompleteDependencies()),
            )), 'warning');
          }
          elseif ($status == MigrationBase::RESULT_STOPPED) {
            $stop = TRUE;
            break;
          }
        }
      }
      else {

        // I'm in a subshell. Import then set return value so parent process can respawn or move on.
        $status = $migration
          ->processImport($options);
        if ($status == MigrationBase::RESULT_SKIPPED) {
          drush_log(dt("Skipping migration !name due to unfulfilled dependencies:\n  !depends\n", array(
            '!name' => $machine_name,
            '!depends' => implode("\n  ", $migration
              ->incompleteDependencies()),
          )), 'warning');
        }
        elseif ($status == MigrationBase::RESULT_INCOMPLETE) {
          $stop = TRUE;
        }
        drush_backend_set_result(array(
          'status' => $status,
          'migrations' => implode(',', array_keys($migrations)),
        ));
      }
      if ($stop) {
        break;
      }
      unset($migrations[$machine_name]);
    }
  } catch (MigrateException $e) {
    drush_print($e
      ->getMessage());
    exit;
  }
  if ($_migrate_track_memory) {
    drush_migrate_print_memory();
  }
  if ($_migrate_track_timer && !drush_get_context('DRUSH_DEBUG')) {
    drush_print_timers();
  }
}

//**

// * Stop clearing or importing a given content set.
// *
// * @param $content_set
// *  The name of the Migration
// */
function drush_migrate_stop($args = NULL) {
  try {
    $migrations = drush_migrate_get_migrations($args);
    foreach ($migrations as $migration) {
      drush_log(dt("Stopping '!description' migration", array(
        '!description' => $migration
          ->getMachineName(),
      )));
      $migration
        ->stopProcess();
    }
  } catch (MigrateException $e) {
    drush_print($e
      ->getMessage());
    exit;
  }
}

/**
 * Reset the status of a given migration.
 */
function drush_migrate_reset_status($args = NULL) {
  try {
    $migrations = drush_migrate_get_migrations($args);
    foreach ($migrations as $migration) {
      drush_log(dt("Resetting '!description' migration", array(
        '!description' => $migration
          ->getMachineName(),
      )));
      $migration
        ->resetStatus();
    }
  } catch (MigrateException $e) {
    drush_print($e
      ->getMessage());
    exit;
  }
}

/**
 * Deregister a given migration, or all orphaned migrations. Note that the
 * migration might no longer "exist" (the class implementation might be gone),
 * so we can't count on being able to instantiate it, or use migrate_migrations().
 */
function drush_migrate_deregister($args = NULL) {
  try {
    $orphans = drush_get_option('orphans');
    if ($orphans) {
      $migrations = array();
      $result = db_select('migrate_status', 'ms')
        ->fields('ms', array(
        'class_name',
        'machine_name',
      ))
        ->execute();
      foreach ($result as $row) {
        if (!class_exists($row->class_name)) {
          $migrations[] = $row->machine_name;
        }
      }
    }
    else {
      $migrations = explode(',', $args);
    }
    foreach ($migrations as $machine_name) {
      drush_migrate_deregister_migration($machine_name);
      drush_log(dt("Deregistered '!description' migration", array(
        '!description' => $machine_name,
      )), 'success');
    }
  } catch (MigrateException $e) {
    drush_print($e
      ->getMessage());
    exit;
  }
}

/**
 * Given a migration machine name, remove its tracking from the database.
 *
 * @param $machine_name
 */
function drush_migrate_deregister_migration($machine_name) {

  // The class is gone, so we'll manually clear migrate_status, and make
  // the default assumptions about the map/message tables.
  dbtng_drop_table('migrate_map_' . $machine_name);
  dbtng_drop_table('migrate_message_' . $machine_name);
  db_delete('migrate_status')
    ->condition('machine_name', $machine_name)
    ->execute();
}

/**
 * Register any previously-unrecognized non-dynamic migrations.
 */
function drush_migrate_auto_register($args = NULL) {
  migrate_autoregister();
}

/**
 * A drush command callback.
 */
function drush_migrate_wipe() {

  // Let's load user 1, seems to be need for creating path aliases and deleting content.
  global $user;
  $user_one = user_load(array(
    'uid' => '1',
  ));
  $user = $user_one;
  session_save_session(FALSE);
  $types = func_get_args();
  $nids = db_select('node', 'n')
    ->fields('n', array(
    'nid',
  ))
    ->condition('type', $types, 'IN')
    ->execute()
    ->fetchCol();
  migrate_instrument_start('node_delete');
  foreach ($nids as $nid) {
    node_delete($nid);
  }
  migrate_instrument_stop('node_delete');
}

// Print all timers for the request.
function drush_migrate_print_memory() {
  global $_migrate_memory;
  $temparray = array();
  foreach ((array) $_migrate_memory as $name => $memoryrec) {

    // We have to use timer_read() for active timers, and check the record for others
    if (isset($memoryrec['start'])) {
      $temparray[$name] = migrate_memory_read($name);
    }
    else {
      $temparray[$name] = $memoryrec['bytes'];
    }
  }

  // Go no farther if there were no timers
  if (count($temparray) > 0) {

    // Put the highest cumulative times first
    arsort($temparray);
    $table = array();
    $table[] = array(
      'Name',
      'Cum (bytes)',
      'Count',
      'Avg (bytes)',
    );
    foreach ($temparray as $name => $memory) {
      $count = $_migrate_memory[$name]['count'];
      if ($count > 0) {
        $avg = round($memory / $count, 0);
      }
      else {
        $avg = 'N/A';
      }
      $table[] = array(
        $name,
        $memory,
        $count,
        $avg,
      );
    }
    drush_print_table($table, TRUE);
  }
}

/**
 * Command argument complete callback.
 *
 * @return
 *   List of migrations.
 */
function migrate_migrate_status_complete() {
  return array(
    'values' => drush_migrate_migrations(),
  );
}
function migrate_migrate_import_complete() {
  return array(
    'values' => drush_migrate_migrations(),
  );
}
function migrate_migrate_rollback_complete() {
  return array(
    'values' => drush_migrate_migrations(),
  );
}
function migrate_migrate_fields_destination_complete() {
  return array(
    'values' => drush_migrate_migrations(),
  );
}
function migrate_migrate_fields_source_complete() {
  return array(
    'values' => drush_migrate_migrations(),
  );
}
function migrate_migrate_mappings_complete() {
  return array(
    'values' => drush_migrate_migrations(),
  );
}
function migrate_migrate_reset_status_complete() {
  return array(
    'values' => drush_migrate_migrations(),
  );
}
function migrate_migrate_stop_complete() {
  return array(
    'values' => drush_migrate_migrations(),
  );
}
function drush_migrate_migrations() {
  drush_bootstrap(DRUPAL_BOOTSTRAP_FULL);
  $migrations = migrate_migrations();
  foreach ($migrations as $migration) {
    $values[] = $migration
      ->getMachineName();
  }
  return $values;
}

Functions

Namesort descending Description
drush_migrate_analyze Analyze the source fields for any passed migrations.
drush_migrate_analyze_validate
drush_migrate_audit Display field mappings for a migration.
drush_migrate_audit_validate
drush_migrate_auto_register Register any previously-unrecognized non-dynamic migrations.
drush_migrate_deregister Deregister a given migration, or all orphaned migrations. Note that the migration might no longer "exist" (the class implementation might be gone), so we can't count on being able to instantiate it, or use migrate_migrations().
drush_migrate_deregister_migration Given a migration machine name, remove its tracking from the database.
drush_migrate_fields_destination
drush_migrate_fields_destination_validate
drush_migrate_fields_source
drush_migrate_fields_source_validate
drush_migrate_get_migrations
drush_migrate_get_options Get the value of all migrate related options. Used when spawning a subshell. Don't pass along all, stop, update, and rollback options.
drush_migrate_import Perform import on one or more migrations.
drush_migrate_import_validate
drush_migrate_invoke_process
drush_migrate_mappings Display field mappings for a migration.
drush_migrate_mappings_validate
drush_migrate_messages Display messages for a migration.
drush_migrate_migrations
drush_migrate_pre_migrate_import
drush_migrate_print_memory
drush_migrate_reset_status Reset the status of a given migration.
drush_migrate_reset_status_validate
drush_migrate_rollback Roll back one specified migration
drush_migrate_rollback_validate
drush_migrate_status A simplified version of the dashboard page.
drush_migrate_stop
drush_migrate_stop_validate
drush_migrate_validate_common
drush_migrate_wipe A drush command callback.
migrate_drush_command Implements hook_drush_command().
migrate_migrate_fields_destination_complete
migrate_migrate_fields_source_complete
migrate_migrate_import_complete
migrate_migrate_mappings_complete
migrate_migrate_reset_status_complete
migrate_migrate_rollback_complete
migrate_migrate_status_complete Command argument complete callback.
migrate_migrate_stop_complete