A sample migration module - importing menu from CSV

It took some time for me to write a migrate module that imports menu links from a csv file. A seemingly simple task, but there was no sample available when I googled it. So, here it is.

class mySampleMenusMigration extends Migration {
  public function
__construct() {
   
parent::__construct(MigrateGroup::getInstance('mySampleMenusMigration'));
   
$this->description = t('Creates sample menues.');

   
// the csv file contains the header.
   
$dir = drupal_get_path('module', 'mySampleMigrate');
   
$this->source = new MigrateSourceCSV("$dir/sample_menus.csv", array(), array('header_rows' => 1));

   
$this->destination = new MigrateDestinationMenuLinks();

   
$this->map = new MigrateSQLMap($this->machineName,
      array(
       
'mlid' => array(
         
'type' => 'int',
         
'unsigned' => TRUE,
         
'not null' => TRUE,
         
'description' => 'ID of destination link',
        ),
      ),
     
MigrateDestinationMenuLinks::getKeySchema()
    );


   
$this->addFieldMapping('menu_name')->defaultValue('main-menu'); // Always Main menu
   
$this->addFieldMapping('plid', 'ref_parent')->sourceMigration($this->getMachineName());
   
$this->addFieldMapping('link_path', 'path');          // path of the link
   
$this->addFieldMapping('router_path')->defaultValue('node/%');
   
$this->addFieldMapping('link_title', 'title');        // Title of the menu item
   
$this->addFieldMapping('external')->defaultValue('0'); // Internal
   
$this->addFieldMapping('expanded')->defaultValue('0');
   
$this->addFieldMapping('weight','weight');            // weight
   
$this->addFieldMapping('customized')->defaultValue('1'); // not customized
   
$this->addFieldMapping('has_children')->defaultValue('0');  // Will be overridden automatically
   
$this->addFieldMapping('depth')->defaultValue('1'); // Will be overridden automatically

   
$this->addUnmigratedDestinations(array('module', 'hidden','options','p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9', 'updated'));

  }


   function
prepareRow($row) {
     
// Convert the alias to the node path.
     
$node_path = drupal_lookup_path('source', $row->path);
      if(
FALSE == $node_path)
      {
        return
FALSE;
      }
     
     
$row->path = $node_path;
   }


/**
   * <a href="http://drupal.org/node/1403044#comment-5790748
" title="http://drupal.org/node/1403044#comment-5790748
">http://drupal.org/node/1403044#comment-5790748
</a>   * Creates a stub menu link, for when a child is imported before its parent
   *
   * @param $migration
   *  The source migration
   * @return
   *  int $mlid on success
   *  FALSE on failure
   */
 
protected function createStub($migration) {
   
// if ref_parent is 0, that means it has no parent, so don't create a stub
   
if (!$migration->sourceValues->ref_parent) {
      return
FALSE;
    }
   
$menu_link = array (
     
'menu_name' => $migration->sourceValues->menu_name,
     
'link_path' => 'stub-path',
     
'router_path' => 'stub-path',
     
'link_title' => t('Stub title'),
    );
   
$mlid = menu_link_save($menu_link);
    if (
$mlid) {
      return array(
$mlid);
    }
    else {
      return
FALSE;
    }
  }

The key of importing menu is that the path must be a Drupal source path, not the alias. For example, if the alias is 'content/hello', the source path would be something like 'node/123'. The path of the menu link must be 'node/123', otherwise it doesn't work. Also, the router_path needs to be specified. In the case above, it is 'node/%'.

To obtain the source path from an alias, prepareRow() is implemented. It converts the url from an alias to the source path.

The sample CSV file looks like this.

mlid,ref_parent,path,title,weight
1
,0,"content/mySamplePage","The Sample",3
2
,1,"content/sample-1","Submenu1",0
3
,1,"content/sample-2","Submenu2",1
4
,1,"content/sample-3","Submenu3",2
?>

Note that ref_parent is referencing to the parent mlid within the CSV. The mlid will be replaced by the real mlid when the CSV is imported.

Hope this helps somebody.