How to migrate a book from Drupal 6 to a menu in Drupal 7 Recently I came across a situation where I had to migrate a book with lots of links into a menu (since the customer wanted a simple navigational menu). 

I came up with some code that worked pretty fine, so I decided to share it as an answer. 

If you believe it could have been done in a better way, please feel free to comment and propose your ideas and methods. This code requires you to be somewhat familiar with the Migration process and the Migrate module.
To begin with we define a class derived from Migration.

&lt;?php


/**
 * To define a migration process from a set of source data to a particular
 * kind of Drupal object (for example, a specific node type), you define
 * a class derived from Migration. You must define a constructor to initialize
 * your migration object. By default, your class name will be the "machine name"
 * of the migration, by which you refer to it. Note that the machine name is
 * case-sensitive.
 *
 * In any serious migration project, you will find there are some options
 * which are common to the individual migrations you're implementing. You can
 * define an abstract intermediate class derived from Migration, then derive your
 * individual migrations from that, to share settings, utility functions, etc.
 */
abstract class MenuMigration extends DynamicMigration {

public function __construct() {

# Always call the parent constructor first for basic setup.

parent::__construct(MigrateGroup::getInstance('Menu'));

}
}


We now create a class that extends the above.

class EducationalGuideMigration extends MenuMigration {

public function __construct() {

parent::__construct();

#Define variable for the default user id (user="grammateia") wherever it is used (uid, revision_uid, etc.). 
$user=36;

# Human-friendly description of your migration process. Be as detailed as you like.

$this-&gt;description = t('Migrate the "Studies Guide" menu');


We create the mapping in the database and our query.

$this-&gt;map = new MigrateSQLMap($this-&gt;machineName,
    array(
      'mlid' =&gt; array('type' =&gt; 'int',
                       'not null' =&gt; TRUE,
                       'description' =&gt; 'Menu link ID',
                       'alias' =&gt; 'ml'
                      )
    ),
    MigrateDestinationMenuLinks::getKeySchema());

$query = Database::getConnection('default', 'migrationDB')
           -&gt;select('book', 'b'); 
$query-&gt;leftJoin('menu_links', 'ml', 'ml.mlid = b.mlid');
$query-&gt;fields('b', array('nid', 'mlid'))
        -&gt;fields('ml', array('menu_name','mlid', 'plid', 'link_path', 'router_path', 'link_title','options', 'module', 'hidden', 'external', 'has_children', 'expanded', 'weight', 'depth','customized', 'p1', 'p2','p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9', 'updated'))
        -&gt;condition('bid', 30, '=')
        -&gt;orderBy('ml.mlid');


As you can see we create our mapping based on the mlid (menu link id) primary key. You can find this key in the menu_links table and the book table. The book table holds only keys, namely nid, mlid and nid. The menu_links table holds all the info pertaining any sort of navigation menu in your drupal site, from your custom menus to administrative ones.

Luckily, Migrate provides us with a migration class for our mapping called "MigrateDestinationMenuLinks".

We use left join to join the two tables mentioned above. Obviously we need to know from which book we have to get our data so the book with id=30 is the one I was interested in.

We then execute the query and provide the destination menu plus anything we won't migrate (these unmigrated fields get default values).

# Create a MigrateSource object, which manages retrieving the input data.
$this-&gt;source = new MigrateSourceSQL($query);

# Set up our destination menu
$this-&gt;destination = new MigrateDestinationMenuLinks();

/**
 * SOURCE FIELDS NOT TO BE MIGRATED 
 */

$this-&gt;addUnmigratedSources(array ('module', 'options', 'has_children', 'depth','hidden', 'updated', 'external', 'customized', 'expanded', 'link_path', 'router_path', 'mlid', 'menu_name', 'mlid'));

    /**
 * DESTINATION FIELDS NOT TO BE MIGRATED
 */

$this-&gt;addUnmigratedDestinations (array('module', 'options', 'has_children', 'depth','hidden', 'updated'));


Now it's time to map the fields that we do need to migrate.

  /**
 * FIELDS TO BE MIGRATED
 */

$this-&gt;addFieldMapping('menu_name')
        -&gt;defaultValue("menu-studiesguide");
$this-&gt;addFieldMapping('plid', 'plid')-&gt;sourceMigration($this-&gt;getMachineName());
$this-&gt;addFieldMapping('link_path', 'link_path');
$this-&gt;addFieldMapping('router_path')-&gt;defaultValue('node/%');
$this-&gt;addFieldMapping('link_title', 'link_title');
$this-&gt;addFieldMapping('external')-&gt;defaultValue('0');
$this-&gt;addFieldMapping('expanded')-&gt;defaultValue('0');
$this-&gt;addFieldMapping('weight', 'weight');
$this-&gt;addFieldMapping('customized')-&gt;defaultValue('1');
$this-&gt;addFieldMapping('p1', 'p1')-&gt;sourceMigration($this-&gt;getMachineName());
$this-&gt;addFieldMapping('p2', 'p2')-&gt;sourceMigration($this-&gt;getMachineName());
$this-&gt;addFieldMapping('p3', 'p3')-&gt;sourceMigration($this-&gt;getMachineName());
$this-&gt;addFieldMapping('p4', 'p4')-&gt;sourceMigration($this-&gt;getMachineName());
$this-&gt;addFieldMapping('p5', 'p5')-&gt;sourceMigration($this-&gt;getMachineName());
$this-&gt;addFieldMapping('p6', 'p6')-&gt;sourceMigration($this-&gt;getMachineName());
$this-&gt;addFieldMapping('p7', 'p7')-&gt;sourceMigration($this-&gt;getMachineName());
$this-&gt;addFieldMapping('p8', 'p8')-&gt;sourceMigration($this-&gt;getMachineName());
$this-&gt;addFieldMapping('p9', 'p9')-&gt;sourceMigration($this-&gt;getMachineName());


In the menu name mapping we provide as a default value, our menu's machine name. (whatever name you gave is appended with the "menu-" prefix by Drupal).

Now you will notice that for the fields plid and p1 to p9 I have added this argument:

-&gt;sourceMigration($this-&gt;getMachineName());


First of all plid and p1 to p9 are all menu link ids. Plid is the immediate parent of every mlid and p1 to p9 is essentially our navigation menu for every menu branch.

What the argument does is pretty simple. It prety much tells our migration, "You know what, I got all the mlids from the query so any parent id you create is gonna be one of those menu link ids". (Note: that is why in our query we order by mlid).

So now ok, we have our menu, however the menu items are supposed to have links that correspond to nodes right? Well the book I had to migrate had 2 different content types which I also migrated, but their nodes were not assigned to these menu links. Also the nids I can get from the Drupal 6 are not the ones I need to assign to my menu links. So what do we do? Enter prepareRow().

function prepareRow($row) {

    $current_nid=$row-&gt;nid;

    $querymmi = Database::getConnection('default')
            -&gt;select('migrate_map_idrimata', 'mmi');
    $querymmi-&gt;fields('mmi', array('destid1', 'sourceid1'))
            -&gt;condition('sourceid1', $current_nid, '=');

    $resultmmi = $querymmi-&gt;execute();

    if($resultmmi-&gt;rowCount()==0){

        $querymms = Database::getConnection('default')
            -&gt;select('migrate_map_studies', 'mms');
        $querymms-&gt;fields('mms', array('destid1', 'sourceid1'))
            -&gt;condition('sourceid1', $current_nid, '=');

        $resultmms = $querymms-&gt;execute();

        if($resultmms-&gt;rowCount()==0){
            print "EVALA TH XAROULA EDW \n";
            print "path: node/9118 \n";
            $row-&gt;link_path = "node/9118";
        }

        else{
            print "STUDIES PATH INSERTED SUCCESFULLY \n";
            $row-&gt;link_path = "node/" . $resultmms-&gt;fetchField(0);
        }
    }

    else{
        print "IDRIMATA PATH INSERTED SUCCESFULLY \n";
        $row-&gt;link_path = "node/" . $resultmmi-&gt;fetchField(0);
    }
   }


So what we do here is this. Every time you migrate nodes a migrate map is created that corresponds drupal 6 nids to Drupal 7 ones. So we query those tables (as I mentioned earlier my book had 2 different content types) and we compare every nid we get from this migration's query with the source nids from our map tables.

We use this "if" condition to handle any case where no results are returned in our comparison:

if($your_result_variable_here-&gt;rowCount()==0)


The first condition checks if we have nids from our first map table, if not go check the other one.
The second does the same check, but the reason to actually have it there is to provide a default value to a menu link if our condition doesn't return true (highly unlikely but it happens).

And finally how do we provide the correct nid to our menu link? Well be it either a default value or one of the node ids this is what we do:

$row-&gt;link_path = "node/" . $resultmms-&gt;fetchField(0);


Get the link_path field from every row of our query, concatenate the string "node/" with the nid that we get from our result set. The fetchField(array index # here) does that.

So that was it. Hope my explanation was clear enough and it actually helps out.