This code might work in a template file with some tweaking.

Dependencies

No longer dependent on a 6.x only function (drupal_html_to_text), will now work in 5.x, I've been told. Tnx to luko28 for pointing out the dependecy.

If available, the following modules will be exploited:

pathauto

pathauto enabled nodes will use the path assigned by pathauto in all links created by this module. Degrades gracefully to use /node/$nid type of links.

taxonomy_vtn

taxonomy_vtn if available will cause this code to create a html head link to "index".

search

If core search is enabled then the code will create a html head link to "search".

print

If the print module is enabled the code will create html head link of type "alternate", media="paper" and with the apropriate document types for either text or x-application/pdf.

NOTE: I do not check if you have enabled the printing for the current node type, nor if pdf is installed.

taxonomy author

There is a define commented out of the code. If you use taxonomy authors you will be pleased to find that you can uncomment that line and add the taxonomy tid of the author taxonomy to have the node take advantage of that module.

NOTE: If you turn on any of those modules _after_ you have created any content using a computed field, that content will not reflect the usage of the above modules. Try downloading and installing the zipped module in that thread to run an automatic update of all nodes. There are other tips in that thread for use of views bulk operations for doing similar updates.

translation

If the translation module is available links with rel="alternate" will be created for each available translation of the content.

Some (unharmful) Assumptions

It looks for the fields authors, contributors and copyright_template (check the code for the exact names). All of them should be node reference fields.

Copyright node Reference

I have created a Creative Commons and Copyright computed field++, if you implement that this code will take advantage of the information provided by the copyright reference to create dublin core metatags, html meta tags and a html head link.

Author Reference

For large organisations, and for some of us bloggers, there might be articles created by one or more authors. If you create a node reference field called authors, this module will try to use the referenced nodes as the author supplied as dublin core metatags, html meta tags and html head links.

The resolution of authors is done as follows:

field_authors
taxonomy author (if enabled)
$node->name, the user which is set as the author natively in Drupal core.

Contributor Reference

For large organisations, and for some of us bloggers, there might be articles where someone contributed to it, but perhaps not as a co-author. If you create a node reference field called contributors this module will use the referenced nodes as dublin core contributors metatags.

It assumes you are using timezone +0100, you can change that by editing it (I plan to extract the timezone of the site in future).

Computed code

// version 0.20

// Some info on the elements in dublin core: http://dublincore.org/documents/usageguide/elements.shtml
// Some info on the link element for head: 
// Test dublin core at http://www.ukoln.ac.uk/metadata/dcdot/
// Test other tags at http://url-info.appspot.com/ 

global $base_url, $language;

$site_name = variable_get('site_name', '');
$site_slogan = variable_get('site_slogan', '');

// The path of the node

$node_path=$node->nid;
if($node->path) $node_path=$node->path;

// LANGUAGE NEGOTIATION
// Leaves all language paths empty if no language/locale settings are available

$lang = '';

if($language->language) {
  $lang = $language->language . '/';
}

$node_lang_path = '';
$node_lang = '';

if($node->language) {
  $node_lang_path = $node->language . '/';
  $node_lang = $node->language;  
}


// HEADER PROLOG

$dc[] = '<link rel="schema.DC" href="http://purl.org/dc/elements/1.1/" />';
$dc[] = '<link rel="schema.DCTERMS" href="http://purl.org/dc/terms/" />';

// AUTHOR RESOLUTION
// Prefers in this order:
// 1. Nodes referenced through field_authors
// 2. Author taxonomy tags
// 3. Node author

// Comment out the next line if you aren't using the author_taxonomy module
#define("AUTHOR_TAXONOMY", "3");

$authors = "";

if($node->field_author[0][nid]) {
    foreach($node->field_author as $author) {
      $author_node = node_load($author[nid]);
      $authors[] = $author_node->title;
      $author_path = $author_node->nid;
      if($author_node->path) $author_path = $author_node->path;
      $navigation[] = '<link rel="Author" href="' . $base_url . '/' . $node_lang_path .  $author_path . '" />';
    }
} elseif(is_array($node->taxonomy[tags]) && array_key_exists(AUTHOR_TAXONOMY, $node->taxonomy[tags])) {
  $authors = $node->taxonomy["tags"][AUTHOR_TAXONOMY];
} else {
  $authors = $node->name;
  $navigation[] = '<link rel="Author" href="' . $base_url . '/' . $node_lang_path .  'user/' . $node->uid . '" />';
}

$authors = is_array($authors) ? trim(implode(', ', $authors)) : trim($authors);

if($node->field_contributors[0][nid]) {
    foreach($node->field_contributors as $contributor) {
      $contibutor_path = $contributor_node->nid;
      if($contributor_node->path)  $contibutor_path = $contributor_node->path;
      $contributor_node = node_load($contributor[nid]);
      $contributors[] = $contributor_node->title;
      $navigation[] = '<link rel="Author" href="' . $base_url . '/' . $node_lang_path .  $contibutor_path . '" />';
    }
}

$contributors = is_array($contributors) ? trim(implode(', ', $contributors)) : trim($contributors);

// TAGS RESOLUTION
// Will not show any authors.
if($node->taxonomy) {
  if(defined(AUTHOR_TAXONOMY)) {
    unset($node->taxonomy[tags][AUTHOR_TAXONOMY]);
    $tags = trim($node->taxonomy["tags"]);
  }
  
  if($tags) {
    $dc_tags = implode('; ', $tags);
    $dc[] = '<meta name="DC.subject" xml:lang="' . $node_lang . '" content="' . $dc_tags . '" />';
    $meta_tags = implode(', ', $tags);
    $meta[] = '<meta name="keywords" xml:lang="' . $node_lang . '" content="' . $meta_tags . '" />';
  }

}

// NODE TRANSLATIONS

if(module_exists('translation')) {
  $translations = translation_link('node', $node);
  $num_trans = count($translations);
    
  for($i=0;$i<$num_trans;$i++) {
    $translation = array_pop($translations);
    $language_code = $translation['language']->language;
    $language_prefix = $translation['language']->prefix;
    $language_native = $translation['language']->native;
    $title_native = $translation['attributes']['title'];

    $href = $translation['href'];
    $url = "$base_url/$href";
    
    $navigation[] = '<link rel="alternate" href="' . $url . '" title="' . $title_native . '" language="'.$trans_lang.'" hreflang="'.$trans_lang.'" media="screen" />';
  }
}

if(module_exists('taxonomy_vtn')) {
  $navigation[] = '<link rel="Index" href="' . $base_url . '/' . $lang . 'taxonomy_vtn' . '" type="text/html" />';
}



if(module_exists('print')) {
  $navigation[] = '<link rel="Alternate" media="print" href="' . $base_url . '/' . $node_lang_path .  'print/' . $node_path . '" type="text/html" />';
  $navigation[] = '<link rel="Alternate" media="print" href="' . $base_url . '/' . $node_lang_path .  'printpdf/' . $node_path . '" type="x-application/pdf" />';
}

if(module_exists('search')) {
  $navigation[] = '<link rel="Search" href="' . $base_url . '/' . $lang . 'search' . '" type="text/html" />';
}

// TIMESTAMPS

$created = strftime("%Y-%m-%d %H:%M:%S +01:00", $node->created);
$changed = strftime("%Y-%m-%d %H:%M:%S +01:00", $node->changed);
$dc_created = strftime("%Y-%m-%d", $node->created);
$dc_changed = strftime("%Y-%m-%d", $node->changed);
$copy_years[] = strftime("%Y", $node->created);
$copy_years[] = strftime("%Y", $node->changed);

if($created) {
  $dc[] = '<meta name="DC.date" scheme="DCTERMS.W3CDTF" content="' . $dc_created . '" />';
  $dc[] = '<meta name="DC.date.created" scheme="DCTERMS.W3CDTF" content="' . $dc_created . '" />';
  $meta[] = '<meta name="date" scheme="ISO8601" content="' . $created . '" />';
}
if($changed) {
  $dc[] = '<meta name="DC.Date.X-MetadataLastModified" scheme="DCTERMS.W3CDTF" content="'. $dc_changed.'" />';
  $dc[] = '<meta name="DC.date.modified" scheme="DCTERMS.W3CDTF" content="' . $dc_changed . '" />';
  $meta[] = '<meta name="modified" scheme="ISO8601" content="'. $changed.'" />';
}

if ($copy_years[0] == $copy_years[1]) {
  unset($copy_years[1]);
  $copy_years = $copy_years[0];
} else {
  $copy_years = implode('-', $copy_years);
}
// COPYRIGHT RESOLUTION
// Needs work
if($node->field_copyright_template[0][nid]) {
  $copyright = node_load($node->field_copyright_template[0][nid]);
  $copyright_path = $copyright->nid;
  if($copyright->path) $copyright_path = $copyright->path;
  $url = $base_url . '/' . $node_lang . '/' . $copyright_path;
  $copy_text = '© ' . $copy_years . ' ' . $copyright->title . '.';
  
  $navigation[] = '<link rel="Copyright" href="' . $url . '" type="text/html" />';
  $dc[] = '<meta name="DC.rights" content="' . $copy_text . '" />';
  $dc[] = '<meta name="DC.date.copyrighted" content="' . $created . '" />';
  $meta[] = '<meta name="copyright" content="' . $copy_text . '" />';
}


$navigation[] = '<link rel="Home" href="' . $base_url . '/' . $lang . '" type="text/html" />';

if($contributors) $dc[] = '<meta name="DC.contributor" content="'. $contributors .'" />';
if($site_slogan) {
  $max = 160;
  $string = addslashes($site_slogan);
  $string=strip_tags(trim($string));
  $string = str_replace("\r", " ", $string);
  $string = str_replace("\n", " ", $string);
  $string = preg_replace( "{[ \t]+}", ' ', $string );
  $string = trim($string);
  $string = stripslashes($string);
  $title = substr($string,0,$max-3);
  if(count($string) > $max-3) {
    $title =   $title . "...";
  } else {
    $title =  $title;
  }
  $dc[] = '<meta name="DC.coverage" content="' . $title . '" />';
}
if($authors) {
  $dc[] = '<meta name="DC.creator" content="' . $authors . '" />';
  $meta[] = '<meta name="author" content="' . $authors . '" />';
}
if($node->teaser) {
  $max = 160;
  $teaser = addslashes($node->teaser);
  $teaser=strip_tags(trim($teaser));
  $teaser = str_replace("\r", " ", $teaser);
  $teaser = str_replace("\n", " ", $teaser);
  $teaser = preg_replace( "{[ \t]+}", ' ', $teaser );
  $teaser = trim($teaser);
  $teaser = stripslashes($teaser);
  $teaser = substr($teaser,0,$max-3);
  if(count($teaser) > $max-3) {
    $teaser =   $teaser . "...";
  } else {
    $teaser =  $teaser;
  }
  $dc[] = '<meta name="DC.description" xml:lang="' . $node_lang . '"  content="' . $teaser . '" />';
  $meta[] = '<meta name="description" xml:lang="' . $node_lang . '"  content="' . $teaser . '" />';
}

$dc[] = '<meta name="DC.identifier" schema="DCterms:URI" content="' . $base_url . '/' . $node_lang_path . $node_path . '" />';
$dc[] = '<link rel="DC.source" schema="DCterms:URI" href="' . $base_url . '/' . $node_lang_path . $node_path . '" />';
if($node_lang) {
  $dc[] = '<meta name="DC.language" scheme="DCTERMS.RFC1766" content="' . $node_lang . '" />';
  $meta[] = '<meta name="language" scheme="DCTERMS.RFC1766" content="' . $node_lang . '" />';
}
if($site_name) {
  $dc[] = '<meta name="DC.publisher" content="' . $site_name . '" />';
  $dc[] = '<meta name="DC.publisher.address" scheme="DCTERMS.DCMIType" content="' . $base_url . '/' . $lang . '" />';
}
if($node->title) {
  $dc[] = '<meta name="DC.title" xml:lang="' . $node_lang . '" content="' . $node->title . '" />';
  $meta[] = '<meta name="title" xml:lang="' . $node_lang . '" content="' . $node->title . '" />';
}
$dc[] = '<meta name="DC.type" scheme="DCTERMS.DCMIType" content="Text" />';
$dc[] = '<meta name="DC.relation" href="' . $base_url . '/' . $node_lang . '" scheme="IsPartOf" />';
$dc[] = '<meta name="DC.format" schema="DCterms:IMT" content="text/html; charset=utf-8" />';

$node_field[0]['value'] = implode("\n", $meta) . "\n" .  implode("\n", $dc) . "\n" . implode("\n", $navigation);

Turn on database storage.

Database Storage Settings
Data type: varchar
Data length: 1023 (might need longer, I used 8191 to be safe)?
Not sortable.

This code works fine as it is now, but I would also like to do better taxonomy support in it. If you use nodewords or integrated metatags modules you don't need those for the node types you use the above code in.

Display format

drupal_set_html_head($node_field_item['value']);

In display settings for the field you created, set the following:

Label: Hidden
Teaser: Hidden
Exclude: x
Full node: Raw text
Exclude:

I don't remember if the exclude things are part of standard CCK or if it is an extra module.

Please Post Further Expansion/Refinements Here

If you expand, fix bugs or otherwise change the code, it would be nice if you posted it as a comment, or even as a new revision of this book, in order to let more people use this.

Todo

Fix the taxonomy handling. I developed this on a prosepoint based site, and it seems they have done something to the taxonomy array in $node.

Fix the copyright handling; at the moment the copyright year is the node date. This should, of course, be a configurable field, which when empty doesn't output any copyright year. Example are public domain works.

Create something similar for views.

Create next/prev first/last links for "Blog entry".

Include support for other search solutions, if neccessary, I don't know if faceted search etc. use the same url for its search as the internal search.

Add more checks for the print module.

Check the usage of dublin core, I'm sure I've winged it, and perhaps used outdated sources for what I inserted.

Requests

Contact me, Paul K Egell-Johnsen, for any suggestions, problems or requests.

Comments

iantresman’s picture

I've added some information on Dublin core and Google Scholar's requirements in a message on the Intergrated Metatag module, which allow CCK fields to be used as metatags.

pkej’s picture

I hope you have noticed that the Nodewords have integrated Dublic Core tags?

They also have integrated site verification codes (for those who might think that it is interesting), copyright and more, so it is pretty much making the above code obsolete.

The Dublin Core tags can be set for the full site by using tokens, thus it will automatically add the right info from the right CCK fields.

Canonical urls are also supported.

Since there is token support in the copyright field as well, I guess that together with the creativecommons module, it does what both my code snippets do.

The only thing which isn't supported is the navigation links.

The solution in integrated metatags was my preferred earlier, but the older nodewords have acquired some pretty nifty features which makes it a good competitor (especially since you don't have to add canonical url and site verify modules in addition to it).

Paul K Egell-Johnsen