by Jeff EatonJune 26, 2009

The Great Pretender: Making your data act like a field

These days, almost every major Drupal site is using CCK, the module that lets you add custom fields to any content type. Among other things, CCK lets administrators rearrange a node type's contents using a simple drag and drop interface. In the past, this only worked for fields that CCK itself managed. If you worked with a custom module that altered a node's content, it was up to you to manage its position in the node content.

Now, though, it's possible for any module to tie into CCK's field management page to control the positioning of custom content. The key is hook_content_extra_fields(), and in this article we'll show you how to use it.

null
To demonstrate this technique, we'll fix something in Drupal that normally can't be re-ordered: the contextual links that go below each node, like "Add a comment" and "Bookmark this." While a Drupal theme can tweak the location of those links, there's no way to move them around from the administrative UI, and no easy way to position those links between two CCK fields.

The first step is to use hook_nodeapi() to create a new entry in the $node->content array that contains the rendered links. $node->content is a collection of data that's ultimately used to build the $content variable used by themes when printing a node. Data inside of $node->content can be easily tweaked and reordered by any module.

  
/**
 * Implementation of hook_nodeapi().
 */
function link_mover_nodeapi(&$node, $op, $teaser, $page) {
  if ($op == 'view') {
    $links = module_invoke_all('link', 'node', $node, $teaser);
    drupal_alter('link', $links, $node);

    if (!empty($links)) {
      $output = theme('links', $links, array('class' => 'links inline'));
      $weight = content_extra_field_weight($node->type, 'links');

      $node->content['links'] = array(
        '#weight' => !empty($weight) ? $weight : 100,
        '#value' => $output,
      );
    }
  }
}
  

One of the key lines in that function is the call to content_extra_field_weight(). It's a utility function provided by the CCK module that returns the current 'weight' of a given item in relation to other parts of a node's content. If CCK isn't keeping track of the item we ask about, it will return a zero -- the 'default' weight of an item. If that happens, we substitute 100, so that the links will fall to the bottom of the node's content by default. How, though, can we get CCK to handle our new "links" element?

  
/**
 * Implementation of hook_content_extra_fields.
 */
function link_mover_content_extra_fields() {
  $extras['links'] = array(
    'label' => t('Node links'),
    'description' => t('Links displayed when a node is viewed.'),
    'weight' => 100,
  );
  return $extras;
}
  

hook_content_extra_fields() is provided by CCK as well; it gives modules a chance to tell it what items they have that need to be considered when reordering a node's component fields. In it, we just need to return an array defining the name, description, and default weight of our item. Once we've done that, visiting the CCK 'Manage Fields' page for a given content type will give us the following:

null

Ta-da! CCK now lets us reorder the node links like any other field. There's only one piece left, though: removing the default $links variable so that the theme won't print it out in addition to our reorder-able version. That's easy enough, using hook_preprocess_node().

  
/**
 * Implementation of hook_preprocess_node().
 */
function link_mover_preprocess_node(&$vars) {
  unset($vars['links']);
}
  

Once that's in place (and we've cleared the cache to ensure Drupal recognizes the new preprocess function), everything should work fine. Below is a screenshot of the final results after moving the 'Links' item above the node's body and other fields. I've also attached a zip file containing the sample code. Feel free to tweak it and experiment -- CCK is immensely popular, and tying into its configuration forms is a great way to make things easier for a site's administrators.

null