Drupal solutions for news sites: Multiple authors for nodes

5 Jun

As you might imagine, a significant part of my job at the Observer has involved creating sites that make publishing news online easier. I am, and always have been, a staunch believer in the fact that Drupal is one of the best web frameworks for news and publishing. The plethora of news organizations that use it now or are planning to use it soon bears me out on this one. That being said, Drupal's base functionality does fall short in several instances for a lot of common things that news requires. So, I'm going to describe common solutions that I've come to know in the course of customizing Drupal for several different news sites.
<!–break–>
I don't propose rolling these concepts into Drupal core; Drupal should remain as general as possible. This is just a cookbook for people who need to create a robust newsy kind of site. These are the questions and requests you're likely to get from the editorial side. All these solutions assume Drupal 6.x.

Solution 1: “We need multiple authors for any given story.”

First of all, you'll need to install CCK if you haven't already. Handling multiple authors is best done using a CCK user reference field attached to the node type that will be used for articles. Essentially, we'll be disregarding the uid column of the node table. This is fine anyway — often the person who “authors” the node will be a producer and not the credited writer of the story (in the workflow of a typical newsroom, anyway).

Add a user reference field and call it field_authors. Allow it to reference only a “writers” role, if you prefer, or leave that blank to allow all roles. You may as well set number of values to unlimited and the widget type to autocomplete. I've found this is what producers vastly prefer, vis-a-vis dropdowns.

Next, we need to make theme changes so that the authors specific by this field be used in lieu of the node's built-in author field. You can do this entirely in your theme's template.php. This should do the trick:

function mytheme_node_submitted($node) {
  return t('By !username | @datetime',
    array(
      '!username' => mytheme_article_authors($node),
      '@datetime' => date('l, F j, Y \a\t g:i a', $node->created),
    ));
}
 
function mytheme_article_authors(&$node) {
  $authors = array();
  if (count($node->field_authors)) {
    foreach ($node->field_authors as $author) {
      $user = user_load($author['uid']);
      if ($user->uid) {
        $authors[] = l($user->name, 'user/' . $user->uid);
      }
    }
  }
  if (count($authors) > 1) {
    $last_author = array_pop($authors);
    return implode(", ", $authors) . " and " . $last_author;
  }
  if (count($authors) == 1) {
    return $authors[0];
  }
  return FALSE;
}

The first function will override the $submitted variable passed to your node templates, so make sure to substitute “mytheme” for the machine-readable actual name of your theme. The second function is a helper; name it whatever you like. Note also that I took some liberties with the format of the date. The first argument of t() can be any string you like. Feel free to use the Drupal default “Submitted by !username on @datetime” or any permutation you like. See the docs for PHP's date() function for details on the format string for the creation date.

You could stop there if you want. However, it's likely that you want to have good user profile pages that list stories a user has written. It's pretty straightforward to add a list to their user profile, but you're going to need to query their user reference field instead of the node table. So, you can add some variables to the user-profile.tpl.php file by putting the following in template.php:

function mytheme_preprocess_user_profile(&$vars) {
  if (isset($vars['account']->uid)) {
    $result = db_query("SELECT n.nid FROM {node} n 
                        LEFT JOIN {content_field_authors} c ON n.nid=c.nid 
                        WHERE n.type='article' AND c.field_authors_uid=%d 
                        ORDER BY n.created DESC", 
                        $vars['account']->uid);
    $nodes = array();
    while ($row = db_fetch_object($result)) {
      $nodes[] = node_load($row->nid);
    }
    $vars['nodes'] = $nodes;
}

Yeah, I know I pointed out you avoid querying CCK tables directly in the past. Bonus points if you want to do that here! I feel safer doing a direct query here since I know the field will have its own table, since you explicitly asked for a many-to-one relationship between users and nodes. Also, make sure to change “article” to whatever your preferred node type(s) is (are).

All that remains now is to theme your user profile page. Get creative! Here's a skeletal version to give you some idea what to do:

<?php if (count($nodes)): ?>
  <h2>Recent articles by <?php print $account->name; ?></h2>
  <?php foreach ($nodes as $node): ?>
    <div class="recent-article">
      <h3><?php print $node->title; ?></h3>
      <p>Posted on <?php print date('l, F j, Y \a\t g:i a', $node->created); ?></p>
      <a href="<?php url('node/' . $node->nid); ?>">read &raquo;</a>
    </div>
  <? endforeach; ?>
<? endif; ?>

I've left out some other things to avoid unnecessary complexity. One thing we did with this on NYFI's user pages was to insert the article photo here to jazz up the profile page. Like I said, hire a themer, get creative.

There's one final (and also optional) step. If you happen to have an existing database full of nodes and don't feel like editing each one individually to populate the field_authors apparatus, you can do it with a pretty simple page callback. This does require creating a module, or grafting the following code in an existing module temporarily. You'll only need to run it once, then you can remove it. This module would be called fixusers.module.

function fixusers_menu() {
  $items = array();
 
  $items['fixusers/user/fix'] = array(
    'title' => 'User Fix',
    'type' => MENU_CALLBACK,
    'page callback' => 'fixusers_user_fix',
    'access arguments' => array('access content'),
  );
 
  return $items;
}
 
function fixusers_user_fix() {
  $result = db_query("SELECT * FROM {node} WHERE type='article'");
 
  while ($node = db_fetch_object($result)) {
    $node = node_load($node->nid);
    if (!isset($node->field_authors[0]['uid'])) {
      $node->field_authors[0]['uid'] = $node->uid;
      node_save($node);
    }
  }
}

This will find any node with no one set in field_authors and add the built-in node author to the field. If you installed the above module, you could just to http://example.com/fixusers/user/fix one time only to implement it, then disable the module.

That's about it for the multiple authors. Stay tuned for more.