diff --git a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Form/MigrateDrupalUpgradeForm.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Form/MigrateDrupalUpgradeForm.php new file mode 100644 index 0000000..02102c0 --- /dev/null +++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Form/MigrateDrupalUpgradeForm.php @@ -0,0 +1,349 @@ + $this->t('Enter the database credentials for the legacy Drupal ' . + 'site you are upgrading into this Drupal 8 instance.'), + ); + // The following is stolen from install.core.inc. If the install process + // would use form classes (https://drupal.org/node/2112569), we could inherit. + global $databases; + + $database = isset($databases['default']['default']) ? $databases['default']['default'] : array(); + + $form['#title'] = $this->t('Database configuration'); + + require_once DRUPAL_ROOT . '/core/includes/install.inc'; + $drivers = drupal_get_database_types(); + $drivers_keys = array_keys($drivers); + + $form['driver'] = array( + '#type' => 'radios', + '#title' => t('Database type'), + '#required' => TRUE, + '#default_value' => !empty($database['driver']) ? $database['driver'] : current($drivers_keys), + ); + if (count($drivers) == 1) { + $form['driver']['#disabled'] = TRUE; + } + + // Add driver specific configuration options. + foreach ($drivers as $key => $driver) { + $form['driver']['#options'][$key] = $driver->name(); + + $form['settings'][$key] = $driver->getFormOptions($database); + $form['settings'][$key]['#prefix'] = '

' . + $this->t('@driver_name settings', array('@driver_name' => $driver->name())) . '

'; + $form['settings'][$key]['#type'] = 'container'; + $form['settings'][$key]['#tree'] = TRUE; + $form['settings'][$key]['advanced_options']['#parents'] = array($key); + $form['settings'][$key]['#states'] = array( + 'visible' => array( + ':input[name=driver]' => array('value' => $key), + ) + ); + } + + $form['actions'] = array('#type' => 'actions'); + $form['actions']['save'] = array( + '#type' => 'submit', + '#value' => t('Next'), + '#button_type' => 'primary', + '#limit_validation_errors' => array( + array('driver'), + array(isset($form_state['input']['driver']) ? $form_state['input']['driver'] : current($drivers_keys)), + ), + ); + return $form; + } + + /** + * Prepare to import configuration. + */ + public function configurationStep(array &$form_state) { + Database::addConnectionInfo('migrate', 'default', $form_state['database']); + $version = $form_state['drupal_version']; + + $form['submit'] = array( + '#type' => 'submit', + '#value' => $this->t('Import configuration from your Drupal @version site', + array('@version' => $version)), + ); + + return $form; + + // @todo: Implement requirements checking. + $migrations = $this->storageController()->loadMultiple(); + // For now, identify the relevant migrations based on prefix. + $prefix = 'd' . $version . '_'; + $options = array(); + $checked = array(); + foreach ($migrations as $machine_name => $migration) { + if (!strncmp($prefix, $machine_name, strlen($prefix))) { + // Only include those migrations which meet any specified requirements. + $source_plugin = $migration->getSourcePlugin(); + if ($source_plugin instanceof RequirementsInterface) { + if ($source_plugin->checkRequirements()) { + $source_ok = TRUE; + } + else { + $source_ok = FALSE; + } + } + else { + $source_ok = TRUE; + } + if ($source_ok) { + // Use the human-friendly migration name if any. + // @todo: Doesn't actually work atm. And, the migration class itself + // ought to default the label name to the ID. + $label = $migration->label(); + if (!$label) { + $label = $machine_name; + } + $options[$machine_name] = $label; + try { + $destination_plugin = $migration->getDestinationPlugin(); + } + catch (\Exception $e) { + drupal_set_message('Failed to get destination plugin: @message', + array('@message' => $e->getMessage())); + } + if (!empty($destination_plugin) && ($destination_plugin instanceof RequirementsInterface)) { + if ($destination_plugin->checkRequirements()) { + $destination_ok = TRUE; + } + else { + $destination_ok = FALSE; + } + } + else { + $destination_ok = TRUE; + } + // Precheck all those with valid destinations. If the source if + // valid but not the destination (e.g., a module that was enabled + // on the source side is not enabled on the destination side), we'll + // leave it unchecked. + // @todo: Right now no destinations define requirements. + // @todo: Give user feedback on why a migration is unchecked (e.g., + // "The forum module is enabled on the legacy site but not on the + // current site - if you wish to preserve your forum content, please + // enable and configure the forum module on this site." + if ($destination_ok) { + $checked[$machine_name] = $machine_name; + } + } + } + } + $form['migrations'] = array( + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => $checked, + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => $this->t('Submit'), + ); + return $form; + } + + /** + * Prepare to import configuration. + */ + public function contentStep(array &$form_state) { + Database::addConnectionInfo('migrate', 'default', $form_state['database']); + $version = $form_state['drupal_version']; + + $form['submit'] = array( + '#type' => 'submit', + '#value' => $this->t('Import content from your Drupal @version site', + array('@version' => $version)), + ); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, array &$form_state) { + // The multistep is for testing only. The final version will run a fixed + // set of migrations. + // @todo: Skip credential step if 'migrate' connection already defined. + if (!isset($form_state['database'])) { + $form = $this->credentialStep(); + } + elseif (isset($form_state['step'])) { + $step = $form_state['step']; + switch ($step) { + case 'configuration': + // @todo: Skip configuration step if configuration import is complete. + $form = $this->configurationStep($form_state); + break; + case 'content': + $form = $this->contentStep($form_state); + break; + } + } + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, array &$form_state) { + if (isset($form_state['values']['driver'])) { + // Verify we have a valid connection to a Drupal database supported for + // upgrade. + $driver = $form_state['values']['driver']; + $form_state['database'] = $form_state['values'][$driver]; + $form_state['database']['driver'] = $driver; + // @todo: There should be a DrupalSqlBase method to use to + // determine the version. + try { + Database::addConnectionInfo('migrate', 'default', $form_state['database']); + $connection = Database::getConnection('default', 'migrate'); + } + catch (\Exception $e) { + $message = t('Unable to connect to the source database. %message', + array('%message' => $e->getMessage())); + $this->setFormError(NULL, $form_state, $message); + return; + } + if (!$connection->schema()->tableExists('node')) { + $this->setFormError(NULL, $form_state, t('Source database does not ' . + 'contain a Drupal installation.')); + } + // Note we check D8 first, because it's reintroduced the menu_router + // table we have used as the signature of D6. + elseif ($connection->schema()->tableExists('key_value')) { + $this->setFormError(NULL, $form_state, t('Upgrade from this version ' . + 'of Drupal is not supported.')); + } + elseif ($connection->schema()->tableExists('filter_format')) { + $form_state['drupal_version'] = 7; + } + elseif ($connection->schema()->tableExists('menu_router')) { + $form_state['drupal_version'] = 6; + } + else { + $this->setFormError(NULL, $form_state, t('Upgrade from this version ' . + 'of Drupal is not supported.')); + } + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, array &$form_state) { + if (isset($form_state['values']['driver'])) { + $form_state['rebuild'] = TRUE; + $form_state['step'] = 'configuration'; + $settings['databases']['migrate']['default'] = (object) array( + 'value' => $form_state['database'], + 'required' => TRUE, + ); +// drupal_rewrite_settings($settings); + } + elseif (isset($form_state['step'])) { + $migration_ids = $this->getDestinationIds($form_state['step']); + if ($form_state['step'] == 'configuration') { + $form_state['rebuild'] = TRUE; + $form_state['step'] = 'content'; + } + $batch = array( + 'title' => t('Running migrations'), + 'operations' => array( + array(array('Drupal\migrate_drupal\MigrateDrupalRunBatch', 'run'), array($migration_ids, $form_state['database'])), + ), + 'finished' => array('Drupal\migrate_drupal\MigrateDrupalRunBatch', 'finished'), + 'progress_message' => '', + 'init_message' => t('Processing migration @num of @max.', array('@num' => '1', '@max' => count($migration_ids))), + 'url_options' => array('step' => 'content'), + ); + $this->batchSet($batch); + } + } + + /** + * Set a batch. + * + * @param $batch + */ + protected function batchSet($batch) { + batch_set($batch); + } + + /** + * @return EntityStorageControllerInterface + */ + protected function storageController() { + if (!isset($this->storageController)) { + $this->storageController = \Drupal::entityManager()->getStorageController('migration'); + } + return $this->storageController; + } + + /** + * Returns the properties to be serialized + * + * @return array + */ + public function __sleep() { + // This apparently contains a PDOStatement somewhere. + unset($this->storageController); + return parent::__sleep(); + } + + /** + * Gets migrate_drupal configurations. + * + * @param string $step + * Migration configuration destination type form step. + * + * @return array + * An array of configuration and content migrations. + */ + function getDestinationIds($step) { + $names = array(); + foreach ($this->configFactory->listAll('migrate.migration.') as $config_name) { + $config = \Drupal::config($config_name); + $type = $config->get('destination.type'); + if ($type == $step) { + $names[] = $config->get('id'); + } + } + return $names; + } +} diff --git a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/MigrateDrupalRunBatch.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/MigrateDrupalRunBatch.php index 7578161..9c1ecf7 100644 --- a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/MigrateDrupalRunBatch.php +++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/MigrateDrupalRunBatch.php @@ -23,17 +23,49 @@ class MigrateDrupalRunBatch { * The batch context. */ public static function run($initial_ids, $db_spec, &$context) { - Database::addConnectionInfo('migrate', 'default', $db_spec); +// Database::addConnectionInfo('migrate', 'default', $db_spec); if (!isset($context['sandbox']['migration_ids'])) { $context['sandbox']['max'] = count($initial_ids); $context['sandbox']['migration_ids'] = $initial_ids; } +// dvm($context['sandbox']['migration_ids']); $migration_id = reset($context['sandbox']['migration_ids']); $migration = entity_load('migration', $migration_id); - // @TODO: if there are no source IDs then remove php.ini time limit. - // @TODO: move time limit back into MigrateExecutable so we can set it here. - $executable = new MigrateExecutable($migration, new MigrateMessage()); - if ($executable->import() == MigrationInterface::RESULT_COMPLETED) { + if ($migration) { + // @TODO: if there are no source IDs then remove php.ini time limit. + // @TODO: move time limit back into MigrateExecutable so we can set it here. + $executable = new MigrateExecutable($migration, new MigrateMessage()); + $migration_status = $executable->import(); + switch ($migration_status) { + case MigrationInterface::RESULT_COMPLETED: + $context['message'] = t('Imported @migration', + array('@migration' => $migration_id)); + break; + case MigrationInterface::RESULT_INCOMPLETE: + $context['message'] = t('Importing @migration', + array('@migration' => $migration_id)); + break; + case MigrationInterface::RESULT_STOPPED: + $context['message'] = t('Import stopped by request'); + break; + case MigrationInterface::RESULT_FAILED: + $context['message'] = t('Import of @migration failed', + array('@migration' => $migration_id)); + break; + case MigrationInterface::RESULT_SKIPPED: + $context['message'] = t('Import of @migration skipped due to unfulfilled dependencies', + array('@migration' => $migration_id)); + break; + case MigrationInterface::RESULT_DISABLED: + // Skip silently if disabled. + break; + } + // Unless we're continuing on with this migration, take it off the list. + if ($executable->import() != MigrationInterface::RESULT_INCOMPLETE) { + array_shift($context['sandbox']['migration_ids']); + } + } + else { array_shift($context['sandbox']['migration_ids']); } $context['finished'] = 1 - count($context['sandbox']['migration_ids']) / $context['sandbox']['max']; diff --git a/core/modules/migrate_drupal/migrate_drupal.module b/core/modules/migrate_drupal/migrate_drupal.module index 5435421..0dc7c4b 100644 --- a/core/modules/migrate_drupal/migrate_drupal.module +++ b/core/modules/migrate_drupal/migrate_drupal.module @@ -1,5 +1,9 @@ 'Migration', + 'description' => 'Manage automatic site maintenance tasks.', + 'route_name' => 'migrate_drupal.upgrade', + 'weight' => 30, + ); + + return $items; } /** - * Implements hook_node_presave(). + * Implements hook_menu_link_defaults(). */ -function migrate_drupal_node_presave(EntityInterface $node) { - // Always save a revision for non-administrators. - if (empty($node->beingMigrated) && !empty($node->book['bid']) && !\Drupal::currentUser()->hasPermission('administer nodes')) { - $node->setNewRevision(); - } - // Make sure a new node gets a new menu link. - if ($node->isNew()) { - $node->book['nid'] = NULL; - } +function migrate_drupal_menu_link_defaults() { + $items['system.admin.config.system.upgrade'] = array( + 'link_title' => 'Migration', + 'parent' => 'system.admin.config.system', + 'description' => 'Migrate configuration and content from your previous Drupal version.', + 'route_name' => 'migrate_drupal.upgrade', + 'weight' => 30, + ); + + return $items; } diff --git a/core/modules/migrate_drupal/migrate_drupal.routing.yml b/core/modules/migrate_drupal/migrate_drupal.routing.yml index a7e8fef..eb24fee 100644 --- a/core/modules/migrate_drupal/migrate_drupal.routing.yml +++ b/core/modules/migrate_drupal/migrate_drupal.routing.yml @@ -5,3 +5,10 @@ migrate_drupal.run: _title: 'Run' requirements: _permission: 'administer site configuration' +migrate_drupal.upgrade: + path: '/admin/config/system/upgrade' + defaults: + _form: '\Drupal\migrate_drupal\Form\MigrateDrupalUpgradeForm' + _title: 'Migration' + requirements: + _permission: 'administer site configuration'