User login

Drupal 6 theme from a module

Comment Module Example

Most of the time, the best way to learn is from looking at core. Randomly clicking around in a Drupal 6 download led us to modules/comment, where we noticed comment-folded.tpl.php. Aha! A template file with that name will probably not be called automatically by Drupal's template systems (as node.tpl.php might). So let's see just how comment.module handles this.

The _theme hook alerts Drupal to the presence of theming functions and, as in this case, a template:

<?php
/**
 * Implementation of hook_theme().
 */
function comment_theme() {
  return array(
// ...
    'comment_folded' => array(
      'template' => 'comment-folded',
      'arguments' => array('comment' => NULL),
    ),
// ...
  );
}
?>

<?php
/**
 * Process variables for comment-folded.tpl.php.
 *
 * @see comment-folded.tpl.php
 * @see theme_comment_folded()
 */
function template_preprocess_comment_folded(&$variables) {
  $comment = $variables['comment'];
  $variables['author'] = theme('username', $comment);
  $variables['date']   = format_date($comment->timestamp);
  $variables['new']    = $comment->new ? t('new') : '';
  $variables['title']  = l($comment->subject, comment_node_url() .'/'. $comment->cid, array('fragment' => "comment-$comment->cid"));
}
?>

And let's take a look at the template file itself:

<?php
// $Id: comment-folded.tpl.php,v 1.2 2007/08/07 08:39:35 goba Exp $

/**
 * @file comment-folded.tpl.php
 * Default theme implementation for folded comments.
 *
 * Available variables:
 * - $title: Linked title to full comment.
 * - $new: New comment marker.
 * - $author: Comment author. Can be link or plain text.
 * - $date: Date and time of posting.
 * - $comment: Full comment object.
 *
 * @see template_preprocess_comment_folded()
 * @see theme_comment_folded()
 */
?>
<div class="comment-folded">
  <span class="subject"><?php print $title .' '. $new; ?></span><span class="credit"><?php print t('by') .' '. $author; ?></span>
</div>

And how is that template file called?

<?php
/**
 * Theme a single comment block.
 *
 * @param $comment
 *   The comment object.
 * @param $node
 *   The comment node.
 * @param $links
 *   An associative array containing control links.
 * @param $visible
 *   Switches between folded/unfolded view.
 * @ingroup themeable
 */
function theme_comment_view($comment, $node, $links = array(), $visible = TRUE) {
  static $first_new = TRUE;

  $output = '';
  $comment->new = node_mark($comment->nid, $comment->timestamp);
  if ($first_new && $comment->new != MARK_READ) {
    // Assign the anchor only for the first new comment. This avoids duplicate
    // id attributes on a page.
    $first_new = FALSE;
    $output .= "<a id="new"></a>\n";
  }

  $output .= "<a id="comment-$comment->cid"></a>\n";

  // Switch to folded/unfolded view of the comment
  if ($visible) {
    $comment->comment = check_markup($comment->comment, $comment->format, FALSE);

    // Comment API hook
    comment_invoke_comment($comment, 'view');

    $output .= theme('comment', $comment, $node, $links);
  }
  else {
    $output .= theme('comment_folded', $comment);
  }

  return $output;
}
?>

The

<?php
 $output .= theme('comment_folded', $comment);
?>

line calls the template, thanks to the comment_theme definition that includes the template definition. The instructions in both the preprocess function and the template itself to see "theme_comment_folded" are incorrect— that function does not exist and should not exist; the template file takes its place.

http://api.drupal.org/api/function/hook_theme/6

http://api.drupal.org/api/function/hook_theme_registry_alter/6

http://11heavens.com/theming-Drupal-6-from-the-module-layer

http://www.packtpub.com/article/theming-modules-in-drupal-6

The theme() function is called in philquotes_block() with three parameters:

<?php
$content = theme('philquotes_quote',
check_plain($item->body),
check_plain($item->title));
?>

The first, philquotes_quote, tells the theme() function which theme hook should be executed. The theme() function will query the theme system to find an implementation of a function called theme_philquotes_quote() or a template called philquotes_quote.

Essentially, when a theme is registered, it declares new theme hooks, which other parts of the theme system can use or override.

To register the module's default theme, we need to implement the theme registration hook [...]:

<?php
/**
* Implementation of hook_theme().
*/
function philquotes_theme() {
  return array(
    'philquotes_quote' => array(
      'arguments' => array('text' => NULL, 'origin' => NULL),
    ),
  );
}
?>

In a nutshell, this function provides the theme system with a comprehensive list of what theme functions (hooks) are provided by this module, and how each function should be called.

There are a lot of different configurations that we might want for a theme hook. Unfortunately, the result of this is that the data structure that hook_theme() must return is a daunting series of nested associative arrays.

In the above example, the arrays are nested three-deep. Let's start with the outermost array.

The outermost array contains elements of the form 'theme_function_name' => configuration_array. In the above example, it looks as follows:

return array(
'philquotes_quote' => array(
// Contents of the array...
),
);

The theme_function_name string should be the name of a theme hook that this module implements or provides. In our theme, for example, we will provide a theme for philquotes_quote.

[...]

The value for the philquotes_quote key in the outer array is itself an associative array. The keys that this array holds are well defined, and all the eight keys are explained in the hook_theme() API documentation (http://api.drupal.org/api/function/hook_theme/).

For example, the template key can be used to point to a template file that should be used instead of a theme function. Had we chosen to use a template file called philquotes_quote.tpl.php, we could have called the above as follows:

'philquotes_quote' => array(
'template' => 'philquotes_quote',
'arguments' => array( /* parameter info */ ),
),

When theme_philquotes_quote() is called with these parameters, it would look for a file called philquotes_quote.tpl.php inside the philquotes module directory. (The template file extension is appended automatically.)

In this example, the items in the arguments array would be passed in as variables to the template.

Other similar directives exist for adding preprocessing functions, including other PHP files, and so on.

For our module, however, we will implement the hook as a function. The only directive we want in this array is arguments.

In the context of a hook function (as opposed to a template), this array entry is used to indicate what parameters will be passed to the function.

For our module hook, we only need two: text and origin.

/**
* Implementation of hook_theme()
*/
function philquotes_theme() {
return array(
'philquotes_quote' => array(
'arguments' => array('text' => NULL, 'origin' => NULL),
),
);
}

Items in the arguments array are of the form 'argument_name' => default_value.

Initially, we set both values to NULL; however, if we want to provide more robust default values, we could do so here.

Based on this array, we can now construct the method signature of our theme hook. It will be:

function theme_philquotes_quote($text, $origin)

The name of the function is the hook name with theme_ prepended, and the two parameters here should correspond to the elements in the arguments array.

[The point to all this is that this function can be overridden now with phptemplate_philquotes_quote or myactivethemename_philquotes_quote. (And apparently with a philquotes_quote.tpl.php file in the theme, too.) And presumably from the module layer as well with hook_theme_registry_alter?]
[...]

Creating a Theme Hook Function

We have just created a theme registration function, overriding the hook_theme() function for our module. In it, we have declared that this module implements a hook called theme_philquotes_quote() that takes two arguments, $text and $origin. Now we will create that function.

The goal of this function is to take the same content (a single quote) and configure it for display. Here is our first version of this function, which will provide a basic display of the quote:

<?php
/**
* Theme function for theming quotes.
*
* @param $text
* The quote content as a string.
* @param $origin
* The original source of the quote, as a string.
* @return
* An HTML themed string.
*/
function theme_philquotes_quote($text, $origin) {
$output = '<div id="philquotes-text">'. t($text)
.'</div><div id="philquotes-origin">'. t($origin) .'</div>';
return $output;
}
?>

All we have done in this function is wrap the content of the $text and $origin variables in tags, each of which has an id attribute.

function theme_philquotes_quote($text, $origin) {
$module_path = drupal_get_path('module', 'philquotes');
$full_path = $module_path .'/philquotes.css';
drupal_add_css($full_path);
$output = ''. t($text)
.'' . t($origin) . '';
return $output;
}

This information has been extracted from: Learning Drupal 6 Module Development
Matt Butcher
www.PacktPub.com/drupal-6-module-development/book

More on Drupal 6 Theming

http://drupal.org/node/132442

Overriding themable output:
http://drupal.org/node/173880

Preprocess functions
http://drupal.org/node/223430

http://mydrupalblog.lhmdesign.com/drupal-theming-links-drupal-org

Resolution

Searched words: 
Drupal 6 theme from module

Comments

commentform over comments

Hi,
do you think, that it is possible to set the commentform over the comments? When you choose "newest comments first" in d6 backend I think it is a little bit confusing to show the commentform under all comments. But I can't find anything about that topic which is working... Also in the $content array in comment-wrapper.tpl is not good for that.

Maybe you can help.

greetings

Good Article!

I try to limit my contributed modules as much as possible, start a few sites on the new release so I can get up to speed, then gradually update a few at a time with a mad rush at the end when support for the old version stops. This release is different.My must-have list differs a little from yours, but Views has to be on most everybody's list. Sure, I could build a fully functional site with no modules at all, but why do it if you do't have to? I do balk at putting dev releases on a client's site, so I have stayed with the familiar this time.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.
  • You can use Markdown syntax to format and style the text. Also see Markdown Extra for tables, footnotes, and more.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <img> <blockquote> <small> <h2> <h3> <h4> <h5> <h6> <sub> <sup> <p> <br> <strike> <table> <tr> <td> <thead> <th> <tbody> <tt> <output>
  • Lines and paragraphs break automatically.

More information about formatting options

By submitting this form, you accept the Mollom privacy policy.