Migrating LinksDB Module Data to CCK

LinksDB Module Screen ShotThe LinksDB module provides a nice "it just works" way for implementing a classic Links or Resources page. The standout feature is its hierarchical display of the URLs. Even after Views and CCK arrived, the hierarchical display was worth staying with the module. Sadly, with a site to upgrade and no Drupal 6 version of LinksDB in sight, it was time to convert.

This post is part 1 of 2 of how I migrated the CIPS Vancouver Security SIG Links Directory page from LinksDB to CCK/Views. It covers migrating LinksDB data into Drupal nodes. Creating a hierarchical display with Views is covered in part 2.

Creating a CCK node to replace the LinksDB URL entry is trivial. The two areas requiring work are migrating the data in the LinksDB database tables to standard Drupal data objects (CCK and Taxonomy), and creating a hierarchical listing of the URLs in Views.

Even if you're not interested in LinksDB, this post provides an example of programmatically importing data into Drupal using a command line script.

LinksDB also provides some user-facing tools for suggesting links, counting outgoing link clicks, and flagging dead links. Due to spam the suggestion tool was turned off; users have been savvy enough to use the contact form for suggesting links. The tool for flagging dead links didn't prove that useful and the tool for counting outgoing links was never used. These tools were therefore not re-created.

The CCK Node

The 4 fields defining a link entry are: name, URL, description, and category. Here's how I defined them in CCK.

name
Name is a standard text field. Although the node title could be used for the name field I've often found it convenient to split the name displayed to the user and the name shown in the administration interface. For example, using a name field instead of the node title allows the use of HTML in the name. If the name and title are always the same, the Automatic Nodetitles module can be used to generate the title from the name so the users aren't annoyed with entering the same data twice.
URL
The CCK Link module link field is used for the URL field. The LinksDB module displayed the full URL (e.g. http://www.example.com). This behaviour is reproduced by configuring the link field with the "No Title" option.
description
I choose to use the node body for the description. In hindsight, disabling the body field and using a separate text field for description may have been a better approach. The teaser baggage that comes with the body field may prove problematic.
category
A taxonomy vocabulary is used for representing category.

Attached to this post is a CCK content type export of the node definition described above. It requires the Link module. The CCK node was given the name "Directory Link".

Data Import

The LinksDB module uses two database tables. The first table holds the link categories, and is converted into the taxonomy vocabulary. The second table holds the actually link entries, and is converted into nodes.

The conversion was done with the a PHP Drupal command line script, shown below. It does the following:

  • Connects to database containing the old LinksDB information
  • Creates a Taxonomy Vocabulary named "Links Directory"
  • Converts the LinksDB category entries into a hierarchical taxonomy in the Links Directory vocabulary
  • Converts the active individual LinksDB link entries into Directory Link nodes, including the taxonomy term for the Links Directory vocabulary. Entries marked inactive are ignored.

linksdb_import.php Script

<?php

  $old_db_name
= 'dale_secsig';
 
$old_db_host = 'localhost';
 
$old_db_user = 'dale_secsiguser';
 
$old_db_pass = 'Password';

 
$stdout = fopen('php://stdout', 'w');
 
fwrite($stdout, "LinksDB Import\n");

 
$drupal_base_url = parse_url('http://www.example.com');
 
$_SERVER['HTTP_HOST'] = $drupal_base_url['host'];
 
$_SERVER['PHP_SELF'] = $drupal_base_url['path'].'/index.php';
 
$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'];
 
$_SERVER['REMOTE_ADDR'] = NULL;
 
$_SERVER['REQUEST_METHOD'] = NULL;

  require_once
'includes/bootstrap.inc';
 
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

 
fwrite($stdout, "=== Script Start\n\n");

  global
$user;
 
$user = user_load(array('uid' => 1));

 
//
  // Database setup for LinksDB information
  //
 
$old_db = mysql_connect($old_db_host, $old_db_user, $old_db_pass);
  if (!
$old_db) {
    die(
'Could not connect to ' . $old_db_host . ' as ' . $old_db_user . ': ' . mysql_error());
  }
 
$status = mysql_select_db($old_db_name, $old_db);
  if (!
$status) {
    die (
'Can\'t use ' . $old_db_name .': ' . mysql_error());
  }


 
//
  // Create a new vocabulary for links
  //
 
$vocabulary = array(
   
'name' => 'Links Directory',
   
'multiple' => 1,
   
'required' => 0,
   
'hierarchy' => 1,
   
'relations' => 0,
   
'weight' => 0,
   
'nodes' => array('links_directory' => 1),
  );
 
taxonomy_save_vocabulary($vocabulary);

 
$vid = $vocabulary['vid'];
 
 
$category_to_term_map = array(
   
0 => 0,
  );
 
$old_content = mysql_query('SELECT * FROM links_categories', $old_db);
  while (
$link_category = mysql_fetch_array($old_content)) {
   
$edit = array(
     
'vid'     => $vid,
     
'name'    => $link_category['name'],
     
'parent'  => array($category_to_term_map[$link_category['parent']]),
    );
   
taxonomy_save_term($edit);
   
$category_to_term_map[$link_category['id']] = $edit['tid'];
  }


 
//
  // Load up the links
  //
 
require_once 'modules/node/node.pages.inc';
 
$old_links = mysql_query('SELECT * FROM links_links WHERE ACTIVE = 1', $old_db);
  while (
$old_link = mysql_fetch_array($old_links)) {
   
$new_link = array(
     
'url'         => $old_link['url'],
     
'title'       => '',
     
'attributes'  => 'N',
    );
   
$tid = $category_to_term_map[$old_link['category']];
   
$term = taxonomy_get_term($tid);
   
$node = new stdClass();
   
$node->type = 'directory_link';   // Your specified content type
   
node_object_prepare($node);
   
$node->title            = $old_link['name'];
   
$node->field_link_name  = array(array('value' => $old_link['name']));
   
$node->field_link       = array($new_link);
   
$node->body             = $old_link['description'];
   
$node->taxonomy         = array($tid => $term);
   
node_save($node);
   
fwrite($stdout, "Added: " . $node->title . "\n");
  }

?>

The CCK Directory Link node type must be created before the script is run. The script can be rerun without causing errors, though each time it's run it creates a new taxonomy vocabulary named "Links Directory" and all of the content.

AttachmentSize
Plain text icon DirectoryLinkCCKDef.txt4.31 KB
Plain text icon linksdb_import.php2.75 KB

Comments

Thanks for sharing. You have a really neat implementation of hierarchical display with Views. I am lookig forward to part 2 of this article.
Also wondering why you have only 2 levels of nesting? Is this because of vocabulaly was simple 2 levels or you have seen any performance issues going deeper than 2 levels?

You're welcome. I'm glad you're finding it interesting.

The depth shown is simply what I captured in the screen shot (which is of the original LinksDB page). Because the graphic is relatively small, I didn't want the text to get too small. I believe LinksDB had no limit on depth. The View will go as deep as your taxonomy tree.

Hello Dale,
Nice to meet you last night at the meet.
Thanks again for the information.
chris

Nice meeting you last night! Happy to see you found the post, ok.