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 d02ea98dbd1edd9d5e48396fc3bb6a653c9814c9..438c8aabc57eaf9ff4971260de2dcca4e754e0e6 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_idconflict: + 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 0000000000000000000000000000000000000000..cd767cc3d1d73c56fca61f07b650f5d97a09a983 --- /dev/null +++ b/core/modules/migrate_drupal_ui/src/Form/CredentialForm.php @@ -0,0 +1,256 @@ +<?php + +namespace Drupal\migrate_drupal_ui\Form; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\RendererInterface; +use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Migrate Upgrade database credential form. + * + * @internal + */ +class CredentialForm extends MigrateUpgradeFormBase { + + /** + * The renderer service. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + + /** + * CredentialForm constructor. + * + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer service. + * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempstore_private + * The private tempstore factory. + */ + public function __construct(RendererInterface $renderer, PrivateTempStoreFactory $tempstore_private) { + parent::__construct($tempstore_private); + $this->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') { + return $this->restartUpgradeForm(); + } + + $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'] = '<h2 class="js-hide">' . $this->t('@driver_name settings', ['@driver_name' => $driver->name()]) . '</h2>'; + $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', 'idconflict'); + $form_state->setRedirect('migrate_drupal_ui.upgrade_idconflict'); + } + + /** + * {@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 0000000000000000000000000000000000000000..2492143abc29b4716e4c4f5c2008e0a0681bedc6 --- /dev/null +++ b/core/modules/migrate_drupal_ui/src/Form/IdConflictForm.php @@ -0,0 +1,203 @@ +<?php + +namespace Drupal\migrate_drupal_ui\Form; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Drupal\migrate\Audit\IdAuditor; +use Drupal\migrate\Plugin\migrate\destination\EntityContentBase; +use Drupal\migrate\Plugin\MigrationPluginManagerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Migrate Upgrade Id Conflict form. + * + * @internal + */ +class IdConflictForm extends MigrateUpgradeFormBase { + + /** + * The migration plugin manager service. + * + * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface + */ + protected $pluginManager; + + /** + * IdConflictForm constructor. + * + * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager + * The migration plugin manager service. + * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempstore_private + * The private tempstore factory. + */ + public function __construct(MigrationPluginManagerInterface $migration_plugin_manager, PrivateTempStoreFactory $tempstore_private) { + parent::__construct($tempstore_private); + $this->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_idconflict_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + // Get all the data needed for this form. + $migrations = $this->store->get('migrations'); + + // If data is missing or this is the wrong step, start over. + if (!$migrations || ($this->store->get('step') != 'idconflict')) { + return $this->restartUpgradeForm(); + } + + $migration_ids = array_keys($migrations); + // Check if there are conflicts. If none, just skip this form! + $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' => '<p>' . $this->t('It looks like you have content on your new site which <strong>may be overwritten</strong> if you continue to run this upgrade. The upgrade should be performed on a clean Drupal 8 installation. For more information see the <a target="_blank" href=":id-conflicts-handbook">upgrade handbook</a>.', [':id-conflicts-handbook' => 'https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#id_conflicts']) . '</p>', + ]; + + 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' => '<p>' . $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 <a target="_blank" href=":id-conflicts-handbook">upgrade handbook</a> 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']) . '</p>', + ]; + + 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 0000000000000000000000000000000000000000..8090190e1f2a2ea7dccf5e96ba00679c9369a389 --- /dev/null +++ b/core/modules/migrate_drupal_ui/src/Form/IncrementalForm.php @@ -0,0 +1,130 @@ +<?php + +namespace Drupal\migrate_drupal_ui\Form; + +use Drupal\Core\Database\DatabaseExceptionWrapper; +use Drupal\Core\Datetime\DateFormatterInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\State\StateInterface; +use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Migrate Upgrade Incremental form. + * + * @internal + */ +class IncrementalForm extends MigrateUpgradeFormBase { + + /** + * The state service. + * + * @var \Drupal\Core\State\StateInterface + */ + protected $state; + + /** + * The date formatter service. + * + * @var \Drupal\Core\Datetime\DateFormatterInterface + */ + protected $dateFormatter; + + /** + * IncrementalForm constructor. + * + * @param \Drupal\Core\State\StateInterface $state + * The state service. + * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter + * The date formatter service. + * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempstore_private + * The private temp store factory. + */ + public function __construct(StateInterface $state, DateFormatterInterface $date_formatter, PrivateTempStoreFactory $tempstore_private) { + parent::__construct($tempstore_private); + $this->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) { + // Get all the data needed for this form. + $date_performed = $this->state->get('migrate_drupal_ui.performed'); + + // If data is missing or this is the wrong step, start over. + if (!$date_performed || $this->store->get('step') != 'incremental') { + return $this->restartUpgradeForm(); + } + + $form = parent::buildForm($form, $form_state); + $form['#title'] = $this->t('Upgrade'); + + // @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 <a href=":url">upgrading handbook</a>.', [':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. + } + } + } + + /** + * {@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('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 6ff17f2c5557da9b420af1aeef55cd503ae44b86..0000000000000000000000000000000000000000 --- a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php +++ /dev/null @@ -1,1077 +0,0 @@ -<?php - -namespace Drupal\migrate_drupal_ui\Form; - -use Drupal\Core\Database\DatabaseExceptionWrapper; -use Drupal\Core\Datetime\DateFormatterInterface; -use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\Core\Form\ConfirmFormBase; -use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Messenger\MessengerInterface; -use Drupal\Core\Render\RendererInterface; -use Drupal\Core\State\StateInterface; -use Drupal\Core\Url; -use Drupal\migrate\Audit\IdAuditor; -use Drupal\migrate\Plugin\migrate\destination\EntityContentBase; -use Drupal\migrate\Plugin\MigrationPluginManagerInterface; -use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface; -use Drupal\migrate_drupal_ui\Batch\MigrateUpgradeImportBatch; -use Drupal\migrate_drupal\MigrationConfigurationTrait; -use Symfony\Component\DependencyInjection\ContainerInterface; - -/** - * Defines a multi-step form for performing direct site upgrades. - * - * @internal - */ -class MigrateUpgradeForm extends ConfirmFormBase { - - use MigrationConfigurationTrait; - - /** - * The current form step. - * - * @var string - */ - protected $step; - - /** - * The state service. - * - * @var \Drupal\Core\State\StateInterface - */ - protected $state; - - /** - * The date formatter service. - * - * @var \Drupal\Core\Datetime\DateFormatterInterface - */ - protected $dateFormatter; - - /** - * The renderer service. - * - * @var \Drupal\Core\Render\RendererInterface - */ - protected $renderer; - - /** - * The migration plugin manager. - * - * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface - */ - protected $pluginManager; - - /** - * The field plugin manager. - * - * @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface - */ - protected $fieldPluginManager; - - /** - * The module handler. - * - * @var \Drupal\Core\Extension\ModuleHandlerInterface - */ - protected $moduleHandler; - - /** - * The messenger service. - * - * @var \Drupal\Core\Messenger\MessengerInterface - */ - protected $messenger; - - /** - * List of extensions that do not need an upgrade path. - * - * This property is an array where the keys are the major Drupal core version - * from which we are upgrading, and the values are arrays of extension names - * that do not need an upgrade path. - * - * @var array[] - */ - protected $noUpgradePaths = [ - '6' => [ - '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 <a href=":url">upgrading handbook</a>.', [':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' => '<p>' . $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 <a href=":url">Drupal site upgrades handbook</a> for more information.', [ - ':url' => 'https://www.drupal.org/upgrade/migrate', - ]), - ]; - - $form['legend']['#markup'] = ''; - $form['legend']['#markup'] .= '<h3>' . $this->t('Definitions') . '</h3>'; - $form['legend']['#markup'] .= '<dl>'; - $form['legend']['#markup'] .= '<dt>' . $this->t('Old site') . '</dt>'; - $form['legend']['#markup'] .= '<dd>' . $this->t('The site you want to upgrade.') . '</dd>'; - $form['legend']['#markup'] .= '<dt>' . $this->t('New site') . '</dt>'; - $form['legend']['#markup'] .= '<dd>' . $this->t('This empty Drupal 8 installation you will import the old site to.') . '</dd>'; - $form['legend']['#markup'] .= '</dl>'; - - $info[] = $this->t('Make sure that <strong>access to the database</strong> for the old site is available from this new site.'); - $info[] = $this->t('<strong>If the old site has private files</strong>, a copy of its files directory must also be accessible on the host of this new site.'); - $info[] = $this->t('<strong>Enable all modules on this new site</strong> 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('<strong>Do not add any content to the new site</strong> before upgrading. Any existing content is likely to be overwritten by the upgrade process. See <a href=":url">the upgrade preparation guide</a>.', [ - ':url' => 'https://www.drupal.org/docs/8/upgrade/preparing-an-upgrade#dont_create_content', - ]); - $info[] = $this->t('Put this site into <a href=":url">maintenance mode</a>.', [ - ':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' => '<p>' . $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'] = '<h2 class="js-hide">' . $this->t('@driver_name settings', ['@driver_name' => $driver->name()]) . '</h2>'; - $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' => '<p>' . $this->t('It looks like you have content on your new site which <strong>may be overwritten</strong> if you continue to run this upgrade. The upgrade should be performed on a clean Drupal 8 installation. For more information see the <a target="_blank" href=":id-conflicts-handbook">upgrade handbook</a>.', [':id-conflicts-handbook' => 'https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#id_conflicts']) . '</p>', - ]; - - 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' => '<p>' . $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 <a target="_blank" href=":id-conflicts-handbook">upgrade handbook</a> 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']) . '</p>', - ]; - - 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 <a href=":review">Review the pre-upgrade analysis</a> in the <a href=":migrate">Upgrading to Drupal 8</a> 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('<front>'); - $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 0000000000000000000000000000000000000000..ffe899254be5888043927f7d6dca6274b25e85a0 --- /dev/null +++ b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeFormBase.php @@ -0,0 +1,120 @@ +<?php + +namespace Drupal\migrate_drupal_ui\Form; + +use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\migrate_drupal\MigrationConfigurationTrait; +use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Form base for the Migrate Upgrade UI. + */ +abstract class MigrateUpgradeFormBase extends FormBase { + + use MigrationConfigurationTrait; + + /** + * Private temporary storage. + * + * @var \Drupal\Core\TempStore\PrivateTempStoreFactory + */ + protected $store; + + /** + * Constructs the Migrate Upgrade Form Base. + * + * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempstore_private + * Private store. + */ + public function __construct(PrivateTempStoreFactory $tempstore_private) { + $this->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; + } + + /** + * Gets and stores information for this migration in temporary store. + * + * 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); + } + + /** + * Helper to redirect to the Overview form. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + * A redirect response object that may be returned by the controller. + */ + protected function restartUpgradeForm() { + $this->store->set('step', 'overview'); + return $this->redirect('migrate_drupal_ui.upgrade'); + } + + /** + * 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 0000000000000000000000000000000000000000..22b45a9104381d8d3a4b3d4ae5e36f031665fd16 --- /dev/null +++ b/core/modules/migrate_drupal_ui/src/Form/OverviewForm.php @@ -0,0 +1,124 @@ +<?php + +namespace Drupal\migrate_drupal_ui\Form; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\State\StateInterface; +use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Drupal\Core\Url; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Migrate Upgrade Overview form. + * + * @internal + */ +class OverviewForm extends MigrateUpgradeFormBase { + + /** + * The state service. + * + * @var \Drupal\Core\State\StateInterface + */ + protected $state; + + /** + * Overview form constructor. + * + * @param \Drupal\Core\State\StateInterface $state + * The state service. + * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempstore_private + * The private tempstore factory. + */ + public function __construct(StateInterface $state, PrivateTempStoreFactory $tempstore_private) { + parent::__construct($tempstore_private); + $this->state = $state; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('state'), + $container->get('tempstore.private') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'migrate_drupal_ui_overview_form'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + // If an upgrade has already been performed, redirect to the incremental + // form. + if ($this->state->get('migrate_drupal_ui.performed')) { + $this->store->set('step', 'incremental'); + return $this->redirect('migrate_drupal_ui.upgrade_incremental'); + } + + $form = parent::buildForm($form, $form_state); + $form['#title'] = $this->t('Upgrade'); + + $form['info_header'] = [ + '#markup' => '<p>' . $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 <a href=":url">Drupal site upgrades handbook</a> for more information.', [ + ':url' => 'https://www.drupal.org/upgrade/migrate', + ]), + ]; + + $form['legend']['#markup'] = ''; + $form['legend']['#markup'] .= '<h3>' . $this->t('Definitions') . '</h3>'; + $form['legend']['#markup'] .= '<dl>'; + $form['legend']['#markup'] .= '<dt>' . $this->t('Old site') . '</dt>'; + $form['legend']['#markup'] .= '<dd>' . $this->t('The site you want to upgrade.') . '</dd>'; + $form['legend']['#markup'] .= '<dt>' . $this->t('New site') . '</dt>'; + $form['legend']['#markup'] .= '<dd>' . $this->t('This empty Drupal 8 installation you will import the old site to.') . '</dd>'; + $form['legend']['#markup'] .= '</dl>'; + + $info[] = $this->t('Make sure that <strong>access to the database</strong> for the old site is available from this new site.'); + $info[] = $this->t('<strong>If the old site has private files</strong>, a copy of its files directory must also be accessible on the host of this new site.'); + $info[] = $this->t('<strong>Enable all modules on this new site</strong> 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('<strong>Do not add any content to the new site</strong> before upgrading. Any existing content is likely to be overwritten by the upgrade process. See <a href=":url">the upgrade preparation guide</a>.', [ + ':url' => 'https://www.drupal.org/docs/8/upgrade/preparing-an-upgrade#dont_create_content', + ]); + $info[] = $this->t('Put this site into <a href=":url">maintenance mode</a>.', [ + ':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' => '<p>' . $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 0000000000000000000000000000000000000000..19dd60b3571beb1cb21ae703a7469e4387dd3387 --- /dev/null +++ b/core/modules/migrate_drupal_ui/src/Form/ReviewForm.php @@ -0,0 +1,413 @@ +<?php + +namespace Drupal\migrate_drupal_ui\Form; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\State\StateInterface; +use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface; +use Drupal\migrate\Plugin\MigrationPluginManagerInterface; +use Drupal\migrate_drupal_ui\Batch\MigrateUpgradeImportBatch; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Migrate Upgrade review form. + * + * This 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. + * + * @internal + */ +class ReviewForm extends MigrateUpgradeFormBase { + + /** + * The state service. + * + * @var \Drupal\Core\State\StateInterface + */ + protected $state; + + /** + * The migration plugin manager service. + * + * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface + */ + protected $pluginManager; + + /** + * The field plugin manager service. + * + * @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface + */ + protected $fieldPluginManager; + + /** + * The migrations. + * + * @var \Drupal\migrate\Plugin\MigrationInterface[] + */ + protected $migrations; + + /** + * List of extensions that do not need an upgrade path. + * + * This property is an array where the keys are the major Drupal core version + * from which we are upgrading, and the values are arrays of extension names + * that do not need an upgrade path. + * + * @var array[] + */ + protected $noUpgradePaths = [ + '6' => [ + '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\Core\State\StateInterface $state + * The state service. + * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager + * The migration plugin manager service. + * @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(StateInterface $state, MigrationPluginManagerInterface $migration_plugin_manager, MigrateFieldPluginManagerInterface $field_plugin_manager, PrivateTempStoreFactory $tempstore_private) { + parent::__construct($tempstore_private); + $this->state = $state; + $this->pluginManager = $migration_plugin_manager; + $this->fieldPluginManager = $field_plugin_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('state'), + $container->get('plugin.manager.migration'), + $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) { + // Get all the data needed for this form. + $version = $this->store->get('version'); + $this->migrations = $this->store->get('migrations'); + // Fetch the system data at the first opportunity. + $system_data = $this->store->get('system_data'); + + // If data is missing or this is the wrong step, start over. + if (!$version || !$this->migrations || !$system_data || + ($this->store->get('step') != 'review')) { + return $this->restartUpgradeForm(); + } + + $form = parent::buildForm($form, $form_state); + $form['#title'] = $this->t('What will be upgraded?'); + + // Get the source_module and destination_module for each migration. + $migrations = $this->pluginManager->createInstances(array_keys($this->store->get('migrations'))); + $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']; + } + } + + // 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 <a href=":review">Review the pre-upgrade analysis</a> in the <a href=":migrate">Upgrading to Drupal 8</a> 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) { + $config['source_base_path'] = $this->store->get('source_base_path'); + $batch = [ + 'title' => $this->t('Running upgrade'), + 'progress_message' => '', + 'operations' => [ + [ + [MigrateUpgradeImportBatch::class, 'run'], + [array_keys($this->migrations), $config], + ], + ], + 'finished' => [ + MigrateUpgradeImportBatch::class, 'finished', + ], + ]; + batch_set($batch); + $form_state->setRedirect('<front>'); + $this->store->set('step', 'overview'); + $this->state->set('migrate_drupal_ui.performed', REQUEST_TIME); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return $this->t('Perform upgrade'); + } + +} diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php index a9d7de1089e5551991e75551286e19d80e738003..36925d2b84b59581e3b68146644389503277e04b 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php @@ -118,6 +118,7 @@ public function testMigrateUpgradeExecute() { $this->drupalGet('/upgrade'); $session->pageTextContains('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.'); $this->drupalPostForm(NULL, [], t('Import new configuration and content from old site')); + $this->drupalPostForm(NULL, $edits, t('Review upgrade')); $session->pageTextContains('WARNING: Content may be overwritten on your new site.'); $session->pageTextContains('There is conflicting content of these types:'); $session->pageTextContains('file entities'); diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeFormStepsTest.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeFormStepsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..caa60c3438393c103036e9f90ffbf59bbeadc908 --- /dev/null +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeFormStepsTest.php @@ -0,0 +1,138 @@ +<?php + +namespace Drupal\Tests\migrate_drupal_ui\Functional; + +use Drupal\migrate_drupal\MigrationConfigurationTrait; +use Drupal\Tests\migrate_drupal\Traits\CreateTestContentEntitiesTrait; +use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\WebAssert; + +/** + * Tests the flow of the Migrate Drupal UI form. + * + * @group migrate_drupal_ui + */ +class MigrateUpgradeFormStepsTest extends BrowserTestBase { + + use MigrationConfigurationTrait; + use CreateTestContentEntitiesTrait; + + /** + * {@inheritdoc} + */ + public static $modules = ['migrate_drupal_ui']; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + // Log in as user 1. Migrations in the UI can only be performed as user 1. + $this->drupalLogin($this->rootUser); + } + + /** + * {@inheritdoc} + */ + protected function getSourceBasePath() { + return __DIR__ . '/files'; + } + + /** + * Tests the flow of the Migrate Drupal UI form. + * + * The Migrate Drupal UI uses several forms to guide you through the upgrade + * process. The forms displayed depend on if this is an incremental migration + * or if there are potential ID conflicts. The forms are to be displayed in + * this order; Overview or Incremental, if a migration has already been run + * then Credential, Id conflict, if conflicts are detected, and lastly Review. + */ + public function testMigrateUpgradeReviewPage() { + /** @var \Drupal\Core\TempStore\PrivateTempStore $store */ + $store = \Drupal::service('tempstore.private')->get('migrate_drupal_ui'); + $state = \Drupal::service('state'); + + // Test that when data required by a form is missing that the correct first + // form is displayed. The first form for an initial migration is the + // Overview form and for an incremental migration it is the Incremental + // form. + $session = $this->assertSession(); + $expected['initial'] = 'Upgrade a site by importing its files and the data from its database into a clean and empty new install of Drupal 8.'; + $expected['incremental'] = "An upgrade has already been performed on this site."; + + foreach (['/upgrade', '/upgrade/incremental'] as $expected) { + if ($expected === '/upgrade/incremental') { + // Set a performed time to signify an incremental migration. The time + // value is a UNIX timestamp. + $state->set('migrate_drupal_ui.performed', 1); + } + // Test that an invalid step to any form goes to the correct first form. + $store->set('step', 'foo'); + $this->assertFirstForm($session, $expected); + // Test that an undefined step to any form goes to the correct first form. + $store->delete('step'); + $this->assertFirstForm($session, $expected); + + // For forms that require data from the private store, test that when that + // data is missing the correct first page is displayed. + // The Id conflict form requires the migrations array. + $store->delete('migrations'); + $store->set('step', 'idconflict'); + $this->drupalGet('/upgrade/idconflict'); + $session->addressEquals($expected); + + // The Review form requires version, migrations and system_data. Test + // three times with only one of the variables missing. + $store->delete('version'); + $store->set('migrations', ['foo', 'bar']); + $store->set('system_data', ['bar', 'foo']); + $store->set('step', 'review'); + $this->drupalGet('/upgrade/review'); + $session->addressEquals($expected); + + $store->set('version', '6'); + $store->delete('migrations'); + $store->set('system_data', ['bar', 'foo']); + $store->set('step', 'review'); + $this->drupalGet('/upgrade/review'); + $session->addressEquals($expected); + + $store->set('version', '6'); + $store->set('migrations', ['foo', 'bar']); + $store->delete('system_data'); + $store->set('step', 'review'); + $this->drupalGet('/upgrade/review'); + $session->addressEquals($expected); + } + + // Test that the credential form is displayed for incremental migrations. + $store->set('step', 'overview'); + $this->drupalGet('/upgrade'); + $session->pageTextContains('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.'); + $this->drupalPostForm(NULL, [], t('Import new configuration and content from old site')); + $session->pageTextContains('Provide credentials for the database of the Drupal site you want to upgrade.'); + } + + /** + * Helper to test that a path goes to the Overview form. + * + * @param \Drupal\Tests\WebAssert $session + * The WebAssert object. + * @param string $expected + * The expected response text. + */ + protected function assertFirstForm(WebAssert $session, $expected) { + $paths = [ + '', + '/incremental', + '/credentials', + '/idconflict', + '/review', + ]; + foreach ($paths as $path) { + $this->drupalGet('/upgrade' . $path); + $session->addressEquals($expected); + } + } + +}