Wordpress to Drupal 8 Migration

This blog post explains how to migrate a stock Wordpress site to Drupal 8 as of D8 beta3. We’ll be covering conversion of users, comments, pages and blog posts to Drupal format. You can find the code hosted on Github. If you’d like to contribute to the project of building a Wordpress Migrate module, please fork it from Github, do the improvements and then raise the pull requests.

The setup

Here’s an example of an existing WordPress site, which has registered users with comments, anonymous comments and an archive of previous postings with metadata (tags) that we will import. We’ll finish with this posting displayed in Drupal 8 version of the blog.

exploring wordpress

The following tables in Wordpress are relevant to the migration:

  • wp_posts (contains blog posts and pages)
  • wp_comments (comments)
  • wp_users (users)
  • wp_terms (categories - taxonomy)

We will started writing our migration module using Migration API (migrate) that comes packaged with D8 core. As usual in Drupal 8 module development, we had .info.yml, files, which uses the Symfony 2 YAML to map data types into an XML-like schema for easy migration from one environment to another, and traditional Drupal .module files. The only thing worth noting here is a dependency on the migrate module in our info.yml file.

For each Entity migration (users, posts, comments) we created 2 more files:

Code breakdown

Users

Let start understanding things by migrating Users first. Here is the complete user YAML file for this migration. Let’s break it down:

In this migrate.migration.users.yml file, ID, Source, Destination and Process (field mapping) are important keys.

id: users # Links this file with wordpress.migrate.yaml 
label: Wordpress Users 
migration_groups: 
 - Wordpress 

ID key is ‘users’ which has been referred in manifest_wordpress.yml and also in Source Plugin (User.php) as part of Annotation.

source:
 plugin: users 

Source key is referring to Source Plugin (User.php) created by us to fetch Wordpress users data, which we will see shortly in detail.

destination: 
 plugin: 
  entity:user

Destination key is user entity as defined by Migration API.

process: 
  uid: id  
  name: user_login  
  pass: user_pass
  mail: user_email
  status: 
  - plugin: 
  default_value
    default_value: 1
  created:
  - plugin: 
    callback
    callable: strtotime
    source: user_registered 

Process is key to mapping source (Wordpress) and destination (Drupal) fields. Some fields are mapped directly like ‘id’ with ‘sid’ while some fields are pre-processed by Migration API before being assigned to a destination field. For instance, ‘user_registered’ will be processed by PHP function strtotime() before it’s value gets assigned to destination field, because the target Drupal site must recognize those users as registered to associate their previous activity in WordPress with their Drupal activity on the new site. We can even write our own process plugins if required.

Now let’s look at Source Plugin (Users.php) which has been used to fetch the Wordpress users data,

<?php
/**
* @file
* Contains \Drupal\migrate_wordpress\Plugin\migrate\source\Users.
*/

namespace Drupal\migrate_wordpress\Plugin\migrate\source;

use Drupal\migrate\Row;
use Drupal\migrate\Plugin\SourceEntityInterface;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;

/**
* Extracts users from Wordpress database.
*
* @MigrateSource(
*   id = "users"
* )
*/

Remember the id field in the YAML file? The id field tells Drupal that this class should be used to process data for that migration set, taking the results of “users” in WordPress and adding them to a new table in the Drupal database, wp_users.

class Users extends DrupalSqlBase implements SourceEntityInterface {
  /**
   * {@inheritdoc}
   */
  public function query() {
    return $this->select('wp_users', 'u') 
     ->fields('u', array_keys($this->userFields()));
  }

The query() method is used to fetch records from Wordpress database table (wp_users).

/**
 * Returns the User fields to be migrated.
 * 
 * @return array 
 * Associative array having field name as key and description as value. 
 */ 
protected function userFields() {
  $fields = array(
    'id' => $this->t('User ID'),
    'user_login' => $this->t('Username'),
    'user_pass' => $this->t('Password'),
    'user_email' => $this->t('Email address'), 
    'user_registered' => $this->t('Created time'), 
  );
  return $fields; 
}

/**
 * {@inheritdoc}
 */
public function fields() {
  $fields = $this->userFields();
} 

userFields() method is called in query() and fields() methods. It contains the list of fields that describe a WordPress user along with their field descriptions.

/**
 * {@inheritdoc}
 */
public function bundleMigrationRequired() {
  return false;
}

/**
 * {@inheritdoc}
 */
public function entityTypeId() {
  return 'users';
}

/**
 * {@inheritdoc}
 */
public function getIds() {
  return array(
    'id' => array(
      'type' => 'integer',
      'alias' => 'u',
    ),
  );
}

As we are also implementing an interface SourceEntityInterface, we need to define 2 more methods namely bundleMigrationRequired() and entityTypeId() to map user-generated content, such as postings and comments, to Drupal. The getIds() method is used to return the key field for this Entity.

Posts

The Posts Entity migration (YAML, Source Plugin) is quite similar to User Entity migration. There is one additional method prepareRow() defined in post migration source plugin. This method gets called for each row to do an additional processing of the source data.

public function prepareRow(Row $row) {
  $post_type = $row->getSourceProperty('post_type');
  $type = $post_type == 'page' ? 'page' : 'article';
  $row->setSourceProperty('type', $type);
  return parent::prepareRow($row);
}

In this method, we are first checking the post type of source data and based on that we set the ‘type’ property. As we have two types of posts in Wordpress (Pages, Blogs posts) we want to map them to two content types in Drupal (Basic page, Article). The ‘type’ property is available in the post-migration YAML file Process step, as discussed above, and we have mapped it to the Drupal content type field.

Comments

The Comments Entity migration is more or less similar to Users and Posts migration. There are some following additions in Comments migration YAML file which are quite interesting to know.

uid: -
    plugin: skip_process_on_empty
    source: user_id -
    plugin: migration
    migration: users
'comment_body/value': comment_content
'comment_body/format': 'basic_html'
migration_dependencies:
required:
    - posts
    - users

Here we are using ‘skip_process_on_empty’ plugin which means skip the process if the source value is empty. As key fields can have different values in source and destination databases so we are using migration plugin to map ‘user_id’ field with ‘uid’ field. This will look for destination id in migration map tables for given source id (user_id).

Next, the ‘comment_body’ field is a text field and made up of two fields in Drupal 8, so we must map the Wordpress ‘comment_content’ field to value field and mapping basic html to format field. Next we have defined comment migration dependencies to posts and users which means comments will be migrated once both posts and users are already migrated.

Putting it all together

Finally you can run the migration using 'migrate-manifest' drush command as below. Make sure to run this command in Drush 7.

drush migrate-manifest manifest_wordpress.yml --legacy-db-url=mysql://{dbuser}:{dbpass}@localhost/{dbname}

The manifest_wordpress.yml is a YAML file which contains reference to IDs of each migration (users, posts, comments etc.).

# A Wordpress posts/users/comments migration, with dependencies.
 - posts
 - users
 - comments

Finished: WordPress postings shown in Drupal

wordpress postings drupal

Additional resources

For further reference on migration, please refer to given links,

Migrate API in Drupal 8 Migrate process Migrate Drupal module in Core