Drupal 6 upgrade with extra RDFa goodness

by ekes

Finally upgraded this site from Drupal 5 to 6. There's nothing special in that, it all went very smoothly and I just changed the theme again. But on the way I thought I'd get a bit of RDFa into the site - so the most interesting stuff on this, and other pages, you probably can't see.

RDFa has always made a huge amount of sense to me, and there's a push to get it into core for 7, which is brilliant, so testing a bit more in here seemed a sensible thing.

Central to implementing a bit more RDF of any sort into Drupal at the moment is the rdf module. Here it's making some nice changes to the header, and adding the name spaces. It's easy to add more that the default too - I certainly wanted Creative Commons and Web of Trust that weren't there already, for example.

Also pretty important, and I guess the way it's going to go with fields in core, is RDF CCK module. This is a lovely module, which when installed with its dependencies, will auto suggest as you type mappings from the imported vocabularies (excellent stuff :) The module's at work here on the profile nodes. However as the rest of the site is based on pretty simple nodes (title, body, with a user and taxonomy) and comments (same but without taxonomy) and no extra fields most of the rest of the RDFa magic's in the template.php and a bit in the appropriate *.tpl.php for now.


First off with nodes just to make sure the subject is usually set I changed the first <div> in the node.tpl.php to include an about="/path/to/node":

<div about="<?php print $node_url; ?>" id="node-<?php print $node->nid; ?>" class="<?php print $classes; ?>"><div class="node-inner">

This means even when it's on a page with multiple nodes the content inside will have correct subject

Next I wanted the dc:subject. I'll get round to integrating with some controlled topic vocabulary, but for now it's referenced on my site. Anyway for this in the THEMENAME_preprocess_node it's just a case of redoing the terms variable:

  $terms = taxonomy_link('taxonomy terms', $vars['node']);
  foreach($terms as &$term) {
    $term['attributes']['property'] = 'dc:subject';
  $vars['terms'] = theme('links', $terms);

Next up the node author. For this I've overridden the theme_username to accept a $options for the link. It's not always the case that the username is an author obviously, so this you choose on pulling the display.

-function theme_username($object) {
+function THEMENAME_username($object, $options = array()) {
   if ($object->uid && $object->name) {
     // Shorten the name when it is too long or it will break many tables.
@@ -8,9 +8,10 @@
     else {
       $name = $object->name;
-    if (user_access('access user profiles')) {
-      $output = l($name, 'user/'. $object->uid, array('attributes' => array('title' => t('View user profile.'))));
+    $profile = content_profile_load('profile', $object->uid);
+    if ($profile && node_access('view', $profile)) {
+      $options['attributes']['title'] = t('View @title', array('@title' => $profile->title));
+      $output = l($name, 'node/'. $profile->nid, $options);
     else {
       $output = check_plain($name);

This needs a bit of thinking about for those occasions when a non-registered, anonymous, user is allowed post and enter their name and url, but it should be as easy to do the same thing on these occasions too if it were so desired (not sure if it makes sense with 'nofollow'?). After making the theme override it's then simple enough to call from the THEMENAME_preprocess_node as:

  // node author link
  $vars['author'] = theme('username', user_load(array('uid' => $vars['node']->uid)), array('attributes' => array('property' => 'dc:creator cc:attributionName', 'rel' => 'foaf:maker sioc:has_creator cc:attributionURL')));

There are lots of predicates here, for most it's clear if their object should be the URL for the user or the literal value of the name string. I'm not 100% sure over the dc:creator though, it's going to the plain text name which seemed to be the best practice suggestion when used with so many others. But, hey, I might be wrong, in which case it wants to be in 'rel'.

I'll deal with the created, updated, stuff below with the comments, because I did a bit of user display side stuff with THEME_node_submitted that complicates it more than I have with THEME_comment_submitted. The logic works the same in both.

Now this all works, except of course that if the nodes are show on the page itself the title isn't displayed by the node.tpl.php but by the page.tpl.php.


I'm guessing there's a whole lot more that can be done here for site wide metadata, but so far I've just gone for the general licence, which is actually in a block, and the title. The line for the title in the page.tpl.php is as simple as:

           <?php if ($title): ?>
              <h1 about="" property="dc:title" class="title"><?php print $title; ?></h1>
            <?php endif; ?>

With the empty about it's related to the page itself, and with pages with other objects on them they will only be related if their about object is the same.


The object for this can just be the url fragment, so I've make this available as $comment_url in THEMENAME_preprocess_comment and then popped it in the <div> just as above.

The title and the user are much as the node, as is the date which I'll describe here. The wish is to add the dcterms:created (and for some nodes the dcterms:modified) both of which best have an object value as a W3C datetime. So for the comment date I've just made a THEMENAME_comment_submitted theme function overrride.

function THEMENAME_comment_submitted($comment) {
  return t('Submitted by !username on !datetime.',
      # note the additional theme() param here for THEMENAME_username() as above
      '!username' => theme('username', $comment, array('attributes' => array('property' => 'dc:creator cc:attributionName', 'rel' => 'foaf:maker sioc:has_creator cc:attributionURL'))),
      '!datetime' => '<span property="dcterms:created" content="'. date('c', $comment->timestamp) .'">'. format_date($comment->timestamp) .'</span>',

It's worth noting though the date 'c' switch is PHP 5 only, and it's apparently full ISO 8601.

The other difference for comments here is that SIOC can describe the relationships between them nicely. Having toyed around with various ideas of where to put sioc:has_reply and sioc:reply_of I was struggling to find anything better than an otherwise empty element, or making an 'in reply to...' link to pop into (and CSS display: none) the submitted information. I'm sure there's a better way, but for now:

  $parent_path = 'node/'. $vars['comment']->nid;
  $parent_options['fragment'] = $vars['comment']->pid ? 'comment-'. $vars['comment']->pid : '';
  $parent_options['attributes']['rel'] = "sioc:reply_of";
  $vars['parent_link'] = l(t('in reply to...'), $parent_path, $parent_options);

Another one that's bothering me is I want to identify what the content is, but encoded content is hard work, from what I gather using RSS 1.0 content is going to be the way, but was just a start.


Throughout CURIE used from:-

 @prefix dc: <http://purl.org/dc/elements/1.1/>.
 @prefix dcterms: <http://purl.org/dc/terms/>.
 @prefix sioc: <http://rdfs.org/sioc/ns#>.
 @prefix foaf: <http://xmlns.com/foaf/0.1/">
 @prefix cc: <http://creativecommons.org/ns#>.
 @prefix wot: <http://xmlns.com/wot/0.1/>.