Using the theme layer (Drupal 6.x)
Modules in Drupal have the capability to allow presentation to be overridden for use on individual sites; this capability is called theming. In order for the theme layer to be usable, a module must be written properly in order to take advantage of it. To do this, logic must be separated as much as possible from presentation.
To accomplish this, modules do as much of the work on the data as possible, and hand that data off to the presentation layer. Modules then provide default implementations that provide the basic presentation and serve as a basis for themes that wish to provide an alternate presentation. This is handled through the theme() function. Every chunk of output that is themed through the theme() function is called a theme hook. There are two ways to provide a default implementation. The easier but less recommend way is to provide a function, and the recommended way is to provide a template and a corresponding preprocessor function. We'll expand on this in a little bit.
Ideally, your module won't produce a single line of HTML output that is not in a theme implementation. In reality, administrative pages and very small items may not necessarily be themable, but a sign of a well written module includes easily utilized themables.
Registering theme hooks
In order to utilize a theme hook, your module first has to register that this exists. This is a change starting in Drupal 6; this registration is necessary due to the amount of time automatic discovery of alternate implementations takes; by registering, this discovery can be done only when needed, and then is always available. In the end, the theming layer is actually just a little bit faster, even though it is now doing more work.
Your module's hook_theme (see the hook API documentation for details on hooks) will return a list of all theme hooks that your module implements. A simple hook_theme implementation might look like this:
<?php
function forum_theme() {
return array(
'forums' => array(
'template' => 'forums',
'arguments' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
),
);
}
?>This registration tells us that a theme hook named forums is implemented. The default implementation is a template. Because there are different kinds of template engines, this registration does not include the extension used by the engine, though Drupal core only supports PHPTemplate templates for modules. These template files have the extension '.tpl.php'.
It also tells us that the forums theme function (template_preprocess_forums) takes 6 arguments, and they all default to NULL. (All arguments must be given defaults as we have no way to assure that a theme() call will provide the proper information. If in doubt, make the default NULL). These arguments are translated into the named variables for the template. When calling this theme hook, an author might write:
$output = theme('forums', $forums, $topics, $parents, 17, 'ASC', 25);If the 'template' had been left off, the theme hook's default implementation would be assumed to be a function named 'theme_forums'.
There are more options that can be set here, but these two are by far the most important. For more information, see the hook_theme documentation.
Implementing default templates
When implemented as a template, the .tpl.php file is required. It should be in the same directory as the .module file (though the 'path' directive can be used to place these templates in another directory or a sub-directory).
Templates should be as much pure HTML as possible, but there are a few functions that are explicitly encouraged in your templates. First is the t() function. Modules should always provide proper translatability, and templates are no exception. Themers need to have the direct text to work with, and translators need all of the text to be passed through t(). Therefore, the use t() is encouraged in templates.
Another function that is encouraged in templates is format_date(). Since this function is, really, a presentation function, the presentation layer is the appropriate place for it. However, its use is somewhat arcane and difficult for people who are not familiar with PHP to use. Nonetheless, it should be used in templates.
For other functions, consider whether or not they really are needed at the presentation layer. If they are not, they can be used in the preprocessor layer. All templates may have an optional preprocess function, named template_preprocess_HOOK. For example, for our forums theme hook above, its preprocess function will be named template_preprocess_forums. (Look it up in the API. It exists).
The purpose of the preprocess function is to perform any logic that needs to be performed to make data presentable, and to sanitize any data so that it is safe to output. It is critically important that your output be secure and not contain XSS (Cross Site Scripting) vulnerabilities. And since data that is output often comes from users, this data must be sanitized before it is output. Since we assume that themers are not necessarily developers, we must assume that they are not going to fully understand how to do this; but that's ok, because we can sanitize data in the preprocess function by running it through check_plain, check_markup, filter_xss_admin or other output sanitizing functions.
Here is a simple example:
<?php
function template_preprocess_poll_bar(&$variables) {
if ($variables['block']) {
$variables['template_files'][] = 'poll-bar-block';
}
$variables['title'] = check_plain($variables['title']);
$variables['percentage'] = round($variables['votes'] * 100 / max($variables['total_votes'], 1));
}
?>First, note that the preprocessor function takes a reference to an array of variables. This array will be seeded with the arguments that were sent via the theme() and named by the 'arguments' section of the hook registration. Since this is a reference, simply modifying this array is enough to transport those changes to the template that accompanies it.
This example illustrates three important concepts:
- The 'title' field is unsafe, because it comes from user input. It is run through check_plain so that the template may safely output it.
- The theme hook receives the total number of votes and the number of votes for just that item, but the template wants to display a percentage. That kind of work shouldn't be done in a template; instead, the math is performed here. The variables still exist, though; a themer overriding this could easily choose to display something other than a percentage.
- The special variable 'template_files' can be used to provide alternative templates to use. This is an array of template files, and it is last in, first out, which means the last item added to the array will be the first one used. If that template doesn't exist, it will use the next one in the list. This method can be quite useful to use an array of different templates, but be warned that it is limited to just templates.
Devel.module includes a template log feature which outputs at bottom of page all the template files which could have been used to theme the current page. This may be handy while building your module, but even more so when themeing a site.
Quick note: Template files should be named with hyphens instead of underscores. If the theme hook is 'forum_list', the template file should be named 'forum-list.tpl.php'.
Implementing theme functions
Drupal allows you to use functions for your default theme implementations. There is one benefit to this, which is that it is somewhat faster, because there is no checking for alternative templates (since there aren't any), and there isn't a preprocess function for it.
The downside is that there isn't a preprocess function. This makes it harder for themers to really override your theme function unless they're developers. This is especially true if your theme function really is more code than presentation. In general, please try to use templates wherever it is possible.
Theme functions are named by prepending 'theme_' to the name of the hook. The arguments given to theme('hook') will be passed straight through, unaltered. The defaults specified in the hook registration will not be provided here; they must be provided as normal PHP argument defaults.
<?php
function theme_forums($topics = NULL, $parents = NULL, $tid = NULL, $sortby = NULL, $forum_per_page = NULL) {
...
return $output;
}
?>Dynamic theming
In addition to being able to specify alternate templates in a preprocess function, Drupal can also help create dynamic theming implemenations which use wildcards to allow creating 'more specific' theming implementations.
This is accomplished in two parts. First, in hook_theme, you can specify a pattern. Patterns are simple regular expressions. ^ (beginning of line) is assumed, but $ (end of line) is not. To signify the dynamic portion of the pattern, a double underscore is the general convention; this is not required but it is highly recommended.
Second, when calling the theme() function, instead of a string for the first argument you may pass an array. This array is much like template_files above, but this one is first in, first out so the first one seen will be used.
For a practical example, the module Views likes to let each view be themed by name. Upon registration, the hook 'views_view' would register with the pattern 'views_view__'. When theming that view, Views would call:
$output = theme(array("views_view__$view->name", 'views_view'), $view);Views will implement a default view for views_view; if a theme registers 'views_view__foo' and Views themes a view named 'foo', the specific override will activate and be used instead. Unlike the 'template_files' variable in the preprocessor function, this works for both theme functions as well as templates.
theme('table') and theme('item_list')
Drupal provides a few helpers to build complex HTML constructs easily. These are very useful features, and by using them it is easy to create a consistent look on tables and lists; however, there is a downside. That downside is that they are not readily accessible to a themer. Instead, they place code that should be at the presentation layer into the logic layer, and only advanced themers are able to do anything with it.
When creating output that is likely to be changed, it is best to avoid the use of these constructs and create the tables and lists with real HTML code. The forum themes are perfect examples of how to accomplish this and still create HTML code that is consistent.

theme functions: Need to "restart" the module
If you are implementing a custom theme functions instead of implementing default templates, you will need to "restart" (disable and enable) your module after each modification in theme_custom() function.
Example here: http://drupal.org/node/226533
Damien
----------------
Keep Open Spirit
or use the devel module
or use the devel module to empty the cache...