Category Technology
Publication date
10 March 2010

Migrate module: migrating a node's taxonomy terms

One of the issues I encountered when migrating nodes to Drupal, using the migrate module, was that I couldn't associate nodes with more than one taxonomy term. Actually in this example, I'm migrating content from one Drupal database to another, so I'm going to assume everyone is already familiar with the database structure, specifically the node and term_node tables.

When I first started using the migrate module, I ran into a similar problem with migrating a user's roles. It's not possible to just create a Views relationship (aka LEFT JOIN) between the node and term_node tables using the node id. This will produce one row for each node and taxonomy combination, but the migrate module is only able to handle data sets that contain one row for each entity. With the above solution, I have more than one row for each node, which causes the migrate module to import the same node more than once, causing all sorts of problems.

Like with the user roles example before, we can overcome this by implementing a migrate hook, specifically hook_migrate_prepare_node().


<?php
/**
 * Implements hook_migrate_prepare_node().
*/

function mymodule_migrate_prepare_node(&$node, $tblinfo, &$row) {
  static $vocabs, $source_vocabs;
  $errors = array();

  // Get a list of vocabs in our target database.
  if (empty($vocabs)) {
    $result = db_query("SELECT vid, name FROM {vocabulary}");
    while ($vrow = db_fetch_object($result)) {
      $vocabs[$vrow->name] = $vrow->vid;
    }
  }

  // Set up per-node type specific stuff.
  $node_vocabs = array();
  switch ($node->type) {
    case 'event':
      // Here the 1 and 0 identify which are free-tagging vocabs and which aren't.
      $node_vocabs = array('Regions' => 0, 'Keywords' => 1, 'Topics' => 0);
      break;
     case 'news':
      $node_vocabs = array('Keywords' => 1, 'Topics' => 0);
      break;
  }

  // I have 2 database connections defined in my settings.php.
  // This statement allows me to use the source Drupal database for subsequent queries.
  db_set_active('old_drupal_db');

  // Get vocabs from our source database.
  if (empty($source_vocabs)) {
    $result = db_query("SELECT vid, name FROM {vocabulary}");
    while ($vrow = db_fetch_object($result)) {
      $source_vocabs[$vrow->name] = $vrow->vid;
    }
  }


  // Map each node to its taxonomy terms.
  if (!empty($node_vocabs)) {
    foreach ($node_vocabs as $vname => $tags) {
      $terms = array();
      $result = db_query("SELECT d.name FROM {term_data} d, {term_node} n WHERE n.tid = d.tid AND n.nid = %d AND d.vid = %d", $row->nid, $source_vocabs[$vname]);
      while ($term = db_fetch_object($result)) {
        $terms[] = $term->name;
      }
      
      // Depending on whether it's a free-tagging vocabulary or not, the terms are stored slightly differently.
      $vid = $vocabs["$vname"];
      $vid_key = 'migrate_taxonomy_' . $vid;
      if (!empty($terms)) {
        if ($tags) {
          $node->$vid_key = '"' . implode('"' . $tblinfo->multiple_separator . '"', $terms) . '"';
        }
        else {
          $node->$vid_key = implode($tblinfo->multiple_separator, $terms);
        }
      }
    }
  }

  // Switch back to using the default, aka target, Drupal database.
  db_set_active('default');

  return $errors;
}
?>

Note, for each vocab for a node type, I identify whether or not it's a free-tagging one or not. This is because I handle them slightly differently because of the way taxonomy module treats them, and to avoid problems with commas and quotes within terms, etc. In addition, there can be problems if you use commas as your separator, so when creating the content set I set the multiple separator ($tblinfo->multiple_separator) to be a pipe |.

 

Profile picture for user Stella Power

Stella Power Managing Director

As well as being the founder and managing director of Annertech, Stella is one of the best known Drupal contributors in the world.