My top 10 favorite theming tweaks for content

Here is a collection of my 10 favorite 'tweaks' to content template files in Drupal 6. I will add one simple tweak a day, starting today.

Theming comments by the node's author

You want the comment style to be different for comments added by the author of a node — who's commenting on his own node. For example, you may want to highlight the node's author's comments, so that any visitor skimming through the comments will easily differentiate them from other comments.

Solution

  1. Edit comment.tpl.php to add a new class (let's say 'comment-by-author-of-post') that...

  2. you can style in your theme's style.css.

Your file comment.tpl.php may look like this before the edit (I am only providing a snippet of the template here):

<div class="comment<?php print ($comment->new) ? ' comment-new' : ''; print ' '. $status ?> clear-block">

If it does, then you will change the above snippet to that:

<div class="comment<?php print ($comment->new) ? ' comment-new' : ''; print ($comment->uid == $node->uid) ? ' comment-by-author-of-post' : ''; print ' '. $status ?> clear-block">

You could now add these two rules to your style.css file, for example:

/**
 * Comment styling rules
 */
.comment-by-author-of-post {
  border: 1px solid #ccc;
  background-color: #eee; 
}
/* reset to default values when previewing */
.preview .comment-by-author-of-post {
  border: none;
  background-color: transparent;
}

This will work in Drupal 5 as well. No, it won't. The $node object is not passed to the comment.tpl.php template. Read the comments below for the Drupal 5 recipe. This solution works only for PHPTemplate-powered themes. If your theme does not provide a template file for comments, copy the one from modules/comment/ to your theme folder, and edit it. And yes, the $node object is available in comment.tpl.php. It is the node the comment is attached to. Possible variations: special-style all comments authored by those who have the 'admin' role; special-style anonymously-submitted comments; etc.

[]

We used a Ternary Comparison Operator in our solution.

($a == 11) ? 'eleven' : 'not eleven'

The above code snippet returns a value. If $a equals 11, the return value is 'eleven'; otherwise, the return value is 'not eleven'. You can print the return value of a ternary comparison operator like so:

print ($a == 11) ? 'eleven' : 'not eleven';

Notice that I added both 'print' and a semicolon in the above snippet.

From php.net: “The expression (expr1) ? (expr2) : (expr3) evaluates to expr2 if expr1 evaluates to TRUE, and expr3 if expr1 evaluates to FALSE.” The ternary comparison operator is used a lot in Drupal templates. In our solution we printed ' comment-by-author-of-post' — in the class attribute of the comment div — if and only if the comment author was the same guy/girl as the author of the node the comment was attached to. And we printed nothing otherwise, ie: ''. (These '' are two SINGLE quotes.) We added this:

<?php print ($comment->uid == $node->uid) ? ' comment-by-author-of-post' : ''; ?>

Displaying the content type name

You want to print the content type name along with the 'submitted' info. This will be particularly useful for those who have created custom content types, with or without CCK.

Solution

  1. Edit node.tpl.php to add the content type name within a span with a class name (e.g. 'content-type-name') and...

  2. style that span element in your theme's style.css file.

In the following example, the human-readable name for the content type is 'News', and the content type is displayed to the right of the 'submitted' info. Let's try and achieve that.

Node with displayed content type name next to the submitted info.

Your file node.tpl.php may look like this before the edit (I am only providing a snippet of the template here):

<?php if ($submitted): ?>
    <span class="submitted"><?php print $submitted; ?></span>
<?php endif; ?>

If it does, then you will change the above snippet to that:

<?php if ($submitted): ?>
  <span class="submitted"><?php print $submitted; ?> &mdash; <span class="content-type-name"><?php print node_get_types('name', $node); ?></span></span>
<?php endif; ?>

You may want to exclude a certain content type. For example, you may not want 'page' to appear in page nodes. Whenever you want to exclude one content type, you will do like so:

<?php if ($submitted): ?>
  <span class="submitted"><?php print $submitted; ?><?php print ($node->type != 'page') ? ' &mdash; <span class="content-type-name">' . node_get_types('name', $node) . '</span>' : ''; ?></span>
<?php endif; ?>

When you want to exclude more than one content type, say 'story' and 'page', you will do like so:

<?php if ($submitted): ?>
  <span class="submitted"><?php print $submitted; ?><?php print (!in_array($node->type, array('story', 'page'))) ? ' &mdash; <span class="content-type-name">' . node_get_types('name', $node) . '</span>' : ''; ?></span>
<?php endif; ?>

This will work in Drupal 5 as well. Again, this solution works only for PHPTemplate-powered themes.

You need to provide the machine-readable names for content types you wish to exclude, because $node->type is used in the condition, and it is the machine-readable name for the content type. Why are we comparing machine-readable names? Simply to avoid database queries. There is one database query we cannot avoid in our solution, it is the one query we execute to print the human-readable name for the content type.

Possible variations: place the content type information somewhere else in the node; display the content type information only in the teaser; etc.

The machine-readable name of a content type is usually not the same as its human-readable name. Sometimes the difference is only in capitalization:

The machine-readable name here is news, while the human-readable name is News.

We used the Drupal function node_get_types() in our solution.

The function can be used in so many ways that it gets rather confusing, as webchick points out here. The function can be used in these seven different ways:

node_get_types('type', $node);
node_get_types('type', $node->type);
node_get_types('names');
node_get_types('name', $node);
node_get_types();
node_get_types('types', NULL, TRUE);
node_get_types('module', $node);

When we use the function like so:

node_get_types('name', $node);

... the returned value is a string, and it is the human-readable name for the content type of the $node object.

By the way, the $node object is available in all these templates:

  • comment.tpl.php
  • node.tpl.php
  • page.tpl.php when a node is displayed on its dedicated page, ie: at node/x

In Drupal 7, there will be a new function to get the human-readable name for the content type of a node:

$name = node_get_name($node);

Until then, we'll make-do-and-mend with node_get_types().

Quick edit and delete links for the Administrator

You want to add quick edit links to the teaser view of your nodes — one link to edit the node, and another to delete it, and you want these links to be shown only to users with the 'administer nodes' permission. Alternatively, you may want these links to be viewed only by the user with uid (user ID) 1.

First Solution

  1. Edit node.tpl.php to add an unordered list of 2 links, contained within a div with a class name (e.g. 'quick-edit-links') and...

  2. style these links in your theme's style.css file.

Say you would like anyone who's logged-in and who has 'administer nodes' permission to see these two additional links below the teaser of every node: Edit [content-type-name] and Delete [content-type-name], like so:

Quick edit links by themselves

Let's get to it.

In your theme folder, your file node.tpl.php may look like this before the edit (I am only providing a snippet of the template here):

<?php if ($links): ?>
  <div class="links"><?php print $links; ?></div>
<?php endif; ?>

Below the above code, you will add your new 'quick edit' links like so:

<?php if ($teaser && user_access('administer nodes')): ?>
  <?php $content_type_name = node_get_types('name', $node); ?>
  <?php $quick_links['quick-edit'] = array('title' => 'Edit ' . $content_type_name, 'href' => 'node/' . $nid . '/edit'); ?>
  <?php $quick_links['quick-delete'] = array('title' => 'Delete ' . $content_type_name, 'href' => 'node/' . $nid . '/delete'); ?>
  <div class="links quick-edit-links">
    <?php print theme('links', $quick_links, array('class' => 'links inline')); ?>
  </div>
<?php endif; ?>

In the above snippet, you build a new array of links, $quick_links, and you theme that array with the function theme('links', ...).

You may prefer to write the markup yourself, using the Drupal function l(), like so:

<?php if ($teaser && user_access('administer nodes')): ?>
<?php $content_type_name = node_get_types('name', $node); ?>
  <div class="links quick-edit-links">
    <ul class="links inline">
      <li class="first quick-edit"><?php print l('Edit ' . $content_type_name, 'node/' . $nid . '/edit'); ?></li>
      <li class="last quick-delete"><?php print l('Delete ' . $content_type_name, 'node/' . $nid . '/delete'); ?></li>
    </ul>
  </div>
<?php endif; ?>

Second Solution

In template.php, using a preprocess function, add 2 new links to $node->links and re-theme (like a re-rince) $node->links to overwrite the existing $links variable. And... style as needed.

Let me show you what I mean. You may want to add the quick edit links next to the other node links, like so:

Quick edit links with other links.

To achieve this, in your theme's template.php file, you will add a prepocess function for your node template. Open your theme template.php file in a text editor, and add the following code:

<?php
/**
* Override or insert PHPTemplate variables into the node template.
*/
function phptemplate_preprocess_node(&$vars) {
 
// If we are in teaser view and have administer nodes permission
 
if ($vars['teaser'] && user_access('administer nodes')) {
   
// get the human-readable name for the content type of the node
   
$content_type_name = node_get_types('name', $vars['node']);
   
// making a back-up of the old node links...
   
$links = $vars['node']->links;
   
// and adding the quick edit link
   
$links['quick-edit'] = array(
     
'title' => 'Edit ' . $content_type_name,
     
'href' => 'node/' . $vars['nid'] . '/edit',
    );
   
// and then adding the quick delete link
   
$links['quick-delete'] = array(
     
'title' => 'Delete ' . $content_type_name,
     
'href' => 'node/' . $vars['nid'] . '/delete',
    );
   
// overwriting the $links variable with our new links THEMED
   
$vars['links'] = theme('links', $links, array('class' => 'links inline'));
  }
}
?>

Here's what you've done:

Modifying the $links variable.

The first solution will work in Drupal 5 as well. Both solutions work only for PHPTemplate-powered themes. With the first solution, it's easy to place the additional links anywhere in the node view.

If you want your quick edit links to be viewed only by the all-mighty Administrator, in your file node.tpl.php, you will replace this code...

&& user_access('administer nodes')

... with that one:

&& ($user->uid == 1)

The variable $user is part of what's called in themespeak the default baseline variables. The baseline variables are available to all template files, as they are generated by the preprocess function template_preprocess(). No need to refer to the oft-used global $user in your template file (although that will work fine as well). Just use the baseline variable $user (it's less code). However, in the preprocess function defined above, to make use of the baseline variable $user, you'll need to write $var['user'].

&& ($var['user']->uid == 1)

You can find more information on preprocess functions (new to Drupal 6) in this other article. Preprocess functions are used to pass additional variables to template files, or to modify the variables already passed to them. In our second solution, we modified the variable $links, which is a themed representation of the node links.


A little variation

Showing these links to people with 'administer nodes' permission is quite restrictive: chances are only a handful of people — maybe one person — have access to the Administration section for content.

You may have a web site with many contributors. Some will have 'edit' permission to some or all of the site content, with or without a right to 'delete'. When the situation is relatively complex, and you do want quick edit links whenever possible, you can rely on the Drupal's function node_access($op, $node, $account = NULL) as Roger López points out in this comment. The preprocess function rewritten with the use of node_access() becomes:

<?php
/**
* Override or insert PHPTemplate variables into the node template.
*/
function phptemplate_preprocess_node(&$vars) {
 
// If we're in teaser view and have right to update(edit) the node
 
if ($vars['teaser'] && node_access('update', $vars['node'])) {
   
// get the human-readable name for the content type of the node
   
$content_type_name = node_get_types('name', $vars['node']);
   
// making a back-up of the old node links...
   
$links = $vars['node']->links;
   
// and adding the quick edit link
   
$links['quick-edit'] = array(
     
'title' => 'Edit ' . $content_type_name,
     
'href' => 'node/' . $vars['nid'] . '/edit',
    );
   
// and then adding the quick delete link
    // if we have the right to delete the node
   
if (node_access('delete', $vars['node'])) {
     
$links['quick-delete'] = array(
       
'title' => 'Delete ' . $content_type_name,
       
'href' => 'node/' . $vars['nid'] . '/delete',
      );
    }
   
// overwriting the $links variable with our new links THEMED
   
$vars['links'] = theme('links', $links, array('class' => 'links inline'));
  }
}
?>

The function node_access($op, $node, $account = NULL) determines whether the current user (OR the user with the specified $account) has the right to perform a given operation on a specified node. If he has the right to perform the operation, the function returns TRUE, if not it returns FALSE. The operations, given by the flag $op, are 'view', 'update' (to edit the node), and 'delete'.

Take note of this: if the node was last saved with an input format not permitted to the user (e.g. say it has been edited by the Administrator who saved it in the 'PHP code' input format), the user won't be able to edit his node anymore — for obvious security reasons. In the node_access() function, you'll find this:

// If the node is in a restricted format, disallow editing.
if ($op == 'update' && !filter_access($node->format)) {
  return FALSE;
}

Putting some order in your terms

In Wordpress, unlike in Drupal, terms are not lumped together in posts. Each Wordpress vocabulary has its own “template tag”, and the ones that come out-of-the box are: the_tags(), and the_category(). The following theming tweak is about putting order in Drupal terms before they're output to screen. It you need to break up your terms by vocabulary before you display them, read on.

Here's a Wordpress blog entry:

Terms presented by vocabularies in Wordpress

I will present an all-purpose solution that will print all terms by vocabulary. Each vocabulary list will be wrapped in its own HTML element, and I will use the vocabulary name as a 'label' for each list — a label you will be able to edit in the Administration Section of your Drupal site.

Solution

  1. Edit template.php to rebuild your $node->taxonomy array...

  2. ... and re-theme that array into a new $terms variable... and then...

  3. style your terms as needed in style.css.

Say you have two vocabularies, one for “free tagging”, and another for filing posts under sections, like so:

My vocabularies listed

You may not like your free tags to be lumped together with your “Filed under” terms.

You may prefer to see something like this:

Terms presented by vocabularies in Drupal

Let's get to it.

You will use a prepocess function for your node template. You will add this function to template.php if it has not already been defined. Open your theme template.php file in a text editor, and add the following code (please read the comments):

/**
* Override or insert PHPTemplate variables into the node template.
*/
function phptemplate_preprocess_node(&$vars) {
  // If we have any terms...
  if ($vars['node']->taxonomy) {
    // Let's iterate through each term.
    foreach ($vars['node']->taxonomy as $term) {
      // We will build a new array where there will be as many
      // nested arrays as there are vocabularies
      // The key for each nested array is the vocabulary ID.     
      $vocabulary[$term->vid]['taxonomy_term_'. $term->tid]  = array(
        'title' => $term->name,
        'href' => taxonomy_term_path($term),
        'attributes' => array(
          'rel' => 'tag', 
          'title' => strip_tags($term->description),
        ),
      );       
    }
    // Making sure vocabularies appear in the same order.
    ksort($vocabulary, SORT_NUMERIC);
    // We will get rid of the old $terms variable.
    unset($vars['terms']);
    // And build a new $terms.
    foreach ($vocabulary as $vid => $terms) {
      // Getting the name of the vocabulary.
      $name = taxonomy_vocabulary_load($vid)->name;
      // Using the theme('links', ...) function to theme terms list.
      $terms = theme('links', $terms, array('class' => 'links inline'));
      // Wrapping the terms list.
      $vars['terms'] .= '<div class="vocabulary taxonomy_vid_';
      $vars['terms'] .= $vid;
      $vars['terms'] .= '">';
      $vars['terms'] .= $name;
      $vars['terms'] .= ':&nbsp;';
      $vars['terms'] .= $terms;
      $vars['terms'] .= '</div>';
    }
  }    
}

Here is what the preprocess function does essentially:

Overwriting the $terms variable with a preprocess function.

The new HTML generated from print $terms (in node.tpl.php) is shown in this Firebug screen capture:

Terms are now printed by vocabularies


Creating PHPTemplate variables to pass on to your node.tpl.php template

As Opusoid recommends in a comment below, you can add each vocabulary to a new variable, that you pass on to your node template. In node.tpl.php, you can output each vocabulary variable wherever you wish. It gets super easy to place each vocabulary list in a precise location in the node: above it, below it, wherever you want. Brilliant, Opusoid, thank you!


Ordering vocabularies by weight — Improved version by manuee

Manuee modified the code snippet above to order vocabularies by weight. This is something you'll most likely prefer to do. After all, that's what weight is all about: presentation order. His code snippet is in his comment below. Thanks manuee!


Does that work in Drupal 5?

This solution will work in Drupal 6 only. Of course, there's an equivalent method for Drupal 5, and if someone asks for it I will provide it.

In Drupal 5, there was a function called taxonomy_get_vocabulary($vid) .This function has been renamed in Drupal 6 to taxonomy_vocabulary_load($vid). It has been renamed probably to bring naming consistency between functions that load objects, such as node_load() and user_load(). The function returns the vocabulary object matching the vocabulary ID $vid. The vocabulary object contains 'name' and 'description' properties, both of which you can use in your theme.


CSS styling

You may need to style your terms if you want them to appear on the same line, like so (these are rules added to the Garland theme style.css file):

/**
 * Terms styling rules
 */
 
.vocabulary {
  display: inline-block;
  padding-right: 1.5em;
}
 
.terms {
  float: none;
}

Adding 'Last edited by name some time ago' information

Administrators are presented with a special marker when content is new or has been updated in the Administration section of their site at webSite.com/admin/content/node. Additionally, in node lists, the module tracker informs any logged-in user if he or she hasn't read a particular (recently created) node (using the marker new), or if a node he/she's read already was modified (using the marker updated). You, the themer, may want everyone, including “anonymous users”, to be informed of updates to nodes right inside their content. This information may specify who last edited the node and when. Note that the last editor of a content may not be the creator of that content, and I'll take this into account in my “solution”. In the following theming tweak, you'll add Last edited by name some time ago information to your nodes' content.

Here is what you'll try to achieve:

Last edited by name some time ago.

Solution

  1. Edit the file template.php to pass an additional variable to the node template, a variable we'll call $last_edit...

  2. print that variable in our node.tpl.php file, and...

  3. style this added markup as needed in style.css.

Before you apply this solution to your theme, we will review how Drupal deals with node editing and node revisions. Read carefully.

When a node is simply edited, with no new revision created

If the editor of the node doesn't edit the Authored by field under Authoring information, the recorded author of the node remains unchanged. What that means is that the variables $name and $uid in node.tpl.php will output the same HTML. However, if the editor is not the person who authored the node, the revision_uid (user ID) of the current revision does change to show who edited the node. In node.tpl.php, the variable $revision_uid always tells us who was the last person to edit the current version of a node.

If the editor changes the Authored by field under Authoring information, the variables $name and $uid in node.tpl.php will change to reflect whatever user the editor has picked as new author. However, still in node.tpl.php, the variable $revision_uid will provide us with the user ID of the editor.

No one with permission to edit a node can effectively edit it without a record of his action being saved to the Drupal database, a record of his action under his name. The info about any node version in the table {node_revision} will tell us when and by whom that version was last edited.

When a new revision of a node is created

What has been said previously still applies. If the Authored by field isn't edited, the author of the new revision and the author of the old revision will be the same person, regardless of who created the new revision. The variable $revision_uid will provide us with the user ID of the creator of the new revision.

If the editor changes the Authored by field under Authoring information when creating a new revision, the info about the author of the node will reflect that change.

Variables available to node.tpl.php — that you will use

What follows is a partial list of the variables available to node.tpl.php.

Available variables:
* - $page: Flag for the full page state.
* - $name: Themed username of node author output from theme_username().
* - $uid: User ID of the node author.
* - $vid: Current version ID of the node, e.g. the version to output.
* - $revision_uid: User ID of the last editor of the node version with $vid.
* - $created: Time the node was published formatted in Unix timestamp.
* - $changed: Time the node was last edited formatted in Unix timestamp.

For a given revision row in the {node_revisions} table of your Drupal database, the variable $revision_uid corresponds to the uid column. Whereas the $uid variable is read from the uid column in the table {node}.

You can find an exhaustive list of the variables available to node.tpl.php if you inspect a node's content using the Themer Info widget that comes with the Devel module. Look under Template Variables:

Complete list of variables available in node.tpl.php

Passing a new variable to print in the node template

You will use a prepocess function to pass a new, e.g. additional, variable to your node template, ie: $last_edit. Hence, you will need to add a phptemplate_preprocess_node() function if it has not already been defined to your template.php file. Open your theme template.php file in a text editor, and add the following code (please do read the comments):

/**
* Override or insert PHPTemplate variables into the node template.
*/
function phptemplate_preprocess_node(&$vars) {
  /*
   * If the node is shown on its dedicated page and
   * the node was edited after its creation. 
   */
  if ($vars['page'] && $vars['created'] != $vars['changed']) {
    $time_ago = format_interval(time() - $vars['changed'], 1);
    /* 
     * If the last person who edited the node 
     * is NOT the author who created the node.
     */
    if ($vars['uid'] != $vars['revision_uid']) {
      // Let's get the THEMED name of the last editor.
      $user = user_load(array('uid' => $vars['revision_uid']));
      $edited_by = theme('username', $user);
    }
    /* 
     * If the last person who edited the node 
     * is also the author who created the node,
     * we already have the THEMED name of that person.
     */
    else {
      $edited_by = $vars['name'];
    }
    /* Adding the variable. */
    $vars['last_edit'] = t('Last edited by !name about @time ago.', 
      array('!name' => $edited_by, '@time' => $time_ago));
  }
 
}

After that, you edit your node.tpl.php file to print your new variable. Take note that the variable will be undefined if we're on a node list page, or if the node wasn't edited since its creation. So we won't add a div unless our variable is defined.

<?php if ($last_edit): ?>  
  <div class="last-edit clear-block">
    <small><?php print $last_edit; ?></small>
  </div>
<?php endif; ?>

A bit of styling

You can pick a special color for that added text, and increase the white space above and below it, with this CSS rule, added to the style.css stylesheet of your theme:

/**
 * Last edit rules
 */
 
.last-edit {
  color: #ca481c;
  margin: 1em 0pt; 
}

Because it makes use of a preprocess function, this solution will work in Drupal 6 only. There's an equivalent method for Drupal 5 themes, and if someone asks for it I will provide it.

A few questions and their answer

Question 1. Why use the function theme('username', ...)? Why aren't we just outputing the name of the editor as is?

You could. When using the function theme('username', $user), the name of the user becomes a link to his profile page under certain conditions. It's very likely you may not like that. If you don't, just output the name like so (this is a snippet only):

/* 
 * If the last person who edited the node 
 * is NOT the author who created the node.
 */
if ($vars['uid'] != $vars['revision_uid']) {
  $user = user_load(array('uid' => $vars['revision_uid']));
}
/* 
 * If the last person who edited the node 
 * is also the author who created the node,
 * we already have the THEMED name of that person.
 */
else {
  $user = user_load(array('uid' => $vars['uid'])); 
}
 
/* Reading the name property of the user object */
$edited_by = $user->name;
 
/* Adding the variable. */
$vars['last_edit'] = t('Last edited by @name about @time ago.', 
  array('@name' => $edited_by, '@time' => $time_ago));

Note that I am using @name instead of !name in the translation function now. That's because I want the user name to be escaped properly and sanitized before being output to screen. Always do this for user names. The ! prefix used for a placeholder means that the variable it stands for should be used as is.

Question 2. Are variables publicized as being available in a template are the same ones we can read in the parameter $vars passed to a preprocess function, right?

Riiight.

Question 3. Some of these variables we're using don't seem to be listed at the beginning of the file modules/node/node.tpl.php...

True. They are missing from the comment area, because they are less commonly used. That's why the Devel module is tremendously useful to themers in Drupal 6: as a themer, you can use the Themer info widget to get an exhaustive list of the available variables for any template.

Question 4. You say that the variable $last_edit is undefined if we're on a page with a list of nodes, or if the node wasn't edited after its creation. Why is that?

Because of this condition in the preprocess function:

if ($vars['page'] && $vars['created'] != $vars['changed']) {

We are defining the new variable only when we pass through this condition.

$vars['page'] becomes $page in the node template and it's a TRUE/FALSE flag. It is TRUE when the node is shown on its dedicated page at webSite.com/node/x, and FALSE otherwise. You could change this to say if we're not showing the node in teaser view, and in most cases this would have the same effect, but take note that it is possible to show a list of nodes in full view, with the module Views for example.

Changing $vars['page'] to !$vars['teaser'] — and changing the != sign to a 'greater than' sign — will give your script the same behavior in most situations:

if (!$vars['teaser'] && $vars['created'] < $vars['changed']) {

No time of day in node date

It is common to wish to remove the time of day shown in the node “submitted” line. In Drupal 5, there were no less than three ways to achieve this, and I will present these when I'm done covering date formatting for Drupal 6. Drupal 6 allows you to modify the different formats for dates in the Administration area of your site, these different formats being small, medium and large — yep, just like pizza, oh... my... god... You could do the same in Drupal 5. However, you then had to pick among a finite list of options. In Drupal 6, you can create an nth option, if you like none of the options presented to you.

It is the medium-size date format that's used to format the submitted line in nodes, as shown in lines 2449-2460 of modules/node/node.module:

/**
 * Format the "Submitted by username on date/time" for each node
 *
 * @ingroup themeable
 */
function theme_node_submitted($node) {
  return t('Submitted by !username on @datetime',
    array(
      '!username' => theme('username', $node),
      '@datetime' => format_date($node->created),
    ));
}

In Drupal 6, the signature of the Drupal function format_date() is:

format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL)

When the 2nd parameter is not set, 'medium' is used to format the date, and the function theme_node_submitted() is not setting that 2nd parameter when calling format_date().

To modify the medium-size format for dates, head over to the page admin/settings/date-time of your Drupal site. Under Formatting, in the drop-down list of options for “Medium date format:”, you will find a “Custom format” option. Select it. There was no such option in Drupal 5. Note that in Drupal 6 — just like in Drupal 5 — all the preset options still show time of day for the medium-size date.

In Drupal 6, the Medium date format now shows a custom format option.

Look for the preview of the current date in the text below the field on the right, where it says: This format is currently set to display as...

(Take note also that a link to the PHP manual is provided to you, to show you how to format dates in PHP.)

In Drupal 6, the default Medium format is D, m/d/Y - H:i

If all you want to do is remove the time of day from your current default, get rid of the - H:i part. Then do a Save configuration.

Your new Medium date format.

Now if you look at any node on your site, you will see that the change has taken effect immediately: no more time of day.

No more time of day in nodes.

If you do not see the change, it can mean either of these two things: your theme is overriding the function theme_node_submitted(), or, the function format_date() is called within your node template file, in your theme, with some custom format, to display the date.

In Drupal 5

In Drupal 5, there were at least three ways of going about changing your date format.

  1. Use template.php (http://drupal.org/node/209793#comment-697884) to modify the variable $submitted. Like so:

    function _phptemplate_variables($hook, $vars) {
      switch($hook) {
        case 'node' :
          $vars['submitted'] = t('Submitted by !username on @date', 
            array(
              '!username' => $vars['name'],
              '@date' => format_date($vars['node']->created, 
                                      'custom', 'l, j M Y'),
            ));
          break;
      }
      return $vars;
    }
  2. Modify your site's settings.php file (http://drupal.org/node/134990#comment-221129) — to change your system's default medium format. Thank you, Mooffie. So you un-comment the $conf array and add one line to it, like so:

    $conf = array(
    #   'site_name' => 'My Drupal site',
    #   'theme_default' => 'minnelli',
    #   'anonymous' => 'Visitor',
        'date_format_medium' => 'D, m/d/Y',
    );
  3. Modify node.tpl.php (http://drupal.org/node/134990#comment-221973) — instead of printing $submitted, you print $node->created formatted. Like so, for example:

    print format_date($node->created, 'custom', 'l, j M Y');