Wednesday 16 September 2015

Drupal to Drupal - Content Migration

At some point you will feel the need for a new cleaner website, so you build your new Drupal site with cleaner content types, and you want to migrate your old content to the new website. Migrate module is the module for that, however, by itself you will not be able to do your migration, you have to build your migration classes on top of that module, so below are the migration steps.
We start with creating a new module that will depend on Migrate module. Create worchestra_legacy.info
name = "Worchestra Legacy"
description = ""
core = "7.x"
package = "Worchestra"

files[] = migrate/base.inc
files[] = migrate/articles.inc
Create worchestra_legacy.module and implement hook_migrate_api to tell Migrate module about our coming migration classes
/**
 * Implements hook_migrate_api().
 */
function worchestra_legacy_migrate_api() {
  return array(
    'api' => 2,
  );
}
In our .info file we declared our 3 new files that need creation inside a /migrate directory in worchesra_legacy module Lets start with base.inc, and define our old database location, you can also use base.inc to create any function that you will use later in the migration process.
abstract class WorchestraLegacyBaseMigration extends Migration {

  /**
   * Should be used for all source queries in order to connect to the right
   * database. It's required that this target database is defined in
   * settings.php.
   */
  var $legacy_db_key = 'worchestra_old';

  public function __construct($group = NULL) {
    parent::__construct($group);
  }

  /**
   * Modified version of db_select() that uses another database connection.
   */
  protected function db_select($table, $alias = NULL, $options = array()) {
    if (empty($options['target'])) {
      $options['target'] = 'default';
    }
    return Database::getConnection($options['target'], $this->legacy_db_key)->select($table, $alias, $options);
  }

}
Now that we defined our old database connection so our module will have access to the posts in the old database, we need to add the declaration in settings.php
$databases['worchestra_old']['default'] = array(
  'driver' => 'mysql',
  'database' => 'worchestra_old',
  'username' => 'mysql_username',
  'password' => 'mysql_password',
  'host' => 'the_host',
);
Now create our articles.inc that will handle the articles migration
// Create a class that extends our base class
class WorchestraLegacyArticlesMigration extends WorchestraLegacyBaseMigration {
  public function __construct() {
    parent::__construct();
    $this->description = t('Import articles');

    $this->map = new MigrateSQLMap(
      $this->machineName,
      array(
        'nid' => array(
          'type' => 'int',
          'not null' => TRUE,
          'description' => 'Node ID',
          'alias' => 'n',
        )
      ),
      MigrateDestinationNode::getKeySchema(),
      $this->legacy_db_key
    );

    $this->highwaterField = array(
      'name' => 'changed',
      'type' => 'int',
      'alias' => 'n',
    );

    // Query source tables.
    $query = $this->db_select('node', 'n');
    $query->innerJoin('field_revision_body_old', 'fr', 'n.vid = fr.revision_id');
    $query->innerJoin('field_data_field_tags_old', 'fdft', 'fdft.entity_id = n.nid');
    $query->leftJoin('node_counter', 'nc', 'nc.nid = n.nid');

    // Query source fields.
    $query
      ->fields('n', array('nid','uid', 'title', 'created', 'changed', 'status', 'language'))
      ->fields('fr', array('body_value', 'body_format'))
      ->fields('nc', array('totalcount', 'timestamp'));
    $query->addField('n', 'nid', 'path');
    // Source grouping.
    // We need to query the tags on each post, and concatenate them with a comma
    $query->addExpression('GROUP_CONCAT(DISTINCT field_tags_tid)', 'tags');
    $query->groupBy('n.nid');
    // Source conditions.
    $query->condition('n.type', 'blog');
    $query->orderBy('n.changed', 'DESC');
    $this->source = new MigrateSourceSQL($query);
    

    // Destination definition.
    // I want to maintain the old node URLs so I will maintain the node ID's
    $this->destination = new MigrateDestinationNode('article');
    $this->addFieldMapping('nid', 'nid');
    $this->addFieldMapping('n_uid', 'uid');
    $this->addFieldMapping('is_new')
         ->defaultValue(TRUE);
    // Mapping
    $this->addFieldMapping('title', 'title');
    $this->addFieldMapping('created', 'created');
    $this->addFieldMapping('changed', 'changed');
    $this->addFieldMapping('status', 'status');
    $this->addFieldMapping('body_new', 'body_value');
    $this->addFieldMapping('totalcount', 'totalcount');
    $this->addFieldMapping('body:format')
      ->defaultValue('full_html');        
    $this->addFieldMapping('field_topics_new', 'tags')
      ->separator(',')
      ->arguments(array('source_type' => 'tid'));

    $this->addFieldMapping('sticky')
      ->defaultValue(0);
    $this->addFieldMapping('promote')
      ->defaultValue(0);
    $this->addFieldMapping('revision')
      ->defaultValue(1);
    $this->addFieldMapping('comment')
      ->defaultValue(2);
  }

}
Now if you go to admin/content/migrate you will see our our node migration class resulted in a checkbox to migrate the nodes. A good point to note here is that in our article.inc class we can use a method called prepare() to do any processing, modification, or cleaning up on any field before saving it in the new database.
This is a simple example on how to do content migrations, you can extend it to migrate taxonomies, users, and file entities.

1 comment:

  1. This looks good! Really good tutorial include so many helpful informations!

    ReplyDelete