Extending Drupal templating

One of the most common questions on Drupal's theming forums is how to create an individual layout for a specific page or node. In this tutorial I'll show you how to do just that!

Let's start off by opening template.php in your theme's folder. If you do not have this file you're probably using a theme that is not powered by the phpTemplate templating engine, and as such, this tutorial will not be of much use to you.

Rather than walking you through every line of code (which is simple enough to be understood by anyone with basic PHP knowledge), I'll just provide the good stuff and explain it beneath:

Throw the following code into template.php, integrating with existing code when appropriate.

For Drupal 5.x:

function _phptemplate_variables($hook, $vars = array())
{
    switch ($hook)
    {
        case 'page': 
            //code block from the Drupal handbook
            
            //the path module is required and must be activated
            if(module_exists('path'))
            {
                //gets the "clean" URL of the current page
                $alias = drupal_get_path_alias($_GET['q']);
                
                $suggestions = array();
                $template_filename = 'page';
                foreach(explode('/', $alias) as $path_part)
                {
                    $template_filename = $template_filename.'-'.$path_part;
                    $suggestions[] = $template_filename;
                }
                
                $vars['template_files'] = $suggestions;
            }
                                    
            break;
        
        case 'node':
            //default template suggestions for all nodes
            $vars['template_files'] = array();
            $vars['template_files'][] = 'node';
        
            //individual node being displayed
            if($vars['page'])
            {    
                $vars['template_files'][] = 'node-page';
                $vars['template_files'][] = 'node-'.$vars['node']->type.'-page';
                $vars['template_files'][] = 'node-'.$vars['node']->nid.'-page';
            }
            //multiple nodes being displayed on one page in either teaser
            //or full view
            else
            {
                //template suggestions for nodes in general
                $vars['template_files'][] = 'node-'.$vars['node']->type;
                $vars['template_files'][] = 'node-'.$vars['node']->nid;
                
                //template suggestions for nodes in teaser view
                //more granular control
                if($vars['teaser'])
                {
                    $vars['template_files'][] = 'node-'.$vars['node']->type.'-teaser';    
                    $vars['template_files'][] = 'node-'.$vars['node']->nid.'-teaser';
                }
            }
                        
            break;
            
        case 'block':    
            $vars['template_files'] = array('block', 'block-'.$vars['block']->delta);
            
            if($vars['block']->subject != '')
            {
                //the "cleaned-up" block title to be used for suggestion file name
                $subject = str_replace(" ", "-", strtolower($vars['block']->subject));
                
                $vars['template_files'][] = 'block-'.$subject;
            }
            
            break;
    }    
  
    return $vars;
}

For Drupal 6.x:

function phptemplate_preprocess_page(&$vars)
{
    //code block from the Drupal handbook
            
    //the path module is required and must be activated
    if(module_exists('path'))
    {
        //gets the "clean" URL of the current page
        $alias = drupal_get_path_alias($_GET['q']);
        
        $suggestions = array();
        $template_filename = 'page';
        foreach(explode('/', $alias) as $path_part)
        {
            $template_filename = $template_filename.'-'.$path_part;
            $suggestions[] = $template_filename;
        }
        
        $vars['template_files'] = $suggestions;
    }
}

function phptemplate_preprocess_node(&$vars)
{
    //default template suggestions for all nodes
    $vars['template_files'] = array();
    $vars['template_files'][] = 'node';

    //individual node being displayed
    if($vars['page'])
    {    
        $vars['template_files'][] = 'node-page';
        $vars['template_files'][] = 'node-'.$vars['node']->type.'-page';
        $vars['template_files'][] = 'node-'.$vars['node']->nid.'-page';
    }
    //multiple nodes being displayed on one page in either teaser
    //or full view
    else
    {
        //template suggestions for nodes in general
        $vars['template_files'][] = 'node-'.$vars['node']->type;
        $vars['template_files'][] = 'node-'.$vars['node']->nid;
        
        //template suggestions for nodes in teaser view
        //more granular control
        if($vars['teaser'])
        {
            $vars['template_files'][] = 'node-'.$vars['node']->type.'-teaser';    
            $vars['template_files'][] = 'node-'.$vars['node']->nid.'-teaser';
        }
    }
}

function phptemplate_preprocess_block(&$vars)
{
    //the "cleaned-up" block title to be used for suggestion file name
    $subject = str_replace(" ", "-", strtolower($vars['block']->subject));
                
    $vars['template_files'] = array('block', 'block-'.$vars['block']->delta, 'block-'.$subject);    
}

Update: If you are using Drupal 6.x, you should clear the "theme registry" whenever you create new theme functions or templates by one of the following two methods:

  • The clear button located at "Administer -> Site configuration -> Performance"
  • Calling drupal_rebuild_theme_registry(). (hint: place this at the top of your template.php file while you're developing the site & remove from production)

In Drupal 5.x everything is handled by the single _phptemplate_variables() function, while in Drupal 6.x the same functionality is achieved by making separate functions for page, node and block content.

What the above code will do is make Drupal look for more template files than just the default node.tpl.php, block.tpl.php and page.tpl.php. We've added considerably more template suggestions, allowing for very granular templating.

The best way to see and understand what template files are being looked for is to print out the $vars['template_files'] array:

echo '<pre>';
print_r($vars['template_files']);
echo '</pre>';

... either at the end of _phptemplate_variables() (for Drupal 5.x) or at the end of the phptemplate_preprocess_page() / phptemplate_preprocess_node() / phptemplate_preprocess_block() functions (for Drupal 6.x).

The output may look something like this (example for pages):

Array
{
    [0] = 'page'
    [1] = 'page-about_us'
    [2] = 'page-about_us-staff'
}

In this example, when the page mysite.com/about-us/staff is loaded, Drupal will look for the following template files: page-about_us-staff.tpl.php, page-about_us.tpl.php and page.tpl.php, using the first available one.

The same technique applies to nodes and to blocks.

When theming, remember to print out $vars['template_files'] to see what template files Drupal will look for!