Migrate Contents from Drupal 6 to Drupal 7 Using Migrate 2

I wouldn’t have thought that one day I’ll need this, but when I decided to rewrite Bounty Hunter with Drupal 7, learning Migrate 2 – the migration framework for Drupal – became my destiny. But trust me, you wouldn’t regret as it’s so flexible and powerful. With this you can turn all the sites to Drupal with ease.

This post shows an example of migrating contents from Drupal 6 to Drupal 7 for a content type.

Entities involved:

terms, nodes, comments

Field types involved:

Taxonomy to Entity Reference

Node Reference to Entity Reference

Text to Text

File to File

Pre-procedure

Before starting coding a migration class, you should clearly know the content structures of source (Drupal 6) and the destination (Drupal 7). And yes you’ll have to create the vocabularies, content types and fields you need on destination Drupal 7 site, Migrate 2 doesn’t build the structures for you, it only migrate contents (terms, nodes, comments, etc).

Here’s my content structure with the machine name used in modules. A content type for artworks from contests.

Content types

Source: Blog (blog)

Destination: Garden work (garden_work)

Vocabularies

Source: Garden work categories (garden_work_categories)

Destination: Garden work categories (garden_work_categories)

Source Fields (Drupal 6)

Title

Work Content (Body)

Attachments: field_file_attachment

Work Description (Text): field_work_desc

Category: Taxonomy module form

Contest (Node reference): field_garden_match

Prize Title (Text): field_work_prize

Prize (Text): field_award_content

Destination Fields (Drupal 7)

Title

Work Content (Body)

Attachments (File): field_garden_attachments

Description (Text): field_garden_description

Category (Term reference): field_garden_categories

Contest (Entity Reference): field_garden_contest

Award (Text): field_garden_award

Prize (Text): field_garden_prize

Create a custom migrate module

I create a bh_migrate.module with this codes.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86   /* * Define some constants to use in class */define("SOURCE_DATABASE",'the_source_db_name');define('SOURCE_TERM_GARDEN_CAT','the_id_of_source_vocabulary');define('SOURCE_URL','http://sourcewebsite.com');   /* * You must implement hook_migrate_api(), setting the API level to 2, for * your migration classes to be recognized by the Migrate module. */function bh_migrate_migrate_api(){$api=array('api'=>2,);return$api;}   /** * Translate between D6 input format id and D7 text format name. */function bh_migrate_get_text_format($format){$output='';   switch($format){case1:$output='filtered_html';break;case2:$output='full_html';break;case3:$output='php_code';break;default:$output='plain_text';}   return$output;}   /** * Retrieve the set of term associated with a node from the migration database. */function bh_migrate_get_terms($nvid,$tvid){$query= db_select(SOURCE_DATABASE .'.term_node','tn');$query->join(SOURCE_DATABASE .'.term_data','td','tn.tid = td.tid');$query->addField('td','name');$query->condition('tn.vid',$nvid,'=');$query->condition('td.vid',$tvid,'=');$query->orderBy('tn.tid','ASC');$result=$query->execute()->fetchCol();   returnis_array($result)&&!empty($result) ? implode(',',$result):NULL;}   /** * Retrieve the url alias associated with the node from the migration database. */function bh_migrate_get_url_alias($nid){$result= db_select(SOURCE_DATABASE .'.url_alias','ua')->fields('ua',array('dst'))->condition('ua.src','node/'.$nid,'=')->execute()->fetchObject();   returnis_object($result)&&!empty($result->dst) ? $result->dst:NULL;}   /** * Retrieve the new id from the old id */function bh_migrate_get_new_id($class,$id){$query= db_select('migrate_map_'.$class,'m')->fields('m',array('destid1'))->condition('m.sourceid1',$id,'=');$result=$query->execute();$output='';foreach($resultas$row){$output=$row->destid1;}   return$output;}
and a bh_migrate.info.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 name ="BH Migration" description ="Migration of data from Drupal 6" package ="Bounty Hunter" core =7.x dependencies[]= migrate dependencies[]= migrate_extras dependencies[]= comment dependencies[]= contact dependencies[]= field dependencies[]=file dependencies[]= text dependencies[]= path dependencies[]= statistics dependencies[]= taxonomy   files[]= garden.inc
#### Start coding the classes

Create a garden.inc specified in .info file.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29   /** * garden.inc * migrate garden works from Drupal 6 to Drupal 7 */   /** * Define a migration process */abstractclass BhGardenMigration extends Migration {publicfunction __construct(){// Always call the parent constructor first for basic setup parent::__construct();   // With migrate_ui enabled, migration pages will indicate people involved in// the particular migration, with their role and contact info. We default the// list in the shared class; it can be overridden for specific migrations.$this->team=array(new MigrateTeamMember('Hana','hana@bhuntr.com', t('Product Leader')),new MigrateTeamMember('Ben','ben@bhuntr.com', t('Technical Advisor')),);   // Define the external issue or ticket URL pattern here in the shared// class with ':id:' representing the position of the issue number, then add// ->issueNumber(1234) to a mapping.$this->issuePattern='https://github.com/bhuntr/n.bhuntr.com/issues/:id:';}}
Then in the same file, migration of term can be easy, just copy this and change SOURCE_TERM_GARDEN_CATEGORIES to your source vocabulary id (in .modules).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 /** * Migrate categories terms */class BhGardenCatMigration extends BhGardenMigration {publicfunction __construct(){ parent::__construct();   $this->description= t('Migrate the category for garden works.');   $this->map=new MigrateSQLMap($this->machineName,array('tid'=>array('type'=>'int','unsigned'=>TRUE,'not null'=>TRUE,'description'=>'D6 Unique Term ID','alias'=>'td',)), MigrateDestinationTerm::getKeySchema());   // We are getting data from tables in the Drupal default database - first,// set up a query for this data.$query= db_select(SOURCE_DATABASE .'.term_data','td')->fields('td',array('tid','vid','name','description','weight'))->condition('td.vid', SOURCE_TERM_GARDEN_CAT,'=');$query->join(SOURCE_DATABASE .'.term_hierarchy','th','td.tid = th.tid');$query->addField('th','parent');$query->orderBy('th.parent','ASC');   // Create a MigrateSource object, which manages retrieving the input data.$this->source=new MigrateSourceSQL($query);   // Set up our destination - term in this case.$this->destination=new MigrateDestinationTerm('garden_work_categories');   // Assign mappings TO destination fields FROM source fields.$this->addFieldMapping('name','name');$this->addFieldMapping('description','description');$this->addFieldMapping('format')->defaultValue('plain_text');$this->addFieldMapping('weight','weight');$this->addFieldMapping('parent','parent')->sourceMigration($this->getMachineName());   // Unmapped source fields$this->addUnmigratedSources(array('vid'));   // Unmapped destination fields$this->addUnmigratedDestinations(array('path','parent_name'));}   publicfunction prepareRow($current_row){if($current_row->parent==0){unset($current_row->parent);}returnTRUE;}}
Then it’s more complicated part, migrate the content. It’s better to use a mysql tool like phpmyadmin to see the actual source data. I recommend [Adminer](http://www.adminer.org/). This is a screenshot for it. ![](/content/images/2012/12/Screen-Shot-2012-12-11-at-下午1.51.52-950x292.png "Screen Shot 2012-12-11 at 下午1.51.52")

It’s easy to see the structure, cuz you’ll need to write a sql query, add the fields one by one.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 /** * BhGardenNodeMigration * migration to Garden work */class BhGardenNodeMigration extends BhGardenMigration {publicfunction __construct(){ parent::__construct();   $this->description= t('Migration of the work pieces to garden');$this->dependencies=array('BhGardenCat');$this->dependencies=array('BhMatchNode');// this is to migrate referenced node first   // Define the field which normally is a primary key$this->map=new MigrateSQLMap($this->machineName,array('nid'=>array('type'=>'int','unsigned'=>TRUE,'not null'=>TRUE,'description'=>'D6 Unique Node ID','alias'=>'n',)), MigrateDestinationNode::getKeySchema());   // Grab data from tables in source database$query= db_select(SOURCE_DATABASE .'.node','n')->fields('n',array('nid','vid','title','uid','status','created','changed','comment','promote','moderate','sticky','tnid','translate'))->condition('n.type','blog','=');$query->join(SOURCE_DATABASE .'.node_revisions','nr','n.vid = nr.vid');$query->fields('nr',array('body','teaser','format'));// Grab other fields which can mapping directly$query->join(SOURCE_DATABASE .'.content_type_blog','ct','ct.vid = n.vid');$query->addField('ct','field_work_desc_value');$query->addField('ct','field_work_prize_value');$query->addField('ct','field_award_content_value');$query->addField('ct','field_garden_match_nid');// set aliases for fields to be preprocessed$query->addField('ct','field_work_desc_format','disc_format');// If there is data from statistic.module, grab the view countsif(module_exists('statistics')){$query->join(SOURCE_DATABASE .'.node_counter','nc','n.nid = nc.nid');$query->addField('nc','totalcount');$query->addField('nc','daycount');$query->addField('nc','timestamp');}$query->orderBy('n.nid','ASC');   // Add source fields which not queried in $query, will be populated in prepareRow()$source_fields=array('url_alias'=> t('The url alias of the node'),'categories'=> t('The category for the node'),'attachments_filename'=> t('attachments_filename'),'attachments_filepath'=> t('attachments_filepath'),'attachments_description'=> t('attachments_description'),'referenced_nid'=> t('match_nid'),);   // Create a MigrateSource object, which manages retrieving the input data.$this->source=new MigrateSourceSQL($query,$source_fields);   // Set up our destination - nodes of type migrate_example_beer$this->destination=new MigrateDestinationNode('garden_work');   // Mapping: Assign mappings TO destination fields FROM source fields.// Simple Mappings : map the fields with the same name$this->addSimpleMappings(array('title','uid','created','changed','status','promote','sticky'));// Define default value for some fields$this->addFieldMapping('is_new')->defaultValue(TRUE);$this->addFieldMapping('revision')->defaultValue(TRUE);$this->addFieldMapping('language')->defaultValue('zh-hant');$this->addFieldMapping('revision_uid','uid');// Mapping of the body field$body_arguments= MigrateTextFieldHandler::arguments(array('source_field'=>'teaser'),array('source_field'=>'format'));$this->addFieldMapping('body','body')->arguments($body_arguments);// Mapping fields not need to be preprocessed (text, user reference to entity reference)$this->addFieldMapping('field_garden_desc','field_work_desc_value');$this->addFieldMapping('field_garden_prize','field_award_content_value');$this->addFieldMapping('field_garden_award','field_work_prize_value');$this->addFieldMapping('field_garden_contest','referenced_nid');   // Pass values from prepareRow()$this->addFieldMapping('path','url_alias');$this->addFieldMapping('field_garden_desc:format','disc_format');$this->addFieldMapping('field_garden_categories','categories');$this->addFieldMapping('field_garden_attachments','attachments_filename');$this->addFieldMapping('field_garden_attachments:source_dir','attachments_filepath');$this->addFieldMapping('field_garden_attachments:description','attachments_description');   // If there is data from statistic.module, map the view countsif(module_exists('statistics')){$this->addSimpleMappings(array('totalcount','daycount','timestamp'));}   // Unmapped source fields$this->addUnmigratedSources(array('vid','tnid','translate','teaser','format'));// Unmapped destination fields$this->addUnmigratedDestinations(array('log','tnid','translate','body:summary','body:format','body:language','comment'));}   publicfunction prepareRow($current_row){   // Set the text format for the node.$current_row->format= bh_migrate_get_text_format($current_row->format);   // Set the terms for the node.$current_row->categories= bh_migrate_get_terms($current_row->vid, SOURCE_TERM_GARDEN_CAT);   // Set the url alias for the node.$current_row->url_alias= bh_migrate_get_url_alias($current_row->nid);   // Set file data for the attachment file fields.$query= db_select(SOURCE_DATABASE .'.content_field_file_attachment','a')->fields('a',array('field_file_attachment_data'))->condition('a.vid',$current_row->vid,'=');$query->join(SOURCE_DATABASE .'.files','f','a.field_file_attachment_fid = f.fid');$query->addField('f','filename');$query->addField('f','filepath');$query->orderBy('a.field_file_attachment_fid','ASC');$result=$query->execute();   foreach($resultas$row){$field_data=unserialize($row->field_file_attachment_data);$field=array('filename'=>$row->filename,'filepath'=>str_replace($row->filename,'',$row->filepath),'description'=>$field_data['description'],);$current_row->attachments_filename[]=$field['filename'];$current_row->attachments_filepath[]= SOURCE_URL .$field['filepath'];$current_row->attachments_description[]=$field['description'];}   $current_row->referenced_nid= bh_migrate_get_new_id('bhmatchnode',$current_row->nid);   returnTRUE;}}
Then we migrate the comment, it’s relatively easy as the comment structure is simple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 /** * Migrate comments from Drupal 6 to Drupal 7 */class BhGardenCommentMigration extends BhGardenMigration {publicfunction __construct(){ parent::__construct();   $this->description= t('Migrate garden work node comments.');$this->dependencies=array('BhGardenNode');   $this->map=new MigrateSQLMap($this->machineName,array('cid'=>array('type'=>'int','unsigned'=>TRUE,'not null'=>TRUE,'description'=>'D6 Unique Comment ID','alias'=>'c',)), MigrateDestinationComment::getKeySchema());   // Grab data from tables in source database$query= db_select(SOURCE_DATABASE .'.comments','c')->fields('c',array('cid','pid','nid','uid','subject','comment','hostname','timestamp','status','thread','name','mail','homepage','format'));$query->leftJoin(SOURCE_DATABASE .'.node','n','c.nid = n.nid');$query->condition('n.type','blog','=');$query->orderBy('c.cid','ASC');   // Add other source fields which may be populated in prepareRow()$source_fields=array('new_nid'=> t('new id'),);   // Create a MigrateSource object, which manages retrieving the input data.$this->source=new MigrateSourceSQL($query,$source_fields);   // Set up our destination - comments in this case.$this->destination=new MigrateDestinationComment('comment_node_garden_work_post');   // Assign mappings TO destination fields FROM source fields.$this->addSimpleMappings(array('pid','uid','subject','hostname','thread','name','mail','homepage'));$this->addFieldMapping('nid','new_nid');$this->addFieldMapping('created','timestamp');$this->addFieldMapping('changed','timestamp');$this->addFieldMapping('status')->defaultValue(COMMENT_PUBLISHED);$this->addFieldMapping('language')->defaultValue(LANGUAGE_NONE);   $comment_body_arguments= MigrateTextFieldHandler::arguments(NULL,array('source_field'=>'format'));$this->addFieldMapping('comment_body','comment')->arguments($comment_body_arguments);   // Unmapped source fields$this->addUnmigratedSources(array('format','status'));   // Unmapped destination fields$this->addUnmigratedDestinations(array('path'));}publicfunction prepareRow($current_row){   // Set the correct text format for the comment.$current_row->format= bh_migrate_get_text_format($current_row->format);   // Set the correct nid for the comment.$current_row->new_nid= bh_migrate_get_new_id('bhgardennode',$current_row->nid);   returnTRUE;}}
That’s it!