Programatically Creating and Storing WordPress Migrate Migrations in Drupal

Migrations are never glamorous, but doing them right and verifying their integrity is essential to their success. The WordPress Migrate module gives you an easy turnkey solution to migrating content into Drupal from WordPress. It allows you to create each migration through an interactive admin form, allowing you to configure your migration entirely through the UI. This is great, but it does not make creating or storing the resulting migrations easy to manage across multiple environments, since the migrations are not defined in code like a typical Migrate class. Short of copying database tables or re-entering the configuration through the admin forms, developers are stuck with the migrations stored in a single database and thus it is not easy to move to other environments for testing or further development.

Copying data tables is almost always the wrong solution and manually re-entering all of the migrations would be way too time consuming, so our solution was to create the migrations programmatically. To do this, we hooked into the existing WordPress Migrate codebase and used its logic to build programmatically, what it builds from data input to its admin forms. Then we are able to define all of our migration sources in code and instantly create all of our migrations in a new environment, or recreate them after something fails during development.

As mentioned, this solution relies upon programmatically submitting admin forms, which is often not an ideal scenario. Additionally, there is the almost inevitable request to add additional customizations beyond what Wordpress Migrate supports out of the box. Sometimes this makes WordPress Migrate more of a hinderance than a help. So why not just create a custom Migrate class from the outset and avoid all of these issues? Here are some factors to consider:

  • Writing a custom Migrate class for your WordPress content always sounds more appealing until you run into problems and realize WordPress Migrate already solved those issues.
  • The WordPress Migrate module offers a lot of functionality, including file transfer, author migration, embedded video processing, internal link rewriting, comment migration, etc.
  • You might not need much custom code and just tweaking the WordPress Migrate functionality by extending one of its classes will easily do the trick.
  • You might not have the resources (time, knowledge, etc.) to write a custom Migrate class.
  • Running and testing the migrations on multiple environments might not be in your workflow, although I would argue it should be.
  • You might only have one or two WordPress sites to migrate content from, so manually re-creating them is not an issue.

If after weighing all of the factors, you decide using the WordPress Migrate module is in your best interest and manually recreating the migrations is not an option, then follow along as we walk you through our approach to creating and storing WordPress Migrate migrations programmatically.

Our Solution

First we need to define the list of source blogs. The keys of each item in this array can be added to as needed to override the default values we assign later.

/**
* Define the WordPress blogs to be imported.
*/

function example_wordpress_migrate_wordpress_blogs() {
// Any key not set here will default to the values set in the
// $blog_default_settings variable in the drush command.
$blogs = array(
array(
'domain' => 'www.example.com/site-one/',
),
array(
'domain' => 'www.example.com/site-two/',
),
array(
'domain' => 'www.test.com/',
),
);
return $blogs;
}

Next we'll create a custom drush command so that we can easily trigger the creation of our migrations from the command line.

/**
* Implements hook_drush_command().
*/

function example_wordpress_migrate_drush_command() {
$items = array();
// Creates WordPress migrations.
$items['example-migrate-create-wordpress-migrations'] = array(
'description' => 'Creates the WordPress migrations.',
'aliases' => array('mcwm'),
);

return $items;
}

Be sure to note the example_migrate_wordpress_password variable below, as you will need to ensure you set that in settings.php before creating the migrations. The WordPress Migrate code needs to be able to login to your site to download the source XML file, and a password is paramount to the success of that operation!

/**
* Callback for WordPress migration creation drush command.
*/

function drush_example_wordpress_migrate_create_wordpress_migrations() {
// Reset the file_get_stream_wrappers static cache so the 'wordpress' stream
// wrapper created by the wordpress_migrate module is available.
$wrappers_storage = &drupal_static('file_get_stream_wrappers', NULL, TRUE);
// The wordpress_migrate module's UI is a multi-step form that collects all
// configuration needed to migrate a given blog. As this form's steps are
// submitted and validated, an export file is downloaded for each blog and its
// contents are migrated. There is no easy way to export these settings or use
// code to provide that configuration and then trigger a migration, so the best
// bet is simulate the submission of those form steps with the needed data.
module_load_include('inc', 'migrate_ui', 'migrate_ui.wizard');
// Get a list of blogs to migrate.
$blogs = example_migrate_wordpress_blogs();
$blog_default_settings = array(
'source_select' => '1',
'domain' => '',
'username' => 'admin',
'password' => variable_get('example_migrate_wordpress_password', ''),
'wxr_file' => NULL,
'do_migration' => 0,
'default_author' => 'admin',
'page_type' => '',
'blog_post_type' => 'story',
'path_action' => 1,
'tag_field' => '',
'category_field' => '',
'attachment_field' => '',
'text_format' => 'filtered_html',
'text_format_comment' => 'filtered_html',
);
// Import each of the blogs.
foreach ($blogs as $blog_settings) {
// Combine the default settings and the custom per blog settings.
$blog_settings = array_merge($blog_default_settings, $blog_settings);
// Skip the import if no username or password was found.
if (empty($blog_settings['username']) || empty($blog_settings['password'])) {
$message = t('The :site-name migration was not created since no username and/or password could be found. Verify that the example_migrate_wordpress_password variable has been set.');
$replacements = array(
":site-name" => $blog_settings['domain'],
);
drupal_set_message(t($message, $replacements), 'warning');
continue;
}
// Set the form state values.
$form_state['values'] = $blog_settings;
// Store the values so we can use them again since $form_state is
// a reference variable.
$form_state_values = $form_state['values'];
// Build the import form.
$form = drupal_get_form('migrate_ui_wizard', 'WordPressMigrateWizard');
$form = migrate_ui_wizard($form, $form_state, 'WordPressMigrateWizard');
// Create a Migrate Wizard object.
$form_state['wizard'] = new WordPressMigrateWizard();
// Set the number of steps in the form.
$form_steps = 6;
// Go through all of the steps.
foreach (range(1, $form_steps) as $step) {
// Validate the form data.
$form_state['wizard']->formValidate($form_state);
// Submit the form page.
migrate_ui_wizard_next_submit($form, $form_state);
// Put any values removed from the array back in for the next step.
$form_state['values'] = array_merge($form_state_values, $form_state['values']);
}
// Submit the form.
drupal_form_submit('migrate_ui_wizard', $form_state);
// Save the settings into the wizard object.
$form_state['wizard']->formSaveSettings();
// Notify the user that the migration was created successfully.
$replacements = array(
'@site-name' => $blog_settings['domain'],
);
$message = t('The @site-name migration was successfully created.', $replacements);
drupal_set_message($message, 'success');
}
}

With all of this in place, the source WordPress sites and the configuration needed to import them are now fully defined in code along with a custom Drush command to create the required migrations. No longer will each individual site need to be re-entered through the UI introducing opportunities for mistakes and wasted time.

Now when you are in a new environment or after you reset your migrations, you can simply run drush mcwm.

Following its successful completion, the following are done for you:

  • A new Migrate group is created for each individual blog.
  • The actual Migrate classes within each group that migrate, authors, content, terms, and attachments are created and configured as defined in the code.
  • The source WordPress XML file is downloaded for each site and stored in wordpress://.

Then simply run drush ms to verify everything was created successfully, and you are ready to migrate some content!

Now that you have the tools and knowledge to evaluate your unique migration needs, you can make a well informed decision if this approach is right for you. However, we think that more often than not, all of the incredible functionality you get pre-built with the WordPress Migrate module will outweigh the issues that arise from not being able to fully build and store your migrations in code, especially when you add the functionality outlined above that gets you the best of both worlds. So have your cake and eat it too, and define your migrations in code and utilize the WordPress Migrate module while you are at it!

If you decide to go this route, all of the code referenced here is available in this gist. Please note that this was all built for WordPress Migrate 7.x-2.3, so future updates to the module could break this functionality.

Roadmap Your Drupal 7 Transition

We’re offering free 45 minute working sessions to help you assess your organizations level of risk, roadmap your transition plan, and identify viable options!

Drop us a note, and we’ll reach out to schedule a time.