diff --git a/migrate_tools.routing.yml b/migrate_tools.routing.yml index b12735a..cecd682 100644 --- a/migrate_tools.routing.yml +++ b/migrate_tools.routing.yml @@ -74,6 +74,13 @@ entity.migration.process: _title: 'Process' requirements: _permission: 'administer migrations' +entity.migration.process.run: + path: '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/process/run' + defaults: + _controller: '\Drupal\migrate_tools\Controller\MigrationController::run' + _title: 'Run' + requirements: + _permission: 'administer migrations' entity.migration.destination: path: '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/destination' defaults: @@ -107,3 +114,11 @@ migrate_tools.messages: _title: 'Messages' requirements: _permission: 'administer migrations' + +migrate_tools.launch: + path: '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/launch' + defaults: + _form: '\Drupal\migrate_tools\Form\MigrationLaunchForm' + _title: 'Launch migration' + requirements: + _permission: 'administer migrations' diff --git a/src/Controller/MigrationController.php b/src/Controller/MigrationController.php index 09ae154..d7abece 100644 --- a/src/Controller/MigrationController.php +++ b/src/Controller/MigrationController.php @@ -9,6 +9,9 @@ use Drupal\Component\Utility\Html; use Drupal\migrate_plus\Plugin\MigrationConfigEntityPluginManager; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Url; +use Drupal\migrate_tools\MigrateExecutable; +use Drupal\migrate\MigrateMessage; /** * Returns responses for migrate_tools migration view routes. @@ -146,6 +149,38 @@ class MigrationController extends ControllerBase implements ContainerInjectionIn } /** + * Run a migration. + * + * @param string $migration_group + * Machine name of the migration's group. + * @param string $migration + * Machine name of the migration. + * + * @return array + * A render array as expected by drupal_render(). + */ + public function run($migration_group, $migration) { + $manager = \Drupal::service('plugin.manager.config_entity_migration'); + $plugins = $manager->createInstances([]); + $migrateMessage = new MigrateMessage(); + + $batch = array( + 'title' => t('Processing migration...'), + 'operations' => array( + array('Drupal\migrate_tools\Controller\MigrationController::batchImport', [$plugins[$migration], $migrateMessage, []]), + ) + ); + + batch_set($batch); + return batch_process("admin/structure/migrate/manage/$migration_group/migrations"); + } + + public static function batchImport($plugin, $message, $options) { + $executable = new MigrateExecutable($plugin, $message, $options); + $executable->import(); + } + + /** * Display process information of a migration entity. * * @param string $migration_group @@ -205,6 +240,12 @@ class MigrationController extends ControllerBase implements ContainerInjectionIn '#empty' => $this->t('No process defined.'), ]; + $build['process']['run'] = [ + '#type' => 'link', + '#title' => $this->t('Run'), + '#url' => Url::fromRoute('entity.migration.process.run', ['migration_group' => $migration_group, 'migration' => $migration->id()]), + ]; + return $build; } diff --git a/src/Controller/MigrationListBuilder.php b/src/Controller/MigrationListBuilder.php index 16ee84f..8486cd3 100644 --- a/src/Controller/MigrationListBuilder.php +++ b/src/Controller/MigrationListBuilder.php @@ -111,6 +111,7 @@ class MigrationListBuilder extends ConfigEntityListBuilder implements EntityHand $header['unprocessed'] = $this->t('Unprocessed'); $header['messages'] = $this->t('Messages'); $header['last_imported'] = $this->t('Last Imported'); + $header['operations'] = $this->t('Operations'); return $header; // + parent::buildHeader(); } @@ -176,6 +177,20 @@ class MigrationListBuilder extends ConfigEntityListBuilder implements EntityHand else { $row['last_imported'] = ''; } + + $row['operations']['data'] = array( + '#type' => 'dropbutton', + '#links' => array( + 'simple_form' => array( + 'title' => $this->t('Launch'), + 'url' => Url::fromRoute('migrate_tools.launch', array( + 'migration_group' => $migration_group, + 'migration' => $migration->id() + )), + ), + ), + ); + return $row; // + parent::buildRow($migration_entity); } diff --git a/src/Form/MigrationLaunchForm.php b/src/Form/MigrationLaunchForm.php new file mode 100644 index 0000000..95ac16d --- /dev/null +++ b/src/Form/MigrationLaunchForm.php @@ -0,0 +1,347 @@ +migrateMigrateOperations(); + + return $form; + } + + + /** + * Get Operations. + */ + private function migrateMigrateOperations() { + // Build the 'Update options' form. + $operations = array( + '#type' => 'fieldset', + '#title' => t('Operations'), + ); + + $options = array( + '' => t('Please select'), + 'import_immediate' => t('Import immediately'), + 'rollback_immediate' => t('Rollback immediately'), + 'stop' => t('Stop'), + 'reset' => t('Reset'), + ); + $operations['operation'] = array( + '#type' => 'select', + '#title' => t('Operation'), + '#title_display' => 'invisible', + '#options' => $options, + ); + $operations['submit'] = array( + '#type' => 'submit', + '#value' => t('Execute'), + ); + $operations['description'] = array( + '#prefix' => '

', + '#markup' => t( + 'Choose an operation to run on all selections above: +

' + ), + '#postfix' => '

', + ); + + $operations['options'] = array( + '#type' => 'fieldset', + '#title' => t('Options'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $operations['options']['update'] = array( + '#type' => 'checkbox', + '#title' => t('Update'), + '#description' => t('Check this box to update all previously-imported content + in addition to importing new content. Leave unchecked to only import + new content'), + ); + $operations['options']['force'] = array( + '#type' => 'checkbox', + '#title' => t('Ignore dependencies'), + '#description' => t('Check this box to ignore dependencies when running imports + - all tasks will run whether or not their dependent tasks have + completed.'), + ); + $operations['options']['limit'] = array( + '#tree' => TRUE, + '#type' => 'fieldset', + '#attributes' => array('class' => array('container-inline')), + 'value' => array( + '#type' => 'textfield', + '#title' => t('Limit to:'), + '#size' => 10, + ), + 'unit' => array( + '#type' => 'select', + '#options' => array( + 'items' => t('items'), + 'seconds' => t('seconds'), + ), + '#description' => t('Set a limit of how many items to process for + each migration task, or how long each should run.'), + ), + ); + + return $operations; + } + + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + if (empty($form_state->getValue('operation'))) { + $form_state->setErrorByName('operation', $this->t('Please select an operation.')); + return; + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + + $operation = $form_state->getValue('operation'); + + $limit = $form_state->getValue('limit'); + + if ($form_state->getValue('update')) { + $update = $form_state->getValue('update'); + } + else { + $update = 0; + } + if ($form_state->getValue('force')) { + $force = $form_state->getValue('force'); + } + else { + $force = 0; + } + + $migration_name = \Drupal::routeMatch()->getParameter('migration'); + $machine_name = $migration_name; + + $operations = array(); + + if ($migration_name) { + + switch ($operation) { + case 'import_immediate': + + $migrationPluginManager = \Drupal::service('plugin.manager.config_entity_migration'); + $migrations = $migrationPluginManager->createInstances([$machine_name]); + + $options = array( + 'limit' => $limit, + 'update' => $update, + 'force' => $force + ); + + $operations += $this->batchOperations($migrations, $options, 'import'); + + break; + + case 'rollback_immediate': + + $migrationPluginManager = \Drupal::service('plugin.manager.config_entity_migration'); + $migrations = $migrationPluginManager->createInstances([$machine_name]); + + $options = array( + 'limit' => $limit, + 'update' => $update, + 'force' => $force + ); + + $operations += $this->batchOperations($migrations, $options, 'rollback'); + + break; + + case 'stop': + + $migrationPluginManager = \Drupal::service('plugin.manager.config_entity_migration'); + $migrations = $migrationPluginManager->createInstances([$machine_name]); + + /** @var MigrationInterface $migration */ + foreach ($migrations as $migration) { + $migration->interruptMigration(MigrationInterface::RESULT_STOPPED); + } + + break; + + case 'reset': + + $migrationPluginManager = \Drupal::service('plugin.manager.config_entity_migration'); + $migrations = $migrationPluginManager->createInstances([$machine_name]); + + /** @var MigrationInterface $migration */ + foreach ($migrations as $migration) { + $migration->setStatus(MigrationInterface::STATUS_IDLE); + } + + break; + + } + } + + // Only immediate rollback and import operations will need to go through Batch API. + if (count($operations) > 0) { + + $batch = array( + 'operations' => $operations, + 'title' => t('Import processing'), + 'init_message' => t('Starting import process'), + 'progress_message' => t('Processing %migrate', array('%migrate' => $machine_name)), + 'error_message' => t('An error occurred. Some or all of the import processing has failed.'), + 'finished' => array($this, 'batchFinished'), + ); + + batch_set($batch); + } + + } + + /** + * Helper to generate the batch operations for importing migrations. + * + * @param array $migrations + * @param array $options + * @param array $operation + * + * @return array + */ + protected function batchOperations($migrations, $options, $operation) { + + $migrationPluginManager = \Drupal::service('plugin.manager.config_entity_migration'); + + $operations = array(); + + /** + * @var string $id + * @var Migration $migration + */ + foreach ($migrations as $id => $migration) { + + if ($options['update']) { + $migration->getIdMap()->prepareUpdate(); + } + + if (!empty($options['force'])) { + $migration->set('requirements', []); + } + else { + if ($required_IDS = $migration->get('requirements')) { + $required_migrations = $migrationPluginManager->createInstances($required_IDS); + $dependency_options = array_merge($options, ['is_dependency' => TRUE]); + $operations += $this->batchOperations($required_migrations, $dependency_options, $operation); + } + } + + $operations[] = array(array($this, 'batchProcess'), array($migration, $options, $operation)); + } + + return $operations; + } + + /** + * Batch 'operation' callback + * + * @param MigrateExecutableInterface $executable + * @param MigrationInterface $migration + * @param string $operation + * @param array $context + */ + public function batchProcess($migration, $options, $operation, &$context) { + + if (empty($context['sandbox'])) { + $context['sandbox']['progress'] = 0; + $context['sandbox']['offset'] = 0; + $total = $source_plugin = $migration->getSourcePlugin()->count(); + $context['sandbox']['max'] = $total; + } + + $executable = new MigrateExecutable($migration, $this, $options); + call_user_func(array($executable, $operation)); + + $context['sandbox']['results'] = 'OK'; + $context['sandbox']['progress'] = $context['sandbox']['max']; + $context['sandbox']['offset'] = $context['sandbox']['max']; + $context['message'] = 'Processed ' . $migration->id(); + + if ($context['sandbox']['progress'] != $context['sandbox']['max']) { + $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; + } + } + + /** + * Batch 'finished' callback + */ + public function batchFinished($success, $results, $operations) { + if ($success) { + // TODO maybe do something with results. + } + else { + // An error occurred. + // $operations contains the operations that remained unprocessed. + $error_operation = reset($operations); + $message = t('An error occurred while processing %error_operation with arguments: @arguments', array( + '%error_operation' => $error_operation[0], + '@arguments' => print_r($error_operation[1], TRUE) + )); + drupal_set_message($message, 'error'); + } + + } + + /** + * {@inheritdoc} + */ + public function display($message, $type = 'status') { + drupal_set_message($message, ($type=='error'?'error':'notice')); + } + +}