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..3898241 --- /dev/null +++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Form/MigrateDrupalUpgradeForm.php @@ -0,0 +1,313 @@ +t('Upgrade step 1: Source site information'); + + $form['site_address'] = array( + '#type' => 'textfield', + '#title' => $this->t('Source site address'), + '#description' => $this->t('Enter the address of your current Drupal ' . + 'site (e.g. "http://www.example.com"). This address will be used to ' . + 'retrieve any public files from the site.'), + ); + + $form['private_file_directory'] = array( + '#type' => 'textfield', + '#title' => $this->t('Private file directory'), + '#description' => $this->t('If you have private files on your current ' . + 'Drupal site which you want imported, please copy the complete private ' . + 'file directory to a place accessible by your new Drupal 8 web server. ' . + 'Enter the address of the directory (e.g., "/home/legacy_files/private" ' . + 'or "http://private.example.com/legacy_files/private") here.'), + ); + + $form['database_description'] = array( + '#markup' => $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(); + + 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['#title'] = $this->t('Upgrade step 2: Import configuration'); + + $form['description'] = array( + '#markup' => $this->t('We will now import configuration, including ' . + 'system settings and any vocabularies and content type and field ' . + 'definitions, from the Drupal @version version of your site into this ' . + 'new Drupal 8 site.', array('@version' => $version)), + '#suffix' => '
', + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => $this->t('Import configuration'), + ); + + 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['#title'] = $this->t('Upgrade step 3: Import content'); + + $form['description'] = array( + '#markup' => $this->t('We will now import content, including any nodes, ' . + 'comments, users, and taxonomy terms, from the Drupal @version ' . + 'version of your site into this new Drupal 8 site.', + array('@version' => $version)), + '#suffix' => '
', + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => $this->t('Import content'), + ); + + 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'; + } + 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'; + } + else { + $form_state['redirect'] = 'admin/config/system'; + } + $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' => '', + 'url_options' => array('step' => 'content'), + ); + $this->batchSet($batch); + } + } + + /** + * Set a batch. + * + * @param $batch + */ + protected function batchSet($batch) { + batch_set($batch); + } + + /** + * @return EntityStorageInterface + */ + protected function storage() { + if (!isset($this->storage)) { + $this->storage = \Drupal::entityManager()->getStorage('migration'); + } + return $this->storage; + } + + /** + * Returns the properties to be serialized + * + * @return array + */ + public function __sleep() { + // This apparently contains a PDOStatement somewhere. + unset($this->storage); + 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) { + $manifest = drupal_get_path('module', 'migrate_drupal') . '/migrate.'; + if ($step == 'content') { + $manifest .= 'content'; + } + else { + $manifest .= 'config'; + } + $manifest .= '.yml'; + $list = Yaml::parse($manifest); + $names = $list[$step]; + 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..7b5570b 100644 --- a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/MigrateDrupalRunBatch.php +++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/MigrateDrupalRunBatch.php @@ -10,7 +10,6 @@ use Drupal\Core\Database\Database; use Drupal\migrate\Entity\MigrationInterface; use Drupal\migrate\MigrateExecutable; -use Drupal\migrate\MigrateMessage; class MigrateDrupalRunBatch { @@ -30,13 +29,55 @@ public static function run($initial_ids, $db_spec, &$context) { } $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. + $messages = new MigrateMessageCapture(); + $executable = new MigrateExecutable($migration, $messages); + $migration_name = $migration->label() ? $migration->label() : $migration_id; + $migration_status = $executable->import(); + switch ($migration_status) { + case MigrationInterface::RESULT_COMPLETED: + $context['message'] = t('Imported @migration', + array('@migration' => $migration_name)); + break; + case MigrationInterface::RESULT_INCOMPLETE: + $context['message'] = t('Importing @migration', + array('@migration' => $migration_name)); + 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_name)); + break; + case MigrationInterface::RESULT_SKIPPED: + $context['message'] = t('Import of @migration skipped due to unfulfilled dependencies', + array('@migration' => $migration_name)); + break; + case MigrationInterface::RESULT_DISABLED: + // Skip silently if disabled. + break; + } + + // Add any captured messages. + foreach ($messages->getMessages() as $message) { + $context['message'] .= "
\n" . $message; + } + + // 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']; + if (!empty($context['message'])) { + $context['results'][] = $context['message']; + } } /** @@ -46,6 +87,8 @@ public static function run($initial_ids, $db_spec, &$context) { * @param $elapsed */ public static function finished($success, $results, $operations, $elapsed) { - + foreach ($results as $message) { + drupal_set_message($message); + } } } diff --git a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/MigrateMessageCapture.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/MigrateMessageCapture.php new file mode 100644 index 0000000..20a775a --- /dev/null +++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/MigrateMessageCapture.php @@ -0,0 +1,45 @@ +messages[] = $message; + } + + /** + * Clear out any captured messages. + */ + public function clear() { + $this->messages = array(); + } + + /** + * Return any captured messages. + * + * @return array + */ + public function getMessages() { + return $this->messages; + } +} diff --git a/core/modules/migrate_drupal/migrate.config.yml b/core/modules/migrate_drupal/migrate.config.yml index 8c3d62d..a1ba7f2 100644 --- a/core/modules/migrate_drupal/migrate.config.yml +++ b/core/modules/migrate_drupal/migrate.config.yml @@ -4,27 +4,26 @@ configuration: - d6_book_settings - d6_cck_field_values:* - d6_cck_field_revision:* - - d6_comment_entity_display - - d6_comment_entity_form_display - - d6_comment_field - - d6_comment_field_instance - - d6_contact_category - - d6_contact_settings - d6_date_formats - d6_dblog_settings - - d6_field - - d6_field_instance - - d6_field_instance_widget_settings - - d6_field_settings - - d6_field_formatter_settings + - d6_view_modes - d6_file_settings - d6_filter_format - d6_forum_settings - d6_locale_settings - d6_menu_settings - d6_menu - - d6_node_settings - d6_node_type + - d6_node_settings + - d6_field + - d6_field_instance + - d6_field_instance_widget_settings + - d6_field_settings + - d6_field_formatter_settings + - d6_comment_field + - d6_comment_field_instance + - d6_comment_entity_display + - d6_comment_entity_form_display - d6_search_page - d6_search_settings - d6_simpletest_settings @@ -39,27 +38,27 @@ configuration: - d6_system_performance - d6_system_rss - d6_system_site - - d6_system_theme - d6_taxonomy_settings - d6_taxonomy_vocabulary - d6_text_settings - d6_update_settings - - d6_upload_entity_display - - d6_upload_entity_form_display - d6_upload_field - d6_upload_field_instance + - d6_upload_entity_display + - d6_upload_entity_form_display - d6_user_mail + - d6_user_profile_field - d6_user_profile_field_instance + - d6_user_picture_field + - d6_user_picture_field_instance - d6_user_profile_entity_display - d6_user_profile_entity_form_display - - d6_user_profile_field - d6_user_picture_entity_display - d6_user_picture_entity_form_display - - d6_user_picture_field_instance - - d6_user_picture_field - d6_user_role - - d6_view_modes + - d6_contact_category + - d6_contact_settings + - d6_vocabulary_field + - d6_vocabulary_field_instance - d6_vocabulary_entity_display - d6_vocabulary_entity_form_display - - d6_vocabulary_field_instance - - d6_vocabulary_field diff --git a/core/modules/migrate_drupal/migrate.content.yml b/core/modules/migrate_drupal/migrate.content.yml index 9819a8d..3f6d993 100644 --- a/core/modules/migrate_drupal/migrate.content.yml +++ b/core/modules/migrate_drupal/migrate.content.yml @@ -2,14 +2,19 @@ content: - d6_aggregator_feed - d6_aggregator_item - d6_block - - d6_comment - d6_custom_block - d6_file - - d6_profile_values:user - d6_taxonomy_term - - d6_term_node_revision:* + - d6_user_picture_file + - d6_user + - d6_profile_values:user + - d6_node:* + - d6_book + - d6_cck_field_values + - d6_node_revision:* + - d6_cck_field_revision - d6_term_node:* + - d6_term_node_revision:* + - d6_comment - d6_upload - - d6_url_alias - - d6_user - - d6_user_picture_file +# - d6_url_alias diff --git a/core/modules/migrate_drupal/migrate_drupal.menu_links.yml b/core/modules/migrate_drupal/migrate_drupal.menu_links.yml new file mode 100644 index 0000000..ccd137c --- /dev/null +++ b/core/modules/migrate_drupal/migrate_drupal.menu_links.yml @@ -0,0 +1,5 @@ +migrate_drupal.upgrade: + title: Upgrade from Drupal 6 or Drupal 7 + parent: system.admin_config_system + description: 'Migrate configuration and content from your previous Drupal version.' + route_name: migrate_drupal.upgrade 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'