Image

Theming and templating with Drupal 7

Drupal 7 uses PhpTemplate as theme engine. The system is simple, each module implements its theme functions (HOOK_theme) to generate a markup, in most cases HTML, in a direct way or using a template file (XXXXX.tpl.php). The function of the theme also permits to the module to set variables which, after processing will be used in HTML display.

These are 2 options for this theme function

Either the HOOK_theme declare a function which will build HTML with variables, ex:

function theme_breadcrumb($variables) {
  $breadcrumb = $variables ['breadcrumb'];

  if (!empty($breadcrumb)) {
    // Provide a navigational heading to give context for breadcrumb links to
    // screen-reader users. Make the heading invisible with .element-invisible.
    $output = '<h2 class="element-invisible">' . t('You are here') . '</h2>';

    $output .= '<div class="breadcrumb">' . implode(' » ', $breadcrumb) . '</div>';
    return $output;
  }
}

Next you can use directly this function in your code, passing corresponding variables, you will get the HTML render, ex:

function template_process_page(&$variables) {
  if (!isset($variables ['breadcrumb'])) {
    // Build the breadcrumb last, so as to increase the chance of being able to
    // re-use the cache of an already rendered menu containing the active link
    // for the current page.
    // @see menu_tree_page_data()
    $variables ['breadcrumb'] = theme('breadcrumb', array('breadcrumb' => drupal_get_breadcrumb()));
  }
  if (!isset($variables ['title'])) {
    $variables ['title'] = drupal_get_title();
  }

  // Generate messages last in order to capture as many as possible for the
  // current page.
  if (!isset($variables ['messages'])) {
    $variables ['messages'] = $variables ['show_messages'] ? theme('status_messages') : '';
  }
}

The second option the more useful is to pass variables to a template file whose extension has to be tpl.php. This file includes HTML and only manage variables display (print, echo, simple if/else) defined by the HOOK_theme function.

This file will be created in the module directory which implements the HOOK_theme, example with blocks:

function block_theme() {
  return array(
    'block' => array(
      'render element' => 'elements',
      'template' => 'block',
    ),
    'block_admin_display_form' => array(
      'template' => 'block-admin-display-form',
      'file' => 'block.admin.inc',
      'render element' => 'form',
    ),
  );
}

The first HOOK_theme will pass a variable using the Render API to build HTML from a template file : block, which should be block.tpl.php

<div id="<?php print $block_HTML_id; ?>" class="<?php print $classes; ?>"<?php print $attributes; ?>>

  <?php print render($title_prefix); ?>
<?php if ($block->subject): ?>
  <h2<?php print $title_attributes; ?>><?php print $block->subject ?></h2>
<?php endif;?>
  <?php print render($title_suffix); ?>

  <div class="content"<?php print $content_attributes; ?>>
    <?php print $content ?>
  </div>
</div>

The key point is that modules core and the Drupal core (includes folder) provides all tpl.php files needed to handle all the HTML markup of a Drupal website, with an order of precedence. You can find on this page a very good preview:

And here, templates list which are created by HOOK_theme functions in the Drupal core modules:

As you saw earlier, each complementary module can implement a new template if wanted, which will be in the module folder.

Basic Drupal template

If I create a Drupal theme following documentation (https://www.drupal.org/node/171205) with no tpl.php files, I am still going to have an html render because Drupal looks for every template in each module implemented.

The problem now is that the HTML template provided with Drupal matches with 2011 standards and since HTML evolved quickly, it is imperative to use a HTML5 basic theme to start a Drupal project or to use a front Framework (see bonus at the end of the page)

Template override

Let's talk about one of the most interesting part of the system, the override. The Drupal core module <em>system</em> "modules/system/..." declare the main file html.tpl.php from its HOOK_theme, which is the global HTML wrapper of our Drupal page :

Imagine that this file is not what I want, for example because it is XHTML and I want HTML 5, it's out of question to modify it where it is, because when Drupal will be updated, this file will be overwritten. To do an override, I will simply copy this file in my active front theme directory and then flush registry cache so Drupal will use my theme file instead of the native one! And so on with every possible templates, core ones or declared by complementary modules.

In general in a theme, you don't override every Drupal core template because some are good but you will override as needed. The best method anyway is to start with the least possible overrides and if needed, and add files one by one… It is not useful to say you will need it later, just override what you need when you need it.

You can note that the templates are often generics, for example every block is made with block.tpl.php

But let's imagine that I want a different html structure for the blocks from a specific module or region, or the block with particular delta, I can use the overrides by names:

For example, I want the blocks from the complementary module Simplenews to be different, I have to copy the file block.tpl.php in my active front theme and rename it block—simplenews.tpl.php, empty registry cache and change the HTML code in this bock.

In the end, I can find in my theme with a file with :

  • block.tpl.php (every block, except overrides)
  • block--header.tpl.php (every block in the header region)
  • block--block--1.tpl.php (the custom block 1 created by the user)

All this with a precedence, from the most generic to the most precise element.

Template variables override

An other case, the template suits me, but I wish to change (or add) just one variable used by this template, an example:
I want to add a class to the <body> according to the user role. In this situation, you can use a hook_preprocess_HOOK function.

This hook can be in a module or in the theme, but in this case, it is more consistent to put it in the theme. If it was in both module and theme, it will be added in last by the theme function.

So I want to add a class to the body markup, I want to alter variables sent to html.tpl.php. I can see in this file a $classes variable, so this is what i want to alter.

I will have this function in my template.php file from my theme (it is actually the php file for the theme functions):

function THEMENAME_preprocess_HTML(&$variables) {
  foreach($variables['user']->roles as $role){
    $variables['classes_array'][] = 'role-' . drupal_HTML_class($role);
  }
}

I simply add a value to the variable, I will be able to delete it, filter it… In this case I don't need to override html.tpl.php file, the preprocess function alter this variable.

A module to help you identify template and suggestions for override :

Advices from my experiences

With my experience, I would say you should start a theme from scratch when you know exactly what you want and you obviously know the Drupal theming and you want to write some PHP. Otherwise, it would be better a better idea to start with a Framework as seen above, you will gain a lot of time… Or better, start from a full front Framework coming with css and javascript components all ready to use.

Most advanced base themes:

Full front Frameworks, you can use:

Golden rule that themers usually flout:

DO NOT do PHP processing in your templates, it's bad!

You can add simple statements (if else…) switches or exist or empty tests but no processing! Processing must go in a theme or module function!

The risk is maintenance and updates which will become really complicated and of course a significant impact on performances.

Comments