Skip to content
Snippets Groups Projects
Commit c136a851 authored by Jess's avatar Jess
Browse files

Issue #2281691 by xjm, mikeryan, Gábor Hojtsy, alexpott, tim.plunkett,...

Issue #2281691 by xjm, mikeryan, Gábor Hojtsy, alexpott, tim.plunkett, webchick, abhishek-anand, quietone, yoroy, stevepurkiss, benjy, Bojhan, Ryan Weal, aleksip, anavarre, hussainweb, er.pushpinderrana, cilefen, Luukyb, Jo Fitzgerald: User interface for migration-based upgrades
parent 50ca1107
No related branches found
No related tags found
No related merge requests found
Showing
with 2292 additions and 0 deletions
......@@ -101,6 +101,7 @@
"drupal/menu_ui": "self.version",
"drupal/migrate": "self.version",
"drupal/migrate_drupal": "self.version",
"drupal/migrate_drupal_ui": "self.version",
"drupal/node": "self.version",
"drupal/options": "self.version",
"drupal/page_cache": "self.version",
......
<?php
/**
* @file
* Contains \Drupal\migrate_drupal\MigrationCreationTrait.
*/
namespace Drupal\migrate_drupal;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Database;
use Drupal\migrate\Entity\Migration;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\RequirementsInterface;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
/**
* Creates the appropriate migrations for a given source Drupal database.
*/
trait MigrationCreationTrait {
/**
* Gets the database connection for the source Drupal database.
*
* @param array $database
* Database array representing the source Drupal database.
*
* @return \Drupal\Core\Database\Connection
* The database connection for the source Drupal database.
*/
protected function getConnection(array $database) {
// Set up the connection.
Database::addConnectionInfo('upgrade', 'default', $database);
$connection = Database::getConnection('default', 'upgrade');
return $connection;
}
/**
* Gets the system data from the system table of the source Drupal database.
*
* @param array $database
* Database array representing the source Drupal database.
*
* @return array
* The system data from the system table of the source Drupal database.
*/
protected function getSystemData(array $database) {
$connection = $this->getConnection($database);
$system_data = [];
try {
$results = $connection->select('system', 's', [
'fetch' => \PDO::FETCH_ASSOC,
])
->fields('s')
->execute();
foreach ($results as $result) {
$system_data[$result['type']][$result['name']] = $result;
}
}
catch (\Exception $e) {
// The table might not exist for example in tests.
}
return $system_data;
}
/**
* Sets up the relevant migrations for import from a database connection.
*
* @param array $database
* Database array representing the source Drupal database.
* @param string $source_base_path
* (Optional) Address of the source Drupal site (e.g., http://example.com/).
*
* @return array
* An array of the migration templates (parsed YAML config arrays) that were
* tagged for the identified source Drupal version. The templates are
* populated with database state key and file source base path information
* for execution. The array is keyed by migration IDs.
*
* @throws \Exception
*/
protected function getMigrationTemplates(array $database, $source_base_path = '') {
// Set up the connection.
$connection = $this->getConnection($database);
if (!$drupal_version = $this->getLegacyDrupalVersion($connection)) {
throw new \Exception('Source database does not contain a recognizable Drupal version.');
}
$database_state['key'] = 'upgrade';
$database_state['database'] = $database;
$database_state_key = 'migrate_drupal_' . $drupal_version;
\Drupal::state()->set($database_state_key, $database_state);
$version_tag = 'Drupal ' . $drupal_version;
$template_storage = \Drupal::service('migrate.template_storage');
$migration_templates = $template_storage->findTemplatesByTag($version_tag);
foreach ($migration_templates as $id => $template) {
$migration_templates[$id]['source']['database_state_key'] = $database_state_key;
// Configure file migrations so they can find the files.
if ($template['destination']['plugin'] == 'entity:file') {
if ($source_base_path) {
// Make sure we have a single trailing slash.
$source_base_path = rtrim($source_base_path, '/') . '/';
$migration_templates[$id]['destination']['source_base_path'] = $source_base_path;
}
}
}
return $migration_templates;
}
/**
* Gets the migrations for import.
*
* Uses the migration template connection to ensure that only the relevant
* migrations are returned.
*
* @param array $migration_templates
* Migration templates (parsed YAML config arrays), keyed by the ID.
*
* @return \Drupal\migrate\Entity\MigrationInterface[]
* The migrations for import.
*/
protected function getMigrations(array $migration_templates) {
// Let the builder service create our migration configuration entities from
// the templates, expanding them to multiple entities where necessary.
/** @var \Drupal\migrate\MigrationBuilder $builder */
$builder = \Drupal::service('migrate.migration_builder');
$initial_migrations = $builder->createMigrations($migration_templates);
$migrations = [];
foreach ($initial_migrations as $migration) {
try {
// Any plugin that has specific requirements to check will implement
// RequirementsInterface.
$source_plugin = $migration->getSourcePlugin();
if ($source_plugin instanceof RequirementsInterface) {
$source_plugin->checkRequirements();
}
$destination_plugin = $migration->getDestinationPlugin();
if ($destination_plugin instanceof RequirementsInterface) {
$destination_plugin->checkRequirements();
}
$migrations[] = $migration;
}
// Migrations which are not applicable given the source and destination
// site configurations (e.g., what modules are enabled) will be silently
// ignored.
catch (RequirementsException $e) {
}
catch (PluginNotFoundException $e) {
}
}
return $migrations;
}
/**
* Saves the migrations for import from the provided template connection.
*
* @param array $migration_templates
* Migration template.
*
* @return array
* The migration IDs sorted in dependency order.
*/
protected function createMigrations(array $migration_templates) {
$migration_ids = [];
$migrations = $this->getMigrations($migration_templates);
foreach ($migrations as $migration) {
// Don't try to resave migrations that already exist.
if (!Migration::load($migration->id())) {
$migration->save();
}
$migration_ids[] = $migration->id();
}
// loadMultiple will sort the migrations in dependency order.
return array_keys(Migration::loadMultiple($migration_ids));
}
/**
* Determines what version of Drupal the source database contains.
*
* @param \Drupal\Core\Database\Connection $connection
* The database connection object.
*
* @return int|FALSE
* An integer representing the major branch of Drupal core (e.g. '6' for
* Drupal 6.x), or FALSE if no valid version is matched.
*/
protected function getLegacyDrupalVersion(Connection $connection) {
// Don't assume because a table of that name exists, that it has the columns
// we're querying. Catch exceptions and report that the source database is
// not Drupal.
// Drupal 5/6/7 can be detected by the schema_version in the system table.
if ($connection->schema()->tableExists('system')) {
try {
$version_string = $connection
->query('SELECT schema_version FROM {system} WHERE name = :module', [':module' => 'system'])
->fetchField();
if ($version_string && $version_string[0] == '1') {
if ((int) $version_string >= 1000) {
$version_string = '5';
}
else {
$version_string = FALSE;
}
}
}
catch (\PDOException $e) {
$version_string = FALSE;
}
}
// For Drupal 8 (and we're predicting beyond) the schema version is in the
// key_value store.
elseif ($connection->schema()->tableExists('key_value')) {
$result = $connection
->query("SELECT value FROM {key_value} WHERE collection = :system_schema and name = :module", [':system_schema' => 'system.schema', ':module' => 'system'])
->fetchField();
$version_string = unserialize($result);
}
else {
$version_string = FALSE;
}
return $version_string ? substr($version_string, 0, 1) : FALSE;
}
}
name: 'Drupal Upgrade UI'
type: module
description: 'UI for direct upgrades from older Drupal versions.'
package: 'Core (Experimental)'
version: VERSION
core: 8.x
configure: migrate_drupal_ui.upgrade
dependencies:
- migrate
- migrate_drupal
- dblog
<?php
/**
* @file
* Install, update, and uninstall functions for the migrate_drupal_ui module.
*/
use Drupal\Core\Url;
/**
* Implements hook_install().
*/
function migrate_drupal_ui_install() {
$url = Url::fromUri('base:upgrade')->toString();
drupal_set_message(t('The Drupal Upgrade UI module has been enabled. Proceed to the <a href=":url">upgrade form</a>.', [':url' => $url]));
}
<?php
/**
* @file
* Alert administrators before starting the import process.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function migrate_drupal_ui_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.migrate_drupal_ui':
$output = '<p>' . t('The Drupal Upgrade UI module provides a one-click upgrade from an earlier version of Drupal. For details, see the <a href=":migrate">online documentation for the Drupal Upgrade UI module</a> in the handbook on upgrading from previous versions.', [':migrate' => 'https://www.drupal.org/upgrade/migrate']) . '</p>';
return $output;
}
}
migrate_drupal_ui.upgrade:
path: '/upgrade'
defaults:
_form: '\Drupal\migrate_drupal_ui\Form\MigrateUpgradeForm'
_title: 'Upgrade'
requirements:
_permission: 'administer software updates'
options:
_admin_route: TRUE
migrate_drupal_ui.log:
path: '/upgrade/log'
defaults:
_controller: '\Drupal\migrate_drupal_ui\Controller\MigrateController::showLog'
requirements:
_permission: 'administer software updates'
options:
_admin_route: TRUE
<?php
/**
* @file
* Contains \Drupal\migrate_drupal_ui\Controller\MigrateController.
*/
namespace Drupal\migrate_drupal_ui\Controller;
use Drupal\Core\Controller\ControllerBase;
/**
* Provides controller methods for the migration.
*/
class MigrateController extends ControllerBase {
/**
* Sets a log filter and redirects to the log.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* A redirect response object that may be returned by the controller.
*/
public function showLog() {
$_SESSION['dblog_overview_filter'] = [];
$_SESSION['dblog_overview_filter']['type'] = ['migrate_drupal_ui' => 'migrate_drupal_ui'];
return $this->redirect('dblog.overview');
}
}
This diff is collapsed.
<?php
/**
* @file
* Contains \Drupal\migrate_drupal_ui\MigrateMessageCapture.
*/
namespace Drupal\migrate_drupal_ui;
use Drupal\migrate\MigrateMessageInterface;
/**
* Allows capturing messages rather than displaying them directly.
*/
class MigrateMessageCapture implements MigrateMessageInterface {
/**
* Array of recorded messages.
*
* @var array
*/
protected $messages = [];
/**
* {@inheritdoc}
*/
public function display($message, $type = 'status') {
$this->messages[] = $message;
}
/**
* Clears out any captured messages.
*/
public function clear() {
$this->messages = [];
}
/**
* Returns any captured messages.
*
* @return array
* The captured messages.
*/
public function getMessages() {
return $this->messages;
}
}
<?php
/**
* @file
* Contains \Drupal\migrate_drupal_ui\MigrateUpgradeRunBatch.
*/
namespace Drupal\migrate_drupal_ui;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\migrate\Entity\Migration;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Event\MigrateEvents;
use Drupal\migrate\Event\MigrateIdMapMessageEvent;
use Drupal\migrate\Event\MigrateMapDeleteEvent;
use Drupal\migrate\Event\MigrateMapSaveEvent;
use Drupal\migrate\Event\MigratePostRowSaveEvent;
use Drupal\migrate\Event\MigrateRowDeleteEvent;
use Drupal\migrate\MigrateExecutable;
/**
* Runs a single migration batch.
*/
class MigrateUpgradeRunBatch {
/**
* Maximum number of previous messages to display.
*/
const MESSAGE_LENGTH = 20;
/**
* The processed items for one batch of a given migration.
*
* @var int
*/
protected static $numProcessed = 0;
/**
* Ensure we only add the listeners once per request.
*
* @var bool
*/
protected static $listenersAdded = FALSE;
/**
* The maximum length in seconds to allow processing in a request.
*
* @see self::run()
*
* @var int
*/
protected static $maxExecTime;
/**
* MigrateMessage instance to capture messages during the migration process.
*
* @var \Drupal\migrate_drupal_ui\MigrateMessageCapture
*/
protected static $messages;
/**
* Runs a single migration batch.
*
* @param int[] $initial_ids
* The full set of migration IDs to import.
* @param string $operation
* The operation to perform, 'import' or 'rollback'.
* @param array $context
* The batch context.
*/
public static function run($initial_ids, $operation, &$context) {
if (!static::$listenersAdded) {
$event_dispatcher = \Drupal::service('event_dispatcher');
if ($operation == 'import') {
$event_dispatcher->addListener(MigrateEvents::POST_ROW_SAVE, [static::class, 'onPostRowSave']);
$event_dispatcher->addListener(MigrateEvents::MAP_SAVE, [static::class, 'onMapSave']);
$event_dispatcher->addListener(MigrateEvents::IDMAP_MESSAGE, [static::class, 'onIdMapMessage']);
}
else {
$event_dispatcher->addListener(MigrateEvents::POST_ROW_DELETE, [static::class, 'onPostRowDelete']);
$event_dispatcher->addListener(MigrateEvents::MAP_DELETE, [static::class, 'onMapDelete']);
}
static::$maxExecTime = ini_get('max_execution_time');
if (static::$maxExecTime <= 0) {
static::$maxExecTime = 60;
}
// Set an arbitrary threshold of 3 seconds (e.g., if max_execution_time is
// 45 seconds, we will quit at 42 seconds so a slow item or cleanup
// overhead don't put us over 45).
static::$maxExecTime -= 3;
static::$listenersAdded = TRUE;
}
if (!isset($context['sandbox']['migration_ids'])) {
$context['sandbox']['max'] = count($initial_ids);
$context['sandbox']['current'] = 1;
// Total number processed for this migration.
$context['sandbox']['num_processed'] = 0;
// migration_ids will be the list of IDs remaining to run.
$context['sandbox']['migration_ids'] = $initial_ids;
$context['sandbox']['messages'] = [];
$context['results']['failures'] = 0;
$context['results']['successes'] = 0;
$context['results']['operation'] = $operation;
}
// Number processed in this batch.
static::$numProcessed = 0;
$migration_id = reset($context['sandbox']['migration_ids']);
/** @var \Drupal\migrate\Entity\Migration $migration */
$migration = Migration::load($migration_id);
if ($migration) {
static::$messages = new MigrateMessageCapture();
$executable = new MigrateExecutable($migration, static::$messages);
$migration_name = $migration->label() ? $migration->label() : $migration_id;
try {
if ($operation == 'import') {
$migration_status = $executable->import();
}
else {
$migration_status = $executable->rollback();
}
}
catch (\Exception $e) {
static::logger()->error($e->getMessage());
$migration_status = MigrationInterface::RESULT_FAILED;
}
switch ($migration_status) {
case MigrationInterface::RESULT_COMPLETED:
// Store the number processed in the sandbox.
$context['sandbox']['num_processed'] += static::$numProcessed;
if ($operation == 'import') {
$message = static::getTranslation()->formatPlural(
$context['sandbox']['num_processed'], 'Upgraded @migration (processed 1 item total)', 'Upgraded @migration (processed @num_processed items total)',
['@migration' => $migration_name, '@num_processed' => $context['sandbox']['num_processed']]);
}
else {
$message = static::getTranslation()->formatPlural(
$context['sandbox']['num_processed'], 'Rolled back @migration (processed 1 item total)', 'Rolled back @migration (processed @num_processed items total)',
['@migration' => $migration_name, '@num_processed' => $context['sandbox']['num_processed']]);
$migration->delete();
}
$context['sandbox']['messages'][] = $message;
static::logger()->notice($message);
$context['sandbox']['num_processed'] = 0;
$context['results']['successes']++;
break;
case MigrationInterface::RESULT_INCOMPLETE:
$context['sandbox']['messages'][] = static::getTranslation()->formatPlural(
static::$numProcessed, 'Continuing with @migration (processed 1 item)', 'Continuing with @migration (processed @num_processed items)',
['@migration' => $migration_name, '@num_processed' => static::$numProcessed]);
$context['sandbox']['num_processed'] += static::$numProcessed;
break;
case MigrationInterface::RESULT_STOPPED:
$context['sandbox']['messages'][] = t('Operation stopped by request');
break;
case MigrationInterface::RESULT_FAILED:
$context['sandbox']['messages'][] = t('Operation on @migration failed', ['@migration' => $migration_name]);
$context['results']['failures']++;
static::logger()->error('Operation on @migration failed', ['@migration' => $migration_name]);
break;
case MigrationInterface::RESULT_SKIPPED:
$context['sandbox']['messages'][] = t('Operation on @migration skipped due to unfulfilled dependencies', ['@migration' => $migration_name]);
static::logger()->error('Operation on @migration skipped due to unfulfilled dependencies', ['@migration' => $migration_name]);
break;
case MigrationInterface::RESULT_DISABLED:
// Skip silently if disabled.
break;
}
// Unless we're continuing on with this migration, take it off the list.
if ($migration_status != MigrationInterface::RESULT_INCOMPLETE) {
array_shift($context['sandbox']['migration_ids']);
$context['sandbox']['current']++;
}
// Add and log any captured messages.
foreach (static::$messages->getMessages() as $message) {
$context['sandbox']['messages'][] = $message;
static::logger()->error($message);
}
// Only display the last MESSAGE_LENGTH messages, in reverse order.
$message_count = count($context['sandbox']['messages']);
$context['message'] = '';
for ($index = max(0, $message_count - self::MESSAGE_LENGTH); $index < $message_count; $index++) {
$context['message'] = $context['sandbox']['messages'][$index] . "<br />\n" . $context['message'];
}
if ($message_count > self::MESSAGE_LENGTH) {
// Indicate there are earlier messages not displayed.
$context['message'] .= '&hellip;';
}
// At the top of the list, display the next one (which will be the one
// that is running while this message is visible).
if (!empty($context['sandbox']['migration_ids'])) {
$migration_id = reset($context['sandbox']['migration_ids']);
$migration = Migration::load($migration_id);
$migration_name = $migration->label() ? $migration->label() : $migration_id;
if ($operation == 'import') {
$context['message'] = t('Currently upgrading @migration (@current of @max total tasks)', [
'@migration' => $migration_name,
'@current' => $context['sandbox']['current'],
'@max' => $context['sandbox']['max'],
]) . "<br />\n" . $context['message'];
}
else {
$context['message'] = t('Currently rolling back @migration (@current of @max total tasks)', [
'@migration' => $migration_name,
'@current' => $context['sandbox']['current'],
'@max' => $context['sandbox']['max'],
]) . "<br />\n" . $context['message'];
}
}
}
else {
array_shift($context['sandbox']['migration_ids']);
$context['sandbox']['current']++;
}
$context['finished'] = 1 - count($context['sandbox']['migration_ids']) / $context['sandbox']['max'];
}
/**
* Returns the logger using the migrate_drupal_ui channel.
*
* @return \Psr\Log\LoggerInterface
* The logger instance.
*/
protected static function logger() {
return \Drupal::logger('migrate_drupal_ui');
}
/**
* Wraps the translation manager.
*
* @return \Drupal\Core\StringTranslation\TranslationManager
* The string translation manager.
*/
protected static function getTranslation() {
return \Drupal::translation();
}
/**
* Implements the Batch API finished method.
*/
public static function finished($success, $results, $operations, $elapsed) {
static::displayResults($results);
}
/**
* Displays counts of success/failures on the migration upgrade complete page.
*
* @param array $results
* An array of result data built during the batch.
*/
protected static function displayResults($results) {
$successes = $results['successes'];
$failures = $results['failures'];
// If we had any successes log that for the user.
if ($successes > 0) {
if ($results['operation'] == 'import') {
drupal_set_message(static::getTranslation()->formatPlural($successes, 'Completed 1 upgrade task successfully', 'Completed @count upgrade tasks successfully'));
}
else {
drupal_set_message(static::getTranslation()->formatPlural($successes, 'Completed 1 rollback task successfully', 'Completed @count rollback tasks successfully'));
}
}
// If we had failures, log them and show the migration failed.
if ($failures > 0) {
if ($results['operation'] == 'import') {
drupal_set_message(static::getTranslation()->formatPlural($failures, '1 upgrade failed', '@count upgrades failed'));
drupal_set_message(t('Upgrade process not completed'), 'error');
}
else {
drupal_set_message(static::getTranslation()->formatPlural($failures, '1 rollback failed', '@count rollbacks failed'));
drupal_set_message(t('Rollback process not completed'), 'error');
}
}
else {
if ($results['operation'] == 'import') {
// Everything went off without a hitch. We may not have had successes
// but we didn't have failures so this is fine.
drupal_set_message(t('Congratulations, you upgraded Drupal!'));
}
else {
drupal_set_message(t('Rollback of the upgrade is complete - you may now start the upgrade process from scratch.'));
}
}
if (\Drupal::moduleHandler()->moduleExists('dblog')) {
$url = Url::fromRoute('migrate_drupal_ui.log');
drupal_set_message(Link::fromTextAndUrl(t('Review the detailed upgrade log'), $url), $failures ? 'error' : 'status');
}
}
/**
* Reacts to item import.
*
* @param \Drupal\migrate\Event\MigratePostRowSaveEvent $event
* The post-save event.
*/
public static function onPostRowSave(MigratePostRowSaveEvent $event) {
// We want to interrupt this batch and start a fresh one.
if ((time() - REQUEST_TIME) > static::$maxExecTime) {
$event->getMigration()->interruptMigration(MigrationInterface::RESULT_INCOMPLETE);
}
}
/**
* Reacts to item deletion.
*
* @param \Drupal\migrate\Event\MigrateRowDeleteEvent $event
* The post-save event.
*/
public static function onPostRowDelete(MigrateRowDeleteEvent $event) {
// We want to interrupt this batch and start a fresh one.
if ((time() - REQUEST_TIME) > static::$maxExecTime) {
$event->getMigration()->interruptMigration(MigrationInterface::RESULT_INCOMPLETE);
}
}
/**
* Counts up any map save events.
*
* @param \Drupal\migrate\Event\MigrateMapSaveEvent $event
* The map event.
*/
public static function onMapSave(MigrateMapSaveEvent $event) {
static::$numProcessed++;
}
/**
* Counts up any map delete events.
*
* @param \Drupal\migrate\Event\MigrateMapDeleteEvent $event
* The map event.
*/
public static function onMapDelete(MigrateMapDeleteEvent $event) {
static::$numProcessed++;
}
/**
* Displays any messages being logged to the ID map.
*
* @param \Drupal\migrate\Event\MigrateIdMapMessageEvent $event
* The message event.
*/
public static function onIdMapMessage(MigrateIdMapMessageEvent $event) {
if ($event->getLevel() == MigrationInterface::MESSAGE_NOTICE || $event->getLevel() == MigrationInterface::MESSAGE_INFORMATIONAL) {
$type = 'status';
}
else {
$type = 'error';
}
$source_id_string = implode(',', $event->getSourceIdValues());
$message = t('Source ID @source_id: @message', ['@source_id' => $source_id_string, '@message' => $event->getMessage()]);
static::$messages->display($message, $type);
}
}
<?php
/**
* @file
* Contains \Drupal\migrate_drupal_ui\Tests\MigrateUpgradeTestBase.
*/
namespace Drupal\migrate_drupal_ui\Tests;
use Drupal\Core\Database\Database;
use Drupal\simpletest\WebTestBase;
/**
* Provides a base class for testing migration upgrades in the UI.
*/
abstract class MigrateUpgradeTestBase extends WebTestBase {
/**
* Use the Standard profile to test help implementations of many core modules.
*/
protected $profile = 'standard';
/**
* The source database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $sourceDatabase;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['migrate_drupal_ui'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->createMigrationConnection();
$this->sourceDatabase = Database::getConnection('default', 'migrate_drupal_ui');
// Create and log in as user 1. Migrations in the UI can only be performed
// as user 1 once https://www.drupal.org/node/2675066 lands.
$this->drupalLogin($this->rootUser);
}
/**
* Loads a database fixture into the source database connection.
*
* @param string $path
* Path to the dump file.
*/
protected function loadFixture($path) {
$default_db = Database::getConnection()->getKey();
Database::setActiveConnection($this->sourceDatabase->getKey());
if (substr($path, -3) == '.gz') {
$path = 'compress.zlib://' . $path;
}
require $path;
Database::setActiveConnection($default_db);
}
/**
* Changes the database connection to the prefixed one.
*
* @todo Remove when we don't use global. https://www.drupal.org/node/2552791
*/
protected function createMigrationConnection() {
$connection_info = Database::getConnectionInfo('default')['default'];
if ($connection_info['driver'] === 'sqlite') {
// Create database file in the test site's public file directory so that
// \Drupal\simpletest\TestBase::restoreEnvironment() will delete this once
// the test is complete.
$file = $this->publicFilesDirectory . '/' . $this->testId . '-migrate.db.sqlite';
touch($file);
$connection_info['database'] = $file;
$connection_info['prefix'] = '';
}
else {
$prefix = is_array($connection_info['prefix']) ? $connection_info['prefix']['default'] : $connection_info['prefix'];
// Simpletest uses fixed length prefixes. Create a new prefix for the
// source database. Adding to the end of the prefix ensures that
// \Drupal\simpletest\TestBase::restoreEnvironment() will remove the
// additional tables.
$connection_info['prefix'] = $prefix . '0';
}
Database::addConnectionInfo('migrate_drupal_ui', 'default', $connection_info);
}
/**
* {@inheritdoc}
*/
protected function tearDown() {
Database::removeConnection('migrate_drupal_ui');
parent::tearDown();
}
/**
* Executes all steps of migrations upgrade.
*/
protected function testMigrateUpgrade() {
$connection_options = $this->sourceDatabase->getConnectionOptions();
$this->drupalGet('/upgrade');
$this->assertText('Upgrade a Drupal site by importing it into a clean and empty new install of Drupal 8. You will lose any existing configuration once you import your site into it. See the upgrading handbook for more detailed information.');
$this->drupalPostForm(NULL, [], t('Continue'));
$this->assertText('Provide credentials for the database of the Drupal site you want to upgrade.');
$this->assertFieldByName('mysql[host]');
$driver = $connection_options['driver'];
$connection_options['prefix'] = $connection_options['prefix']['default'];
// Use the driver connection form to get the correct options out of the
// database settings. This supports all of the databases we test against.
$drivers = drupal_get_database_types();
$form = $drivers[$driver]->getFormOptions($connection_options);
$connection_options = array_intersect_key($connection_options, $form + $form['advanced_options']);
$edits = $this->translatePostValues([
'driver' => $driver,
$driver => $connection_options,
'source_base_path' => $this->getSourceBasePath(),
]);
$this->drupalPostForm(NULL, $edits, t('Review upgrade'));
$this->assertResponse(200);
$this->assertText('Are you sure?');
$this->drupalPostForm(NULL, [], t('Perform upgrade'));
$this->assertText(t('Congratulations, you upgraded Drupal!'));
// Have to reset all the statics after migration to ensure entities are
// loadable.
$this->resetAll();
$expected_counts = $this->getEntityCounts();
foreach (array_keys(\Drupal::entityTypeManager()->getDefinitions()) as $entity_type) {
$real_count = count(\Drupal::entityTypeManager()->getStorage($entity_type)->loadMultiple());
$expected_count = isset($expected_counts[$entity_type]) ? $expected_counts[$entity_type] : 0;
$this->assertEqual($expected_count, $real_count, "Found $real_count $entity_type entities, expected $expected_count.");
}
}
/**
* Gets the source base path for the concrete test.
*
* @return string
* The source base path.
*/
abstract protected function getSourceBasePath();
/**
* Gets the expected number of entities per entity type after migration.
*
* @return int[]
* An array of expected counts keyed by entity type ID.
*/
abstract protected function getEntityCounts();
}
<?php
/**
* @file
* Contains \Drupal\migrate_drupal_ui\Tests\d6\MigrateUpgrade6Test.
*/
namespace Drupal\migrate_drupal_ui\Tests\d6;
use Drupal\migrate_drupal_ui\Tests\MigrateUpgradeTestBase;
/**
* Tests Drupal 6 upgrade using the migrate UI.
*
* The test method is provided by the MigrateUpgradeTestBase class.
*
* @group migrate_drupal_ui
*/
class MigrateUpgrade6Test extends MigrateUpgradeTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->loadFixture(drupal_get_path('module', 'migrate_drupal') . '/tests/fixtures/drupal6.php');
}
/**
* {@inheritdoc}
*/
protected function getSourceBasePath() {
return __DIR__ . '/files';
}
/**
* {@inheritdoc}
*/
protected function getEntityCounts() {
return [
'block' => 30,
'block_content' => 2,
'block_content_type' => 1,
'comment' => 3,
'comment_type' => 2,
'contact_form' => 5,
'editor' => 2,
'field_config' => 61,
'field_storage_config' => 42,
'file' => 4,
'filter_format' => 8,
'image_style' => 5,
'migration' => 105,
'node' => 9,
'node_type' => 11,
'rdf_mapping' => 5,
'search_page' => 2,
'shortcut' => 2,
'shortcut_set' => 1,
'action' => 22,
'menu' => 8,
'taxonomy_term' => 6,
'taxonomy_vocabulary' => 6,
'tour' => 1,
'user' => 7,
'user_role' => 6,
'menu_link_content' => 4,
'view' => 12,
'date_format' => 11,
'entity_form_display' => 15,
'entity_form_mode' => 1,
'entity_view_display' => 32,
'entity_view_mode' => 12,
'base_field_override' => 33,
];
}
}
<?php
/**
* @file
* Contains \Drupal\migrate_drupal_ui\Tests\d7\MigrateUpgrade7Test.
*/
namespace Drupal\migrate_drupal_ui\Tests\d7;
use Drupal\migrate_drupal_ui\Tests\MigrateUpgradeTestBase;
/**
* Tests Drupal 7 upgrade using the migrate UI.
*
* The test method is provided by the MigrateUpgradeTestBase class.
*
* @group migrate_drupal_ui
*/
class MigrateUpgrade7Test extends MigrateUpgradeTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->loadFixture(drupal_get_path('module', 'migrate_drupal') . '/tests/fixtures/drupal7.php');
}
/**
* {@inheritdoc}
*/
protected function getSourceBasePath() {
return __DIR__ . '/files';
}
/**
* {@inheritdoc}
*/
protected function getEntityCounts() {
return [
'block' => 25,
'block_content' => 1,
'block_content_type' => 1,
'comment' => 1,
'comment_type' => 7,
'contact_form' => 3,
'editor' => 2,
'field_config' => 40,
'field_storage_config' => 30,
'file' => 0,
'filter_format' => 7,
'image_style' => 6,
'migration' => 59,
'node' => 2,
'node_type' => 6,
'rdf_mapping' => 5,
'search_page' => 2,
'shortcut' => 6,
'shortcut_set' => 2,
'action' => 18,
'menu' => 10,
'taxonomy_term' => 18,
'taxonomy_vocabulary' => 3,
'tour' => 1,
'user' => 3,
'user_role' => 4,
'menu_link_content' => 9,
'view' => 12,
'date_format' => 11,
'entity_form_display' => 15,
'entity_form_mode' => 1,
'entity_view_display' => 22,
'entity_view_mode' => 10,
'base_field_override' => 7,
];
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment