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()
.
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Implements hook_migrate_prepare_node().<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">mymodule_migrate_prepare_node</span><span style="color: #007700">(&</span><span style="color: #0000BB">$node</span><span style="color: #007700">, </span><span style="color: #0000BB">$tblinfo</span><span style="color: #007700">, &</span><span style="color: #0000BB">$row</span><span style="color: #007700">) {<br> static </span><span style="color: #0000BB">$vocabs</span><span style="color: #007700">, </span><span style="color: #0000BB">$source_vocabs</span><span style="color: #007700">;<br> </span><span style="color: #0000BB">$errors </span><span style="color: #007700">= array();<br><br> </span><span style="color: #FF8000">// Get a list of vocabs in our target database.<br> </span><span style="color: #007700">if (empty(</span><span style="color: #0000BB">$vocabs</span><span style="color: #007700">)) {<br> </span><span style="color: #0000BB">$result </span><span style="color: #007700">= </span><span style="color: #0000BB">db_query</span><span style="color: #007700">(</span><span style="color: #DD0000">"SELECT vid, name FROM {vocabulary}"</span><span style="color: #007700">);<br> while (</span><span style="color: #0000BB">$vrow </span><span style="color: #007700">= </span><span style="color: #0000BB">db_fetch_object</span><span style="color: #007700">(</span><span style="color: #0000BB">$result</span><span style="color: #007700">)) {<br> </span><span style="color: #0000BB">$vocabs</span><span style="color: #007700">[</span><span style="color: #0000BB">$vrow</span><span style="color: #007700">-></span><span style="color: #0000BB">name</span><span style="color: #007700">] = </span><span style="color: #0000BB">$vrow</span><span style="color: #007700">-></span><span style="color: #0000BB">vid</span><span style="color: #007700">;<br> }<br> }<br><br> </span><span style="color: #FF8000">// Set up per-node type specific stuff.<br> </span><span style="color: #0000BB">$node_vocabs </span><span style="color: #007700">= array();<br> switch (</span><span style="color: #0000BB">$node</span><span style="color: #007700">-></span><span style="color: #0000BB">type</span><span style="color: #007700">) {<br> case </span><span style="color: #DD0000">'event'</span><span style="color: #007700">:<br> </span><span style="color: #FF8000">// Here the 1 and 0 identify which are free-tagging vocabs and which aren't.<br> </span><span style="color: #0000BB">$node_vocabs </span><span style="color: #007700">= array(</span><span style="color: #DD0000">'Regions' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">, </span><span style="color: #DD0000">'Keywords' </span><span style="color: #007700">=> </span><span style="color: #0000BB">1</span><span style="color: #007700">, </span><span style="color: #DD0000">'Topics' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">);<br> break;<br> case </span><span style="color: #DD0000">'news'</span><span style="color: #007700">:<br> </span><span style="color: #0000BB">$node_vocabs </span><span style="color: #007700">= array(</span><span style="color: #DD0000">'Keywords' </span><span style="color: #007700">=> </span><span style="color: #0000BB">1</span><span style="color: #007700">, </span><span style="color: #DD0000">'Topics' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">);<br> break;<br> }<br><br> </span><span style="color: #FF8000">// I have 2 database connections defined in my settings.php.<br> // This statement allows me to use the source Drupal database for subsequent queries.<br> </span><span style="color: #0000BB">db_set_active</span><span style="color: #007700">(</span><span style="color: #DD0000">'old_drupal_db'</span><span style="color: #007700">);<br><br> </span><span style="color: #FF8000">// Get vocabs from our source database.<br> </span><span style="color: #007700">if (empty(</span><span style="color: #0000BB">$source_vocabs</span><span style="color: #007700">)) {<br> </span><span style="color: #0000BB">$result </span><span style="color: #007700">= </span><span style="color: #0000BB">db_query</span><span style="color: #007700">(</span><span style="color: #DD0000">"SELECT vid, name FROM {vocabulary}"</span><span style="color: #007700">);<br> while (</span><span style="color: #0000BB">$vrow </span><span style="color: #007700">= </span><span style="color: #0000BB">db_fetch_object</span><span style="color: #007700">(</span><span style="color: #0000BB">$result</span><span style="color: #007700">)) {<br> </span><span style="color: #0000BB">$source_vocabs</span><span style="color: #007700">[</span><span style="color: #0000BB">$vrow</span><span style="color: #007700">-></span><span style="color: #0000BB">name</span><span style="color: #007700">] = </span><span style="color: #0000BB">$vrow</span><span style="color: #007700">-></span><span style="color: #0000BB">vid</span><span style="color: #007700">;<br> }<br> }<br><br><br> </span><span style="color: #FF8000">// Map each node to its taxonomy terms.<br> </span><span style="color: #007700">if (!empty(</span><span style="color: #0000BB">$node_vocabs</span><span style="color: #007700">)) {<br> foreach (</span><span style="color: #0000BB">$node_vocabs </span><span style="color: #007700">as </span><span style="color: #0000BB">$vname </span><span style="color: #007700">=> </span><span style="color: #0000BB">$tags</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$terms </span><span style="color: #007700">= array();<br> </span><span style="color: #0000BB">$result </span><span style="color: #007700">= </span><span style="color: #0000BB">db_query</span><span style="color: #007700">(</span><span style="color: #DD0000">"SELECT d.name FROM {term_data} d, {term_node} n WHERE n.tid = d.tid AND n.nid = %d AND d.vid = %d"</span><span style="color: #007700">, </span><span style="color: #0000BB">$row</span><span style="color: #007700">-></span><span style="color: #0000BB">nid</span><span style="color: #007700">, </span><span style="color: #0000BB">$source_vocabs</span><span style="color: #007700">[</span><span style="color: #0000BB">$vname</span><span style="color: #007700">]);<br> while (</span><span style="color: #0000BB">$term </span><span style="color: #007700">= </span><span style="color: #0000BB">db_fetch_object</span><span style="color: #007700">(</span><span style="color: #0000BB">$result</span><span style="color: #007700">)) {<br> </span><span style="color: #0000BB">$terms</span><span style="color: #007700">[] = </span><span style="color: #0000BB">$term</span><span style="color: #007700">-></span><span style="color: #0000BB">name</span><span style="color: #007700">;<br> }<br> <br> </span><span style="color: #FF8000">// Depending on whether it's a free-tagging vocabulary or not, the terms are stored slightly differently.<br> </span><span style="color: #0000BB">$vid </span><span style="color: #007700">= </span><span style="color: #0000BB">$vocabs</span><span style="color: #007700">[</span><span style="color: #DD0000">"</span><span style="color: #0000BB">$vname</span><span style="color: #DD0000">"</span><span style="color: #007700">];<br> </span><span style="color: #0000BB">$vid_key </span><span style="color: #007700">= </span><span style="color: #DD0000">'migrate_taxonomy_' </span><span style="color: #007700">. </span><span style="color: #0000BB">$vid</span><span style="color: #007700">;<br> if (!empty(</span><span style="color: #0000BB">$terms</span><span style="color: #007700">)) {<br> if (</span><span style="color: #0000BB">$tags</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$node</span><span style="color: #007700">-></span><span style="color: #0000BB">$vid_key </span><span style="color: #007700">= </span><span style="color: #DD0000">'"' </span><span style="color: #007700">. </span><span style="color: #0000BB">implode</span><span style="color: #007700">(</span><span style="color: #DD0000">'"' </span><span style="color: #007700">. </span><span style="color: #0000BB">$tblinfo</span><span style="color: #007700">-></span><span style="color: #0000BB">multiple_separator </span><span style="color: #007700">. </span><span style="color: #DD0000">'"'</span><span style="color: #007700">, </span><span style="color: #0000BB">$terms</span><span style="color: #007700">) . </span><span style="color: #DD0000">'"'</span><span style="color: #007700">;<br> }<br> else {<br> </span><span style="color: #0000BB">$node</span><span style="color: #007700">-></span><span style="color: #0000BB">$vid_key </span><span style="color: #007700">= </span><span style="color: #0000BB">implode</span><span style="color: #007700">(</span><span style="color: #0000BB">$tblinfo</span><span style="color: #007700">-></span><span style="color: #0000BB">multiple_separator</span><span style="color: #007700">, </span><span style="color: #0000BB">$terms</span><span style="color: #007700">);<br> }<br> }<br> }<br> }<br><br> </span><span style="color: #FF8000">// Switch back to using the default, aka target, Drupal database.<br> </span><span style="color: #0000BB">db_set_active</span><span style="color: #007700">(</span><span style="color: #DD0000">'default'</span><span style="color: #007700">);<br><br> return </span><span style="color: #0000BB">$errors</span><span style="color: #007700">;<br>}<br></span><span style="color: #0000BB">?></span></span>
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 |.