From da3818ab0e31696c0fc3aa988c9d87f78dca0cff Mon Sep 17 00:00:00 2001 From: Alex Pott Date: Mon, 12 Feb 2018 21:25:39 +0000 Subject: [PATCH] Issue #2918761 by quietone, chiranjeeb2410, phenaproxima, maxocub, heddn, alexpott: Break up MigrateUpgradeForm into smaller forms --- .../migrate_drupal_ui.routing.yml | 38 +- .../src/Form/CredentialForm.php | 257 ++++ .../src/Form/IdConflictForm.php | 200 +++ .../src/Form/IncrementalForm.php | 131 ++ .../src/Form/MigrateUpgradeForm.php | 1077 ----------------- .../src/Form/MigrateUpgradeFormBase.php | 109 ++ .../src/Form/OverviewForm.php | 92 ++ .../migrate_drupal_ui/src/Form/ReviewForm.php | 380 ++++++ 8 files changed, 1206 insertions(+), 1078 deletions(-) create mode 100644 core/modules/migrate_drupal_ui/src/Form/CredentialForm.php create mode 100644 core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php create mode 100644 core/modules/migrate_drupal_ui/src/Form/IncrementalForm.php delete mode 100644 core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php create mode 100644 core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeFormBase.php create mode 100644 core/modules/migrate_drupal_ui/src/Form/OverviewForm.php create mode 100644 core/modules/migrate_drupal_ui/src/Form/ReviewForm.php diff --git a/core/modules/migrate_drupal_ui/migrate_drupal_ui.routing.yml b/core/modules/migrate_drupal_ui/migrate_drupal_ui.routing.yml index d02ea98dbd1..9eff2227891 100644 --- a/core/modules/migrate_drupal_ui/migrate_drupal_ui.routing.yml +++ b/core/modules/migrate_drupal_ui/migrate_drupal_ui.routing.yml @@ -1,7 +1,43 @@ migrate_drupal_ui.upgrade: path: '/upgrade' defaults: - _form: '\Drupal\migrate_drupal_ui\Form\MigrateUpgradeForm' + _form: '\Drupal\migrate_drupal_ui\Form\OverviewForm' + _title: 'Upgrade' + requirements: + _custom_access: '\Drupal\migrate_drupal_ui\MigrateAccessCheck::checkAccess' + options: + _admin_route: TRUE +migrate_drupal_ui.upgrade_incremental: + path: '/upgrade/incremental' + defaults: + _form: '\Drupal\migrate_drupal_ui\Form\IncrementalForm' + _title: 'Upgrade' + requirements: + _custom_access: '\Drupal\migrate_drupal_ui\MigrateAccessCheck::checkAccess' + options: + _admin_route: TRUE +migrate_drupal_ui.upgrade_credential: + path: '/upgrade/credentials' + defaults: + _form: '\Drupal\migrate_drupal_ui\Form\CredentialForm' + _title: 'Upgrade' + requirements: + _custom_access: '\Drupal\migrate_drupal_ui\MigrateAccessCheck::checkAccess' + options: + _admin_route: TRUE +migrate_drupal_ui.upgrade_id_conflict: + path: '/upgrade/idconflict' + defaults: + _form: '\Drupal\migrate_drupal_ui\Form\IdConflictForm' + _title: 'Upgrade' + requirements: + _custom_access: '\Drupal\migrate_drupal_ui\MigrateAccessCheck::checkAccess' + options: + _admin_route: TRUE +migrate_drupal_ui.upgrade_review: + path: '/upgrade/review' + defaults: + _form: '\Drupal\migrate_drupal_ui\Form\ReviewForm' _title: 'Upgrade' requirements: _custom_access: '\Drupal\migrate_drupal_ui\MigrateAccessCheck::checkAccess' diff --git a/core/modules/migrate_drupal_ui/src/Form/CredentialForm.php b/core/modules/migrate_drupal_ui/src/Form/CredentialForm.php new file mode 100644 index 00000000000..c3cbbc8b49f --- /dev/null +++ b/core/modules/migrate_drupal_ui/src/Form/CredentialForm.php @@ -0,0 +1,257 @@ +renderer = $renderer; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('renderer'), + $container->get('tempstore.private') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'migrate_drupal_ui_credential_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + if ($this->store->get('step') != 'credential') { + $this->store->set('step', 'overview'); + return $this->redirect('migrate_drupal_ui.upgrade'); + } + + $form = parent::buildForm($form, $form_state); + $form['actions']['submit']['#value'] = $this->t('Review upgrade'); + + $form['#title'] = $this->t('Drupal Upgrade'); + + $drivers = $this->getDatabaseTypes(); + $drivers_keys = array_keys($drivers); + // @todo https://www.drupal.org/node/2678510 Because this is a multi-step + // form, the form is not rebuilt during submission. Ideally we would get + // the chosen driver from form input, if available, in order to use + // #limit_validation_errors in the same way + // \Drupal\Core\Installer\Form\SiteSettingsForm does. + $default_driver = current($drivers_keys); + + $default_options = []; + + $form['version'] = [ + '#type' => 'radios', + '#default_value' => 7, + '#title' => $this->t('Drupal version of the source site'), + '#options' => ['6' => $this->t('Drupal 6'), '7' => $this->t('Drupal 7')], + '#required' => TRUE, + ]; + + $form['database'] = [ + '#type' => 'details', + '#title' => $this->t('Source database'), + '#description' => $this->t('Provide credentials for the database of the Drupal site you want to upgrade.'), + '#open' => TRUE, + ]; + + $form['database']['driver'] = [ + '#type' => 'radios', + '#title' => $this->t('Database type'), + '#required' => TRUE, + '#default_value' => $default_driver, + ]; + if (count($drivers) == 1) { + $form['database']['driver']['#disabled'] = TRUE; + } + + // Add driver-specific configuration options. + foreach ($drivers as $key => $driver) { + $form['database']['driver']['#options'][$key] = $driver->name(); + + $form['database']['settings'][$key] = $driver->getFormOptions($default_options); + // @todo https://www.drupal.org/node/2678510 Using + // #limit_validation_errors in the submit does not work so it is not + // possible to require the database and username for mysql and pgsql. + // This is because this is a multi-step form. + $form['database']['settings'][$key]['database']['#required'] = FALSE; + $form['database']['settings'][$key]['username']['#required'] = FALSE; + $form['database']['settings'][$key]['#prefix'] = '

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

'; + $form['database']['settings'][$key]['#type'] = 'container'; + $form['database']['settings'][$key]['#tree'] = TRUE; + $form['database']['settings'][$key]['advanced_options']['#parents'] = [$key]; + $form['database']['settings'][$key]['#states'] = [ + 'visible' => [ + ':input[name=driver]' => ['value' => $key], + ], + ]; + + // Move the host fields out of advanced settings. + if (isset($form['database']['settings'][$key]['advanced_options']['host'])) { + $form['database']['settings'][$key]['host'] = $form['database']['settings'][$key]['advanced_options']['host']; + $form['database']['settings'][$key]['host']['#title'] = 'Database host'; + $form['database']['settings'][$key]['host']['#weight'] = -1; + unset($form['database']['settings'][$key]['database']['#default_value']); + unset($form['database']['settings'][$key]['advanced_options']['host']); + } + } + + $form['source'] = [ + '#type' => 'details', + '#title' => $this->t('Source files'), + '#open' => TRUE, + ]; + $form['source']['d6_source_base_path'] = [ + '#type' => 'textfield', + '#title' => $this->t('Files directory'), + '#description' => $this->t('To import files from your current Drupal site, enter a local file directory containing your site (e.g. /var/www/docroot), or your site address (for example http://example.com).'), + '#states' => [ + 'visible' => [ + ':input[name="version"]' => ['value' => '6'], + ], + ], + ]; + + $form['source']['source_base_path'] = [ + '#type' => 'textfield', + '#title' => $this->t('Public files directory'), + '#description' => $this->t('To import public files from your current Drupal site, enter a local file directory containing your site (e.g. /var/www/docroot), or your site address (for example http://example.com).'), + '#states' => [ + 'visible' => [ + ':input[name="version"]' => ['value' => '7'], + ], + ], + ]; + + $form['source']['source_private_file_path'] = [ + '#type' => 'textfield', + '#title' => $this->t('Private file directory'), + '#default_value' => '', + '#description' => $this->t('To import private files from your current Drupal site, enter a local file directory containing your site (e.g. /var/www/docroot).'), + '#states' => [ + 'visible' => [ + ':input[name="version"]' => ['value' => '7'], + ], + ], + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + + // Retrieve the database driver from the form, use reflection to get the + // namespace, and then construct a valid database array the same as in + // settings.php. + $driver = $form_state->getValue('driver'); + $drivers = $this->getDatabaseTypes(); + $reflection = new \ReflectionClass($drivers[$driver]); + $install_namespace = $reflection->getNamespaceName(); + + $database = $form_state->getValue($driver); + // Cut the trailing \Install from namespace. + $database['namespace'] = substr($install_namespace, 0, strrpos($install_namespace, '\\')); + $database['driver'] = $driver; + + // Validate the driver settings and just end here if we have any issues. + if ($errors = $drivers[$driver]->validateDatabaseSettings($database)) { + foreach ($errors as $name => $message) { + $form_state->setErrorByName($name, $message); + } + return; + } + + try { + $connection = $this->getConnection($database); + $version = (string) $this->getLegacyDrupalVersion($connection); + if (!$version) { + $form_state->setErrorByName($database['driver'] . '][0', $this->t('Source database does not contain a recognizable Drupal version.')); + } + elseif ($version !== (string) $form_state->getValue('version')) { + $form_state->setErrorByName($database['driver'] . '][0', $this->t('Source database is Drupal version @version but version @selected was selected.', [ + '@version' => $version, + '@selected' => $form_state->getValue('version'), + ])); + } + else { + // Setup migrations and save form data to private store. + $this->setupMigrations($database, $form_state); + } + } + catch (\Exception $e) { + $error_message = [ + '#title' => $this->t('Resolve the issue below to continue the upgrade.'), + '#theme' => 'item_list', + '#items' => [$e->getMessage()], + ]; + $form_state->setErrorByName($database['driver'] . '][0', $this->renderer->renderPlain($error_message)); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->store->set('step', 'id_conflict'); + $form_state->setRedirect('migrate_drupal_ui.upgrade_id_conflict'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Review upgrade'); + } + + /** + * Returns all supported database driver installer objects. + * + * @return \Drupal\Core\Database\Install\Tasks[] + * An array of available database driver installer objects. + */ + protected function getDatabaseTypes() { + // Make sure the install API is available. + include_once DRUPAL_ROOT . '/core/includes/install.inc'; + return drupal_get_database_types(); + } + +} diff --git a/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php b/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php new file mode 100644 index 00000000000..6ef5ab7a629 --- /dev/null +++ b/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php @@ -0,0 +1,200 @@ +pluginManager = $migration_plugin_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.migration'), + $container->get('tempstore.private') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'migrate_drupal_ui_id_conflict_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + if ($this->store->get('step') != 'id_conflict') { + $this->store->set('step', 'overview'); + return $this->redirect('migrate_drupal_ui.upgrade'); + } + + // Check if there are conflicts. If none, just skip this form! + $migration_ids = array_keys($this->store->get('migrations')); + $migrations = $this->pluginManager->createInstances($migration_ids); + + $translated_content_conflicts = $content_conflicts = []; + + $results = (new IdAuditor())->auditMultiple($migrations); + + /** @var \Drupal\migrate\Audit\AuditResult $result */ + foreach ($results as $result) { + $destination = $result->getMigration()->getDestinationPlugin(); + if ($destination instanceof EntityContentBase && $destination->isTranslationDestination()) { + // Translations are not yet supported by the audit system. For now, we + // only warn the user to be cautious when migrating translated content. + // I18n support should be added in https://www.drupal.org/node/2905759. + $translated_content_conflicts[] = $result; + } + elseif (!$result->passed()) { + $content_conflicts[] = $result; + } + } + + if ($content_conflicts || $translated_content_conflicts) { + $this->messenger()->addWarning($this->t('WARNING: Content may be overwritten on your new site.')); + + $form = parent::buildForm($form, $form_state); + $form['#title'] = $this->t('Upgrade analysis report'); + + if ($content_conflicts) { + $form = $this->conflictsForm($form, $content_conflicts); + } + if ($translated_content_conflicts) { + $form = $this->i18nWarningForm($form, $translated_content_conflicts); + } + return $form; + } + else { + $this->store->set('step', 'review'); + return $this->redirect('migrate_drupal_ui.upgrade_review'); + } + } + + /** + * Build the markup for conflict warnings. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\migrate\Audit\AuditResult[] $conflicts + * The failing audit results. + * + * @return array + * The form structure. + */ + protected function conflictsForm(array &$form, array $conflicts) { + $form['conflicts'] = [ + '#title' => $this->t('There is conflicting content of these types:'), + '#theme' => 'item_list', + '#items' => $this->formatConflicts($conflicts), + ]; + + $form['warning'] = [ + '#type' => 'markup', + '#markup' => '

' . $this->t('It looks like you have content on your new site which may be overwritten if you continue to run this upgrade. The upgrade should be performed on a clean Drupal 8 installation. For more information see the upgrade handbook.', [':id-conflicts-handbook' => 'https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#id_conflicts']) . '

', + ]; + + return $form; + } + + /** + * Formats a set of failing audit results as strings. + * + * Each string is the label of the destination plugin of the migration that + * failed the audit, keyed by the destination plugin ID in order to prevent + * duplication. + * + * @param \Drupal\migrate\Audit\AuditResult[] $conflicts + * The failing audit results. + * + * @return string[] + * The formatted audit results. + */ + protected function formatConflicts(array $conflicts) { + $items = []; + + foreach ($conflicts as $conflict) { + $definition = $conflict->getMigration()->getDestinationPlugin()->getPluginDefinition(); + $id = $definition['id']; + $items[$id] = $definition['label']; + } + sort($items, SORT_STRING); + + return $items; + } + + /** + * Build the markup for i18n warnings. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\migrate\Audit\AuditResult[] $conflicts + * The failing audit results. + * + * @return array + * The form structure. + */ + protected function i18nWarningForm(array &$form, array $conflicts) { + $form['i18n'] = [ + '#title' => $this->t('There is translated content of these types:'), + '#theme' => 'item_list', + '#items' => $this->formatConflicts($conflicts), + ]; + + $form['i18n_warning'] = [ + '#type' => 'markup', + '#markup' => '

' . $this->t('It looks like you are migrating translated content from your old site. Possible ID conflicts for translations are not automatically detected in the current version of Drupal. Refer to the upgrade handbook for instructions on how to avoid ID conflicts with translated content.', [':id-conflicts-handbook' => 'https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#id_conflicts']) . '

', + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->store->set('step', 'review'); + $form_state->setRedirect('migrate_drupal_ui.upgrade_review'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('I acknowledge I may lose data. Continue anyway.'); + } + +} diff --git a/core/modules/migrate_drupal_ui/src/Form/IncrementalForm.php b/core/modules/migrate_drupal_ui/src/Form/IncrementalForm.php new file mode 100644 index 00000000000..31ca8ab962b --- /dev/null +++ b/core/modules/migrate_drupal_ui/src/Form/IncrementalForm.php @@ -0,0 +1,131 @@ +state = $state; + $this->dateFormatter = $date_formatter; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('state'), + $container->get('date.formatter'), + $container->get('tempstore.private') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'migrate_drupal_ui_incremental_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + if ($this->store->get('step') != 'incremental') { + $this->store->set('step', 'overview'); + return $this->redirect('migrate_drupal_ui.upgrade'); + } + + $form = parent::buildForm($form, $form_state); + $form['#title'] = $this->t('Upgrade'); + + $date_performed = $this->store->get('performed'); + // @todo Add back support for rollbacks. + // https://www.drupal.org/node/2687849 + $form['upgrade_option_item'] = [ + '#type' => 'item', + '#prefix' => $this->t('An upgrade has already been performed on this site. To perform a new migration, create a clean and empty new install of Drupal 8. Rollbacks are not yet supported through the user interface. For more information, see the upgrading handbook.', [':url' => 'https://www.drupal.org/upgrade/migrate']), + '#description' => $this->t('Last upgrade: @date', ['@date' => $this->dateFormatter->format($date_performed)]), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + // Retrieve the database driver from state. + $database_state_key = $this->state->get('migrate.fallback_state_key', ''); + if ($database_state_key) { + try { + $database = $this->state->get($database_state_key, [])['database']; + if ($connection = $this->getConnection($database)) { + if ($version = $this->getLegacyDrupalVersion($connection)) { + $this->setupMigrations($database, $form_state); + $valid_legacy_database = TRUE; + } + } + } + catch (DatabaseExceptionWrapper $exception) { + // Hide DB exceptions and forward to the DB credentials form. In that + // form we can more properly display errors and accept new credentials. + } + } + if (empty($valid_legacy_database)) { + $form_state->setValue('step', 'credentials')->setRebuild(); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->store->set('step', 'id_conflict'); + $form_state->setRedirect('migrate_drupal_ui.upgrade_id_conflict'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Import new configuration and content from old site'); + } + +} diff --git a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php deleted file mode 100644 index 6ff17f2c555..00000000000 --- a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php +++ /dev/null @@ -1,1077 +0,0 @@ - [ - 'blog', - 'blogapi', - 'calendarsignup', - 'color', - 'content_copy', - 'content_multigroup', - 'content_permissions', - 'date_api', - 'date_locale', - 'date_php4', - 'date_popup', - 'date_repeat', - 'date_timezone', - 'date_tools', - 'datepicker', - 'ddblock', - 'event', - 'fieldgroup', - 'filefield_meta', - 'help', - 'i18n', - 'i18nstrings', - 'imageapi', - 'imageapi_gd', - 'imageapi_imagemagick', - 'imagecache_ui', - 'jquery_ui', - 'nodeaccess', - 'number', - 'openid', - 'php', - 'ping', - 'poll', - 'throttle', - 'tracker', - 'translation', - 'trigger', - 'variable', - 'variable_admin', - 'views_export', - 'views_ui', - ], - '7' => [ - 'blog', - 'bulk_export', - 'contextual', - 'ctools', - 'ctools_access_ruleset', - 'ctools_ajax_sample', - 'ctools_custom_content', - 'dashboard', - 'date_all_day', - 'date_api', - 'date_context', - 'date_migrate', - 'date_popup', - 'date_repeat', - 'date_repeat_field', - 'date_tools', - 'date_views', - 'entity', - 'entity_feature', - 'entity_token', - 'entityreference', - 'field_ui', - 'help', - 'openid', - 'overlay', - 'page_manager', - 'php', - 'poll', - 'search_embedded_form', - 'search_extra_type', - 'search_node_tags', - 'simpletest', - 'stylizer', - 'term_depth', - 'toolbar', - 'translation', - 'trigger', - 'views_content', - 'views_ui', - ], - ]; - - /** - * Constructs the MigrateUpgradeForm. - * - * @param \Drupal\Core\State\StateInterface $state - * The state service. - * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter - * The date formatter service. - * @param \Drupal\Core\Render\RendererInterface $renderer - * The renderer service. - * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $plugin_manager - * The migration plugin manager. - * @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $field_plugin_manager - * The field plugin manager. - * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler - * The module handler. - */ - public function __construct(StateInterface $state, DateFormatterInterface $date_formatter, RendererInterface $renderer, MigrationPluginManagerInterface $plugin_manager, MigrateFieldPluginManagerInterface $field_plugin_manager, ModuleHandlerInterface $module_handler, MessengerInterface $messenger) { - $this->state = $state; - $this->dateFormatter = $date_formatter; - $this->renderer = $renderer; - $this->pluginManager = $plugin_manager; - $this->fieldPluginManager = $field_plugin_manager; - $this->moduleHandler = $module_handler; - $this->messenger = $messenger; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('state'), - $container->get('date.formatter'), - $container->get('renderer'), - $container->get('plugin.manager.migration'), - $container->get('plugin.manager.migrate.field'), - $container->get('module_handler'), - $container->get('messenger') - ); - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return 'migrate_drupal_ui_form'; - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state) { - $this->step = $form_state->get('step') ?: 'overview'; - switch ($this->step) { - case 'overview': - return $this->buildOverviewForm($form, $form_state); - - case 'credentials': - return $this->buildCredentialForm($form, $form_state); - - case 'confirm_id_conflicts': - return $this->buildIdConflictForm($form, $form_state); - - case 'confirm': - return $this->buildConfirmForm($form, $form_state); - - default: - $this->messenger->addError($this->t('Unrecognized form step @step', ['@step' => $this->step])); - return []; - } - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - // This method is intentionally empty, see the specific submit methods for - // each form step. - } - - /** - * Builds the form presenting an overview of the migration process. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * - * @return array - * The form structure. - */ - public function buildOverviewForm(array $form, FormStateInterface $form_state) { - $form['#title'] = $this->t('Upgrade'); - - if ($date_performed = $this->state->get('migrate_drupal_ui.performed')) { - // @todo Add back support for rollbacks. - // https://www.drupal.org/node/2687849 - $form['upgrade_option_item'] = [ - '#type' => 'item', - '#prefix' => $this->t('An upgrade has already been performed on this site. To perform a new migration, create a clean and empty new install of Drupal 8. Rollbacks are not yet supported through the user interface. For more information, see the upgrading handbook.', [':url' => 'https://www.drupal.org/upgrade/migrate']), - '#description' => $this->t('Last upgrade: @date', ['@date' => $this->dateFormatter->format($date_performed)]), - ]; - $form['actions']['incremental'] = [ - '#type' => 'submit', - '#value' => $this->t('Import new configuration and content from old site'), - '#button_type' => 'primary', - '#validate' => ['::validateIncrementalForm'], - '#submit' => ['::submitIncrementalForm'], - ]; - return $form; - } - else { - $form['info_header'] = [ - '#markup' => '

' . $this->t('Upgrade a site by importing its files and the data from its database into a clean and empty new install of Drupal 8. See the Drupal site upgrades handbook for more information.', [ - ':url' => 'https://www.drupal.org/upgrade/migrate', - ]), - ]; - - $form['legend']['#markup'] = ''; - $form['legend']['#markup'] .= '

' . $this->t('Definitions') . '

'; - $form['legend']['#markup'] .= '
'; - $form['legend']['#markup'] .= '
' . $this->t('Old site') . '
'; - $form['legend']['#markup'] .= '
' . $this->t('The site you want to upgrade.') . '
'; - $form['legend']['#markup'] .= '
' . $this->t('New site') . '
'; - $form['legend']['#markup'] .= '
' . $this->t('This empty Drupal 8 installation you will import the old site to.') . '
'; - $form['legend']['#markup'] .= '
'; - - $info[] = $this->t('Make sure that access to the database for the old site is available from this new site.'); - $info[] = $this->t('If the old site has private files, a copy of its files directory must also be accessible on the host of this new site.'); - $info[] = $this->t('Enable all modules on this new site that are enabled on the old site. For example, if the old site uses the book module, then enable the book module on this new site so that the existing data can be imported to it.'); - $info[] = $this->t('Do not add any content to the new site before upgrading. Any existing content is likely to be overwritten by the upgrade process. See the upgrade preparation guide.', [ - ':url' => 'https://www.drupal.org/docs/8/upgrade/preparing-an-upgrade#dont_create_content', - ]); - $info[] = $this->t('Put this site into maintenance mode.', [ - ':url' => Url::fromRoute('system.site_maintenance_mode')->toString(TRUE)->getGeneratedUrl(), - ]); - - $form['info'] = [ - '#theme' => 'item_list', - '#title' => $this->t('Preparation steps'), - '#list_type' => 'ol', - '#items' => $info, - ]; - - $form['info_footer'] = [ - '#markup' => '

' . $this->t('The upgrade can take a long time. It is better to upgrade from a local copy of your site instead of directly from your live site.'), - ]; - - $validate = []; - } - - $form['actions'] = ['#type' => 'actions']; - $form['actions']['save'] = [ - '#type' => 'submit', - '#value' => $this->t('Continue'), - '#button_type' => 'primary', - '#validate' => $validate, - '#submit' => ['::submitOverviewForm'], - ]; - return $form; - } - - /** - * Form submission handler for the overview form. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - public function submitOverviewForm(array &$form, FormStateInterface $form_state) { - $form_state->set('step', 'credentials')->setRebuild(); - } - - /** - * Validation handler for the incremental overview form. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - public function validateIncrementalForm(array &$form, FormStateInterface $form_state) { - // Retrieve the database driver from state. - $database_state_key = $this->state->get('migrate.fallback_state_key', ''); - if ($database_state_key) { - try { - $database = $this->state->get($database_state_key, [])['database']; - if ($connection = $this->getConnection($database)) { - if ($version = $this->getLegacyDrupalVersion($connection)) { - $this->setupMigrations($database, $form_state); - $valid_legacy_database = TRUE; - } - } - } - catch (DatabaseExceptionWrapper $exception) { - // Hide DB exceptions and forward to the DB credentials form. In that - // form we can more properly display errors and accept new credentials. - } - } - if (empty($valid_legacy_database)) { - $form_state->setValue('step', 'credentials')->setRebuild(); - } - } - - /** - * Form submission handler for the incremental overview form. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - public function submitIncrementalForm(array &$form, FormStateInterface $form_state) { - $form_state->set('step', 'confirm_id_conflicts')->setRebuild(); - } - - /** - * Builds the database credential form and adds file location information. - * - * This is largely borrowed from \Drupal\Core\Installer\Form\SiteSettingsForm. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * - * @return array - * The form structure. - * - * @todo Private files directory not yet implemented, depends on - * https://www.drupal.org/node/2547125. - */ - public function buildCredentialForm(array $form, FormStateInterface $form_state) { - $form['#title'] = $this->t('Drupal Upgrade'); - - $drivers = $this->getDatabaseTypes(); - $drivers_keys = array_keys($drivers); - // @todo https://www.drupal.org/node/2678510 Because this is a multi-step - // form, the form is not rebuilt during submission. Ideally we would get - // the chosen driver from form input, if available, in order to use - // #limit_validation_errors in the same way - // \Drupal\Core\Installer\Form\SiteSettingsForm does. - $default_driver = current($drivers_keys); - - $default_options = []; - - $form['version'] = [ - '#type' => 'radios', - '#default_value' => 7, - '#title' => $this->t('Drupal version of the source site'), - '#options' => ['6' => $this->t('Drupal 6'), '7' => $this->t('Drupal 7')], - '#required' => TRUE, - ]; - - $form['database'] = [ - '#type' => 'details', - '#title' => $this->t('Source database'), - '#description' => $this->t('Provide credentials for the database of the Drupal site you want to upgrade.'), - '#open' => TRUE, - ]; - - $form['database']['driver'] = [ - '#type' => 'radios', - '#title' => $this->t('Database type'), - '#required' => TRUE, - '#default_value' => $default_driver, - ]; - if (count($drivers) == 1) { - $form['database']['driver']['#disabled'] = TRUE; - } - - // Add driver-specific configuration options. - foreach ($drivers as $key => $driver) { - $form['database']['driver']['#options'][$key] = $driver->name(); - - $form['database']['settings'][$key] = $driver->getFormOptions($default_options); - // @todo https://www.drupal.org/node/2678510 Using - // #limit_validation_errors in the submit does not work so it is not - // possible to require the database and username for mysql and pgsql. - // This is because this is a multi-step form. - $form['database']['settings'][$key]['database']['#required'] = FALSE; - $form['database']['settings'][$key]['username']['#required'] = FALSE; - $form['database']['settings'][$key]['#prefix'] = '

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

'; - $form['database']['settings'][$key]['#type'] = 'container'; - $form['database']['settings'][$key]['#tree'] = TRUE; - $form['database']['settings'][$key]['advanced_options']['#parents'] = [$key]; - $form['database']['settings'][$key]['#states'] = [ - 'visible' => [ - ':input[name=driver]' => ['value' => $key], - ], - ]; - - // Move the host fields out of advanced settings. - if (isset($form['database']['settings'][$key]['advanced_options']['host'])) { - $form['database']['settings'][$key]['host'] = $form['database']['settings'][$key]['advanced_options']['host']; - $form['database']['settings'][$key]['host']['#title'] = 'Database host'; - $form['database']['settings'][$key]['host']['#weight'] = -1; - unset($form['database']['settings'][$key]['database']['#default_value']); - unset($form['database']['settings'][$key]['advanced_options']['host']); - } - } - - $form['source'] = [ - '#type' => 'details', - '#title' => $this->t('Source files'), - '#open' => TRUE, - ]; - $form['source']['d6_source_base_path'] = [ - '#type' => 'textfield', - '#title' => $this->t('Files directory'), - '#description' => $this->t('To import files from your current Drupal site, enter a local file directory containing your site (e.g. /var/www/docroot), or your site address (for example http://example.com).'), - '#states' => [ - 'visible' => [ - ':input[name="version"]' => ['value' => '6'], - ], - ], - ]; - - $form['source']['source_base_path'] = [ - '#type' => 'textfield', - '#title' => $this->t('Public files directory'), - '#description' => $this->t('To import public files from your current Drupal site, enter a local file directory containing your site (e.g. /var/www/docroot), or your site address (for example http://example.com).'), - '#states' => [ - 'visible' => [ - ':input[name="version"]' => ['value' => '7'], - ], - ], - ]; - - $form['source']['source_private_file_path'] = [ - '#type' => 'textfield', - '#title' => $this->t('Private file directory'), - '#default_value' => '', - '#description' => $this->t('To import private files from your current Drupal site, enter a local file directory containing your site (e.g. /var/www/docroot).'), - '#states' => [ - 'visible' => [ - ':input[name="version"]' => ['value' => '7'], - ], - ], - ]; - - $form['actions'] = ['#type' => 'actions']; - $form['actions']['save'] = [ - '#type' => 'submit', - '#value' => $this->t('Review upgrade'), - '#button_type' => 'primary', - '#validate' => ['::validateCredentialForm'], - '#submit' => ['::submitCredentialForm'], - ]; - return $form; - } - - /** - * Validation handler for the credentials form. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - public function validateCredentialForm(array &$form, FormStateInterface $form_state) { - - // Retrieve the database driver from the form, use reflection to get the - // namespace, and then construct a valid database array the same as in - // settings.php. - $driver = $form_state->getValue('driver'); - $drivers = $this->getDatabaseTypes(); - $reflection = new \ReflectionClass($drivers[$driver]); - $install_namespace = $reflection->getNamespaceName(); - - $database = $form_state->getValue($driver); - // Cut the trailing \Install from namespace. - $database['namespace'] = substr($install_namespace, 0, strrpos($install_namespace, '\\')); - $database['driver'] = $driver; - - // Validate the driver settings and just end here if we have any issues. - if ($errors = $drivers[$driver]->validateDatabaseSettings($database)) { - foreach ($errors as $name => $message) { - $form_state->setErrorByName($name, $message); - } - return; - } - - try { - $connection = $this->getConnection($database); - $version = (string) $this->getLegacyDrupalVersion($connection); - if (!$version) { - $form_state->setErrorByName($database['driver'] . '][0', $this->t('Source database does not contain a recognizable Drupal version.')); - } - elseif ($version !== (string) $form_state->getValue('version')) { - $form_state->setErrorByName($database['driver'] . '][0', $this->t('Source database is Drupal version @version but version @selected was selected.', [ - '@version' => $version, - '@selected' => $form_state->getValue('version'), - ])); - } - else { - $this->setupMigrations($database, $form_state); - } - } - catch (\Exception $e) { - $error_message = [ - '#title' => $this->t('Resolve the issue below to continue the upgrade.'), - '#theme' => 'item_list', - '#items' => [$e->getMessage()], - ]; - $form_state->setErrorByName($database['driver'] . '][0', $this->renderer->renderPlain($error_message)); - } - } - - /** - * Submission handler for the credentials form. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - public function submitCredentialForm(array &$form, FormStateInterface $form_state) { - // Indicate the next step is confirmation. - $form_state->set('step', 'confirm_id_conflicts'); - $form_state->setRebuild(); - } - - /** - * Confirmation form for ID conflicts. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * - * @return array - * The form structure. - */ - public function buildIdConflictForm(array &$form, FormStateInterface $form_state) { - // Check if there are conflicts. If none, just skip this form! - $migration_ids = array_keys($form_state->get('migrations')); - $migrations = $this->pluginManager->createInstances($migration_ids); - - $translated_content_conflicts = $content_conflicts = []; - - $results = (new IdAuditor())->auditMultiple($migrations); - - /** @var \Drupal\migrate\Audit\AuditResult $result */ - foreach ($results as $result) { - $destination = $result->getMigration()->getDestinationPlugin(); - if ($destination instanceof EntityContentBase && $destination->isTranslationDestination()) { - // Translations are not yet supperted by the audit system. For now, we - // only warn the user to be cautious when migrating translated content. - // I18n support should be added in https://www.drupal.org/node/2905759. - $translated_content_conflicts[] = $result; - } - elseif (!$result->passed()) { - $content_conflicts[] = $result; - } - - } - if (empty($content_conflicts) && empty($translated_content_conflicts)) { - $form_state->set('step', 'confirm'); - return $this->buildForm($form, $form_state); - } - - $this->messenger->addWarning($this->t('WARNING: Content may be overwritten on your new site.')); - - $form = parent::buildForm($form, $form_state); - $form['actions']['submit']['#submit'] = ['::submitConfirmIdConflictForm']; - $form['actions']['submit']['#value'] = $this->t('I acknowledge I may lose data. Continue anyway.'); - - if ($content_conflicts) { - $form = $this->conflictsForm($form, $form_state, $content_conflicts); - } - if ($translated_content_conflicts) { - $form = $this->i18nWarningForm($form, $form_state, $translated_content_conflicts); - } - return $form; - } - - /** - * Build the markup for conflict warnings. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * @param \Drupal\migrate\Audit\AuditResult[] $conflicts - * The failing audit results. - * - * @return array - * The form structure. - */ - protected function conflictsForm(array &$form, FormStateInterface $form_state, array $conflicts) { - $form['conflicts'] = [ - '#title' => $this->t('There is conflicting content of these types:'), - '#theme' => 'item_list', - '#items' => $this->formatConflicts($conflicts), - ]; - - $form['warning'] = [ - '#type' => 'markup', - '#markup' => '

' . $this->t('It looks like you have content on your new site which may be overwritten if you continue to run this upgrade. The upgrade should be performed on a clean Drupal 8 installation. For more information see the upgrade handbook.', [':id-conflicts-handbook' => 'https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#id_conflicts']) . '

', - ]; - - return $form; - } - - /** - * Formats a set of failing audit results as strings. - * - * Each string is the label of the destination plugin of the migration that - * failed the audit, keyed by the destination plugin ID in order to prevent - * duplication. - * - * @param \Drupal\migrate\Audit\AuditResult[] $conflicts - * The failing audit results. - * - * @return string[] - * The formatted audit results. - */ - protected function formatConflicts(array $conflicts) { - $items = []; - - foreach ($conflicts as $conflict) { - $definition = $conflict->getMigration()->getDestinationPlugin()->getPluginDefinition(); - $id = $definition['id']; - $items[$id] = $definition['label']; - } - sort($items, SORT_STRING); - - return $items; - } - - /** - * Build the markup for i18n warnings. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * @param \Drupal\migrate\Audit\AuditResult[] $conflicts - * The failing audit results. - * - * @return array - * The form structure. - */ - protected function i18nWarningForm(array &$form, FormStateInterface $form_state, array $conflicts) { - $form['i18n'] = [ - '#title' => $this->t('There is translated content of these types:'), - '#theme' => 'item_list', - '#items' => $this->formatConflicts($conflicts), - ]; - - $form['i18n_warning'] = [ - '#type' => 'markup', - '#markup' => '

' . $this->t('It looks like you are migrating translated content from your old site. Possible ID conflicts for translations are not automatically detected in the current version of Drupal. Refer to the upgrade handbook for instructions on how to avoid ID conflicts with translated content.', [':id-conflicts-handbook' => 'https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#id_conflicts']) . '

', - ]; - - return $form; - } - - /** - * Submission handler for the confirmation form. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - public function submitConfirmIdConflictForm(array &$form, FormStateInterface $form_state) { - $form_state->set('step', 'confirm'); - $form_state->setRebuild(); - } - - /** - * Confirmation form showing available and missing migration paths. - * - * The confirmation form uses the source_module and destination_module - * properties on the source, destination and field plugins as well as the - * system data from the source to determine if there is a migration path for - * each module in the source. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - * - * @return array - * The form structure. - */ - public function buildConfirmForm(array $form, FormStateInterface $form_state) { - $form = parent::buildForm($form, $form_state); - $form['actions']['submit']['#submit'] = ['::submitConfirmForm']; - - $form['actions']['submit']['#value'] = $this->t('Perform upgrade'); - - $version = $form_state->get('version'); - - // Get the source_module and destination_module for each migration. - $migrations = $this->getMigrations('migrate_drupal_' . $version, $version); - $table_data = []; - foreach ($migrations as $migration) { - $migration_id = $migration->getPluginId(); - $source_module = $migration->getSourcePlugin()->getSourceModule(); - if (!$source_module) { - $this->messenger->addError($this->t('Source module not found for @migration_id.', ['@migration_id' => $migration_id])); - } - $destination_module = $migration->getDestinationPlugin()->getDestinationModule(); - if (!$destination_module) { - $this->messenger->addError($this->t('Destination module not found for @migration_id.', ['@migration_id' => $migration_id])); - } - - if ($source_module && $destination_module) { - $table_data[$source_module][$destination_module][$migration_id] = $migration->label(); - } - } - - // Get the source_module and destination_module from the field plugins. - $definitions = $this->fieldPluginManager->getDefinitions(); - foreach ($definitions as $definition) { - // This is not strict so that we find field plugins with an annotation - // where the Drupal core version is an integer and when it is a string. - if (in_array($version, $definition['core'])) { - $source_module = $definition['source_module']; - $destination_module = $definition['destination_module']; - $table_data[$source_module][$destination_module][$definition['id']] = $definition['id']; - } - } - - // Fetch the system data at the first opportunity. - $system_data = $form_state->get('system_data'); - - // Add source_module and destination_module for modules that do not need an - // upgrade path and are enabled on the source site. - foreach ($this->noUpgradePaths[$version] as $extension) { - if ($system_data['module'][$extension]['status']) { - $table_data[$extension]['core'][$extension] = $extension; - } - } - - // Sort the table by source module names and within that destination - // module names. - ksort($table_data); - foreach ($table_data as $source_module => $destination_module_info) { - ksort($table_data[$source_module]); - } - - // Remove core profiles from the system data. - foreach (['standard', 'minimal'] as $profile) { - unset($system_data['module'][$profile]); - } - - $unmigrated_source_modules = array_diff_key($system_data['module'], $table_data); - - // Missing migrations. - $missing_module_list = [ - '#type' => 'details', - '#open' => TRUE, - '#title' => [ - '#type' => 'html_tag', - '#tag' => 'span', - '#value' => $this->t('Modules that will not be upgraded'), - '#attributes' => ['id' => ['error']], - ], - '#description' => $this->t('There are no modules installed on your new site to replace these modules. If you proceed with the upgrade now, configuration and/or content needed by these modules will not be available on your new site. For more information, see Review the pre-upgrade analysis in the Upgrading to Drupal 8 handbook.', [':review' => 'https://www.drupal.org/docs/8/upgrade/upgrade-using-web-browser#pre-upgrade-analysis', ':migrate' => 'https://www.drupal.org/docs/8/upgrade']), - '#weight' => 2, - ]; - $missing_module_list['module_list'] = [ - '#type' => 'table', - '#header' => [ - $this->t('Drupal @version', ['@version' => $version]), - $this->t('Drupal 8'), - ], - ]; - $missing_count = 0; - ksort($unmigrated_source_modules); - foreach ($unmigrated_source_modules as $source_module => $module_data) { - if ($module_data['status']) { - $missing_count++; - $missing_module_list['module_list'][$source_module] = [ - 'source_module' => [ - '#type' => 'html_tag', - '#tag' => 'span', - '#value' => $source_module, - '#attributes' => [ - 'class' => [ - 'upgrade-analysis-report__status-icon', - 'upgrade-analysis-report__status-icon--error', - ], - ], - ], - 'destination_module' => ['#plain_text' => 'Not upgraded'], - ]; - } - } - - // Available migrations. - $available_module_list = [ - '#type' => 'details', - '#title' => [ - '#type' => 'html_tag', - '#tag' => 'span', - '#value' => $this->t('Modules that will be upgraded'), - '#attributes' => ['id' => ['checked']], - ], - '#weight' => 3, - ]; - - $available_module_list['module_list'] = [ - '#type' => 'table', - '#header' => [ - $this->t('Drupal @version', ['@version' => $version]), - $this->t('Drupal 8'), - ], - ]; - - $available_count = 0; - foreach ($table_data as $source_module => $destination_module_info) { - $available_count++; - $destination_details = []; - foreach ($destination_module_info as $destination_module => $migration_ids) { - $destination_details[$destination_module] = [ - '#type' => 'item', - '#plain_text' => $destination_module, - ]; - } - $available_module_list['module_list'][$source_module] = [ - 'source_module' => [ - '#type' => 'html_tag', - '#tag' => 'span', - '#value' => $source_module, - '#attributes' => [ - 'class' => [ - 'upgrade-analysis-report__status-icon', - 'upgrade-analysis-report__status-icon--checked', - ], - ], - ], - 'destination_module' => $destination_details, - ]; - } - - $counters = []; - $general_info = []; - - if ($missing_count) { - $counters[] = [ - '#theme' => 'status_report_counter', - '#amount' => $missing_count, - '#text' => $this->formatPlural($missing_count, 'Module will not be upgraded', 'Modules will not be upgraded'), - '#severity' => 'error', - '#weight' => 0, - ]; - $general_info[] = $missing_module_list; - } - if ($available_count) { - $counters[] = [ - '#theme' => 'status_report_counter', - '#amount' => $available_count, - '#text' => $this->formatPlural($available_count, 'Module will be upgraded', 'Modules will be upgraded'), - '#severity' => 'checked', - '#weight' => 1, - ]; - $general_info[] = $available_module_list; - } - - $form['status_report_page'] = [ - '#theme' => 'status_report_page', - '#counters' => $counters, - '#general_info' => $general_info, - ]; - - $form['#attached']['library'][] = 'migrate_drupal_ui/base'; - - return $form; - } - - /** - * Submission handler for the confirmation form. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - public function submitConfirmForm(array &$form, FormStateInterface $form_state) { - $storage = $form_state->getStorage(); - - $migrations = $storage['migrations']; - $config['source_base_path'] = $storage['source_base_path']; - $batch = [ - 'title' => $this->t('Running upgrade'), - 'progress_message' => '', - 'operations' => [ - [ - [MigrateUpgradeImportBatch::class, 'run'], - [array_keys($migrations), $config], - ], - ], - 'finished' => [ - MigrateUpgradeImportBatch::class, 'finished', - ], - ]; - batch_set($batch); - $form_state->setRedirect(''); - $this->state->set('migrate_drupal_ui.performed', REQUEST_TIME); - } - - /** - * Returns all supported database driver installer objects. - * - * @return \Drupal\Core\Database\Install\Tasks[] - * An array of available database driver installer objects. - */ - protected function getDatabaseTypes() { - // Make sure the install API is available. - include_once DRUPAL_ROOT . '/core/includes/install.inc'; - return drupal_get_database_types(); - } - - /** - * Puts migrations information in form state. - * - * Gets all the migrations, converts each to an array and stores it in the - * form state. The source base path for public and private files is also - * put into form state. - * - * @param array $database - * Database array representing the source Drupal database. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - protected function setupMigrations(array $database, FormStateInterface $form_state) { - $connection = $this->getConnection($database); - $version = $this->getLegacyDrupalVersion($connection); - $this->createDatabaseStateSettings($database, $version); - $migrations = $this->getMigrations('migrate_drupal_' . $version, $version); - - // Get the system data from source database. - $system_data = $this->getSystemData($connection); - - // Convert the migration object into array - // so that it can be stored in form storage. - $migration_array = []; - foreach ($migrations as $migration) { - $migration_array[$migration->id()] = $migration->label(); - } - - // Store the retrieved migration IDs in form storage. - $form_state->set('version', $version); - $form_state->set('migrations', $migration_array); - if ($version == 6) { - $form_state->set('source_base_path', $form_state->getValue('d6_source_base_path')); - } - else { - $form_state->set('source_base_path', $form_state->getValue('source_base_path')); - } - $form_state->set('source_private_file_path', $form_state->getValue('source_private_file_path')); - // Store the retrieved system data in form storage. - $form_state->set('system_data', $system_data); - } - - /** - * {@inheritdoc} - */ - public function getQuestion() { - if ($this->step === 'confirm_id_conflicts') { - return $this->t('Upgrade analysis report'); - } - return $this->t('What will be upgraded?'); - } - - /** - * {@inheritdoc} - */ - public function getCancelUrl() { - return new Url('migrate_drupal_ui.upgrade'); - } - - /** - * {@inheritdoc} - */ - public function getDescription() { - // The description is added by the buildConfirmForm() method. - // @see \Drupal\migrate_drupal_ui\Form\MigrateUpgradeForm::buildConfirmForm() - return; - } - - /** - * {@inheritdoc} - */ - public function getConfirmText() { - return $this->t('Perform upgrade'); - } - -} diff --git a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeFormBase.php b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeFormBase.php new file mode 100644 index 00000000000..a54dba8c942 --- /dev/null +++ b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeFormBase.php @@ -0,0 +1,109 @@ +store = $tempstore_private->get('migrate_drupal_ui'); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('tempstore.private') + ); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = []; + $form['actions']['#type'] = 'actions'; + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->getConfirmText(), + '#button_type' => 'primary', + '#weight' => 10, + ]; + return $form; + } + + /** + * Puts migrations information in form state. + * + * Gets all the migrations, converts each to an array and stores it in the + * form state. The source base path for public and private files is also + * put into form state. + * + * @param array $database + * Database array representing the source Drupal database. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + */ + protected function setupMigrations(array $database, FormStateInterface $form_state) { + $connection = $this->getConnection($database); + $version = $this->getLegacyDrupalVersion($connection); + $this->createDatabaseStateSettings($database, $version); + $migrations = $this->getMigrations('migrate_drupal_' . $version, $version); + + // Get the system data from source database. + $system_data = $this->getSystemData($connection); + + // Convert the migration object into array + // so that it can be stored in form storage. + $migration_array = []; + foreach ($migrations as $migration) { + $migration_array[$migration->id()] = $migration->label(); + } + + // Store information in the private store. + $this->store->set('version', $version); + $this->store->set('migrations', $migration_array); + if ($version == 6) { + $this->store->set('source_base_path', $form_state->getValue('d6_source_base_path')); + } + else { + $this->store->set('source_base_path', $form_state->getValue('source_base_path')); + } + $this->store->set('source_private_file_path', $form_state->getValue('source_private_file_path')); + // Store the retrieved system data in the private store. + $this->store->set('system_data', $system_data); + } + + /** + * Returns a caption for the button that confirms the action. + * + * @return string + * The form confirmation text. + */ + abstract protected function getConfirmText(); + +} diff --git a/core/modules/migrate_drupal_ui/src/Form/OverviewForm.php b/core/modules/migrate_drupal_ui/src/Form/OverviewForm.php new file mode 100644 index 00000000000..d8d28e0b22e --- /dev/null +++ b/core/modules/migrate_drupal_ui/src/Form/OverviewForm.php @@ -0,0 +1,92 @@ +store->get('performed')) { + $this->store->set('step', 'incremental'); + return $this->redirect('migrate_drupal_ui.upgrade_incremental'); + } + + $form['#title'] = $this->t('Upgrade'); + + $form['info_header'] = [ + '#markup' => '

' . $this->t('Upgrade a site by importing its files and the data from its database into a clean and empty new install of Drupal 8. See the Drupal site upgrades handbook for more information.', [ + ':url' => 'https://www.drupal.org/upgrade/migrate', + ]), + ]; + + $form['legend']['#markup'] = ''; + $form['legend']['#markup'] .= '

' . $this->t('Definitions') . '

'; + $form['legend']['#markup'] .= '
'; + $form['legend']['#markup'] .= '
' . $this->t('Old site') . '
'; + $form['legend']['#markup'] .= '
' . $this->t('The site you want to upgrade.') . '
'; + $form['legend']['#markup'] .= '
' . $this->t('New site') . '
'; + $form['legend']['#markup'] .= '
' . $this->t('This empty Drupal 8 installation you will import the old site to.') . '
'; + $form['legend']['#markup'] .= '
'; + + $info[] = $this->t('Make sure that access to the database for the old site is available from this new site.'); + $info[] = $this->t('If the old site has private files, a copy of its files directory must also be accessible on the host of this new site.'); + $info[] = $this->t('Enable all modules on this new site that are enabled on the old site. For example, if the old site uses the book module, then enable the book module on this new site so that the existing data can be imported to it.'); + $info[] = $this->t('Do not add any content to the new site before upgrading. Any existing content is likely to be overwritten by the upgrade process. See the upgrade preparation guide.', [ + ':url' => 'https://www.drupal.org/docs/8/upgrade/preparing-an-upgrade#dont_create_content', + ]); + $info[] = $this->t('Put this site into maintenance mode.', [ + ':url' => Url::fromRoute('system.site_maintenance_mode') + ->toString(TRUE) + ->getGeneratedUrl(), + ]); + + $form['info'] = [ + '#theme' => 'item_list', + '#title' => $this->t('Preparation steps'), + '#list_type' => 'ol', + '#items' => $info, + ]; + + $form['info_footer'] = [ + '#markup' => '

' . $this->t('The upgrade can take a long time. It is better to upgrade from a local copy of your site instead of directly from your live site.'), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->store->set('step', 'credential'); + $form_state->setRedirect('migrate_drupal_ui.upgrade_credential'); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Continue'); + } + +} diff --git a/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php b/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php new file mode 100644 index 00000000000..d6c82206332 --- /dev/null +++ b/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php @@ -0,0 +1,380 @@ + [ + 'blog', + 'blogapi', + 'calendarsignup', + 'color', + 'content_copy', + 'content_multigroup', + 'content_permissions', + 'date_api', + 'date_locale', + 'date_php4', + 'date_popup', + 'date_repeat', + 'date_timezone', + 'date_tools', + 'datepicker', + 'ddblock', + 'event', + 'fieldgroup', + 'filefield_meta', + 'help', + 'i18n', + 'i18nstrings', + 'imageapi', + 'imageapi_gd', + 'imageapi_imagemagick', + 'imagecache_ui', + 'jquery_ui', + 'nodeaccess', + 'number', + 'openid', + 'php', + 'ping', + 'poll', + 'throttle', + 'tracker', + 'translation', + 'trigger', + 'variable', + 'variable_admin', + 'views_export', + 'views_ui', + ], + '7' => [ + 'blog', + 'bulk_export', + 'contextual', + 'ctools', + 'ctools_access_ruleset', + 'ctools_ajax_sample', + 'ctools_custom_content', + 'dashboard', + 'date_all_day', + 'date_api', + 'date_context', + 'date_migrate', + 'date_popup', + 'date_repeat', + 'date_repeat_field', + 'date_tools', + 'date_views', + 'entity', + 'entity_feature', + 'entity_token', + 'entityreference', + 'field_ui', + 'help', + 'openid', + 'overlay', + 'page_manager', + 'php', + 'poll', + 'search_embedded_form', + 'search_extra_type', + 'search_node_tags', + 'simpletest', + 'stylizer', + 'term_depth', + 'toolbar', + 'translation', + 'trigger', + 'views_content', + 'views_ui', + ], + ]; + + /** + * ReviewForm constructor. + * + * @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $field_plugin_manager + * The field plugin manager service. + * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempstore_private + * The private tempstore factory. + */ + public function __construct(MigrateFieldPluginManagerInterface $field_plugin_manager, PrivateTempStoreFactory $tempstore_private) { + parent::__construct($tempstore_private); + $this->fieldPluginManager = $field_plugin_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.migrate.field'), + $container->get('tempstore.private') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'migrate_drupal_ui_review_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + if ($this->store->get('step') != 'review') { + $this->store->set('step', 'overview'); + return $this->redirect('migrate_drupal_ui.upgrade'); + } + + $form = parent::buildForm($form, $form_state); + $form['#title'] = $this->t('What will be upgraded?'); + + $version = $this->store->get('version'); + // Get the source_module and destination_module for each migration. + $migrations = $this->getMigrations('migrate_drupal_' . $version, $version); + $table_data = []; + foreach ($migrations as $migration) { + $migration_id = $migration->getPluginId(); + $source_module = $migration->getSourcePlugin()->getSourceModule(); + if (!$source_module) { + $this->messenger()->addError($this->t('Source module not found for @migration_id.', ['@migration_id' => $migration_id])); + } + $destination_module = $migration->getDestinationPlugin()->getDestinationModule(); + if (!$destination_module) { + $this->messenger()->addError($this->t('Destination module not found for @migration_id.', ['@migration_id' => $migration_id])); + } + + if ($source_module && $destination_module) { + $table_data[$source_module][$destination_module][$migration_id] = $migration->label(); + } + } + + // Get the source_module and destination_module from the field plugins. + $definitions = $this->fieldPluginManager->getDefinitions(); + foreach ($definitions as $definition) { + // This is not strict so that we find field plugins with an annotation + // where the Drupal core version is an integer and when it is a string. + if (in_array($version, $definition['core'])) { + $source_module = $definition['source_module']; + $destination_module = $definition['destination_module']; + $table_data[$source_module][$destination_module][$definition['id']] = $definition['id']; + } + } + + // Fetch the system data at the first opportunity. + $system_data = $this->store->get('system_data'); + + // Add source_module and destination_module for modules that do not need an + // upgrade path and are enabled on the source site. + foreach ($this->noUpgradePaths[$version] as $extension) { + if ($system_data['module'][$extension]['status']) { + $table_data[$extension]['core'][$extension] = $extension; + } + } + + // Sort the table by source module names and within that destination + // module names. + ksort($table_data); + foreach ($table_data as $source_module => $destination_module_info) { + ksort($table_data[$source_module]); + } + + // Remove core profiles from the system data. + foreach (['standard', 'minimal'] as $profile) { + unset($system_data['module'][$profile]); + } + + $unmigrated_source_modules = array_diff_key($system_data['module'], $table_data); + + // Missing migrations. + $missing_module_list = [ + '#type' => 'details', + '#open' => TRUE, + '#title' => [ + '#type' => 'html_tag', + '#tag' => 'span', + '#value' => $this->t('Modules that will not be upgraded'), + '#attributes' => ['id' => ['error']], + ], + '#description' => $this->t('There are no modules installed on your new site to replace these modules. If you proceed with the upgrade now, configuration and/or content needed by these modules will not be available on your new site. For more information, see Review the pre-upgrade analysis in the Upgrading to Drupal 8 handbook.', [':review' => 'https://www.drupal.org/docs/8/upgrade/upgrade-using-web-browser#pre-upgrade-analysis', ':migrate' => 'https://www.drupal.org/docs/8/upgrade']), + '#weight' => 2, + ]; + $missing_module_list['module_list'] = [ + '#type' => 'table', + '#header' => [ + $this->t('Drupal @version', ['@version' => $version]), + $this->t('Drupal 8'), + ], + ]; + $missing_count = 0; + ksort($unmigrated_source_modules); + foreach ($unmigrated_source_modules as $source_module => $module_data) { + if ($module_data['status']) { + $missing_count++; + $missing_module_list['module_list'][$source_module] = [ + 'source_module' => [ + '#type' => 'html_tag', + '#tag' => 'span', + '#value' => $source_module, + '#attributes' => [ + 'class' => [ + 'upgrade-analysis-report__status-icon', + 'upgrade-analysis-report__status-icon--error', + ], + ], + ], + 'destination_module' => ['#plain_text' => 'Not upgraded'], + ]; + } + } + + // Available migrations. + $available_module_list = [ + '#type' => 'details', + '#title' => [ + '#type' => 'html_tag', + '#tag' => 'span', + '#value' => $this->t('Modules that will be upgraded'), + '#attributes' => ['id' => ['checked']], + ], + '#weight' => 3, + ]; + + $available_module_list['module_list'] = [ + '#type' => 'table', + '#header' => [ + $this->t('Drupal @version', ['@version' => $version]), + $this->t('Drupal 8'), + ], + ]; + + $available_count = 0; + foreach ($table_data as $source_module => $destination_module_info) { + $available_count++; + $destination_details = []; + foreach ($destination_module_info as $destination_module => $migration_ids) { + $destination_details[$destination_module] = [ + '#type' => 'item', + '#plain_text' => $destination_module, + ]; + } + $available_module_list['module_list'][$source_module] = [ + 'source_module' => [ + '#type' => 'html_tag', + '#tag' => 'span', + '#value' => $source_module, + '#attributes' => [ + 'class' => [ + 'upgrade-analysis-report__status-icon', + 'upgrade-analysis-report__status-icon--checked', + ], + ], + ], + 'destination_module' => $destination_details, + ]; + } + + $counters = []; + $general_info = []; + + if ($missing_count) { + $counters[] = [ + '#theme' => 'status_report_counter', + '#amount' => $missing_count, + '#text' => $this->formatPlural($missing_count, 'Module will not be upgraded', 'Modules will not be upgraded'), + '#severity' => 'error', + '#weight' => 0, + ]; + $general_info[] = $missing_module_list; + } + if ($available_count) { + $counters[] = [ + '#theme' => 'status_report_counter', + '#amount' => $available_count, + '#text' => $this->formatPlural($available_count, 'Module will be upgraded', 'Modules will be upgraded'), + '#severity' => 'checked', + '#weight' => 1, + ]; + $general_info[] = $available_module_list; + } + + $form['status_report_page'] = [ + '#theme' => 'status_report_page', + '#counters' => $counters, + '#general_info' => $general_info, + ]; + + $form['#attached']['library'][] = 'migrate_drupal_ui/base'; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $migrations = $this->store->get('migrations'); + $config['source_base_path'] = $this->store->get('source_base_path'); + $batch = [ + 'title' => $this->t('Running upgrade'), + 'progress_message' => '', + 'operations' => [ + [ + [MigrateUpgradeImportBatch::class, 'run'], + [array_keys($migrations), $config], + ], + ], + 'finished' => [ + MigrateUpgradeImportBatch::class, 'finished', + ], + ]; + batch_set($batch); + $form_state->setRedirect(''); + $this->store->set('step', 'overview'); + $this->store->set('performed', REQUEST_TIME); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Perform upgrade'); + } + +}