124
Drupal Theming Guide Abderrahman Firoud

Drupal 7

  • Upload
    firoud

  • View
    612

  • Download
    3

Embed Size (px)

DESCRIPTION

How to create a Drupal theme

Citation preview

Page 1: Drupal 7

Drupal Theming Guide

Abderrahman Firoud

Page 2: Drupal 7

2

About theming Drupal version: Drupal 6.x, Drupal 7.x Audience: Themers Last modified: May 9, 2011

You can do more with a theme than change the appearance of an entire site. It is also possible to "theme" only certain sections of a site, select types of content, or even individual pages. For example, your theme could specify a different look for just the front page of your site.

Some other things you may not know that you can do with a theme are:

Change layouts, images or fonts Hide or display fields dependent on user role Dynamically respond to changes in the content or to user input Modify or replace text (for example the labels) and variables generated by modules It's also possible to port open source designs between other systems (Joomla!

templates, WordPress themes, etc.) and Drupal, or convert any website layout or template into a Drupal theme

Page 3: Drupal 7

3

Theming Drupal 6 and 7 There were several changes in the theme system between Drupal 6 and 7. This guide contains information on both.

The default theme for Drupal 7 is Bartik, and for Drupal 6 is Garland.

Please note: Not all information has been updated for Drupal 7, but more information is continually being added about Drupal 7 theming.

Page 4: Drupal 7

4

How the Drupal theme system works Pages in this section describe the way the Drupal theme system operates, theme components and settings, .info files and page templates. This section is a starting point for creating a new custom theme.

Overview of theme files Structure of the .info file Default .info values Assigning content to regions Theme settings Clearing the theme cache Creating a sub-theme

Page 5: Drupal 7

5

Overview of theme files A theme is a collection of files that define the presentation layer. You can also create one or more "sub-themes" or variations on a theme. Only the .info file is required, but most themes and sub-themes will use other files as well. The following diagram illustrates the files that are found in a typical theme and sub-theme.

Drupal 6

Page 6: Drupal 7

6

Drupal 7

.info (required)

All that is required for Drupal to see your theme is a ".info" file. Should the theme require them, meta data, style sheets, JavaScripts, block regions and more can be defined here. Everything else is optional.

The internal name of the theme is also derived from this file. For example, if it is named "drop.info", then Drupal will see the name of the theme as "drop". Drupal 5 and below used the name of the enclosing folder of the theme.

Info files for themes are new in Drupal 6. In version 5, .info files were used solely for modules.

template files (.tpl.php)

Page 7: Drupal 7

7

These templates are used for the (x)HTML markup and PHP variables. In some situations they may output other types of data --xml rss for example. Each .tpl.php file handles the output of a specific themable chunk of data, and in some situations it can handle multiple .tpl.php files through suggestions. They are optional, and if none exists in your theme it will fall back to the default output. Refrain from having complex logic in these files. In most cases, it should be straight (x)HTML tags and PHP variables. A handful of these templates exist in directories where core and contributed modules exist. Copying them to your theme folder will force Drupal to read your version.

Note: The theme registry caches information about the available theming data. You must reset it when adding or removing template files or theme functions from your theme.

template.php

For all the conditional logic and data processing of the output, there is the template.php file. It is not required, but to keep the .tpl.php files tidy it can be used to hold preprocessors for generating variables before they are merged with the markup inside .tpl.php files. Custom functions, overriding theme functions or any other customization of the raw output should also be done here. This file must start with a PHP opening tag "<?php", but the close tag is not needed and it is recommended that you omit it.

Sub-themes

On the surface, sub-themes behave just like any other theme. The only differences is that they inherit the resources from their parent themes. To create one, a "base theme" entry inside the .info file is needed. From there it will inherit the resources from its parent theme. There can be multiple levels of inheritance; i.e., a sub-theme can declare another sub-theme as its base. There are no hard set limits to this.

Drupal 5 and below required sub-themes to be in sub-directories of the parent theme. This is no longer the case.

Others

The logo and screen shot is not absolutely necessary for the theme to function, but it is recommended, especially if you are contributing your theme to the Drupal repository. Screenshots will show inside the theme administration page and the user account settings for selecting themes when the appropriate permissions are set. See the screenshot guidelines for more information.

To supply administrative UI settings or "features" beyond logo, search, mission, etc., a "theme-settings.php" file can be used. This is an advanced feature. More information can be found in the Advanced settings handbook page.

For color module support, a "color" directory with a "color.inc" file is needed along with various support files.

Page 8: Drupal 7

8

If you want to base your work on a core theme, use sub-theming or make a copy and rename the theme. Directly modifying Bartik, Garland or Minnelli is strongly discouraged, since they are used for the install and upgrade process.

All non-Core or modifications to Core themes should be installed under the "sites/all/themes" directory to keep them separate from core files. If you plan to run multiple sites from a single Drupal code base, you can make a theme available to a specific site rather than all sites; read about how to set this up in Multi-site installations.

Page 9: Drupal 7

9

Structure of the .info file The .info file is a static text file for configuring a theme. Each line in the .info file is a key-value pair with the key on the left and the value on the right, with an "equals sign" between them (example: key = value). Semicolons are used to comment out a line. Some keys use a special syntax with square brackets for building a list of associated values, referred to as an "array". If you are unfamiliar with arrays, have a look at the default .info files that come with Drupal and read the explanations of the examples that follow. Even though the .info file extension is not natively opened by an Application, you can use TextEdit on a Mac or Notepad on a Windows computer in order to view, edit, and save your changes.

Note that this page describes .info files used for Drupal themes, and not modules. For information about the structure of .info files for Drupal modules, see Writing .info files in the Module developer's guide.

Theme name requirements

The name should start with an alphabetic character, can contain numbers and underscores, but not hyphens, spaces or punctuation. The name will be used by Drupal in forming various functions in PHP and therefore it has the same limitations. Warning! Do not choose the same name as a module, as all installed components must have unique names. For locally created themes using a prefix that is likely to be unique is good for theme naming. A site example.com might call its themes ex_themename.

Because the .info file is cached, you must clear the cache before any changes are displayed in your site.

The .info file can also specify which theme settings should be accessed from the Drupal administration interface, as you will soon see.

Encoding

The file must be saved as UTF-8 without a Byte Order Mark (BOM).

Contents

Drupal understands the keys listed below. Drupal will use default values for the optional keys not present in the .info file. See the examples set for core themes.

name required description recommended screenshot version discouraged core required engine required in most cases base theme regions features stylesheets

Page 10: Drupal 7

10

scripts php

name (required)

The human readable name can now be set independently from the internal "machine" readable name. This imposes fewer restrictions on the allowed characters.

name = A fantasy name

description (recommended)

A short description of the theme. This description is displayed on the theme select page at "Administer > Site building > themes".

description = Tableless multi-column theme designed for blogs.

screenshot

The optional screenshot key tells Drupal where to find the theme's thumbnail image, used on the theme selection page (admin/build/themes). If this key is omitted from the .info file, Drupal uses the "screenshot.png" file in the theme's directory.

Use this key only if your thumbnail file is not called "screenshot.png" or if you want to place it in a directory outside of your theme's base directory (e.g. screenshot = images/screenshot.png).

screenshot = screenshot.png

More details on creating a screenshot for the administration page.

version (discouraged)

The version string will automatically be added by drupal.org when a release is created and a tarball packaged. So you may omit this value for contributed themes. However, if your theme is not being hosted on the drupal.org infrastructure, you can give your theme whatever version string makes sense.

version = 1.0

core (required)

From 6.x onward, all .info files for modules and themes must indicate what major version of Drupal core they are compatible with. The value set here is compared with the DRUPAL_CORE_COMPATIBILITY constant. If it does not match, the theme will be disabled.

core = 6.x

Page 11: Drupal 7

11

The drupal.org packaging script automatically sets this value based on the Drupal core compatibility setting on each release node. So people downloading packaged themes from drupal.org will always get the right thing. However, for sites that deploy Drupal directly from git, it helps if you commit this change to the .info file for your theme. This is also a good way to indicate to users of each theme what version of core the HEAD of git is compatible with at any given time.

engine (required in most cases)

The theme engine, which is used by the theme. If none is provided, the theme is assumed to be stand alone, i.e., implemented with a ".theme" file. Most themes should use "phptemplate" as the default engine.

PHPTemplate's job is to discover theme functions and templates for the behavior of the theme. Omit this entry only if you know what you are doing.

engine = phptemplate

base theme

Sub-themes can declare a base theme. This allows for theme inheritance, meaning the resources from the "base theme" will cascade and be reused inside the sub-theme. Sub-themes can declare other sub-themes as their base, allowing multiple levels of inheritance. Use the internal "machine" readable name of the base theme. The following is used in Minnelli, the sub-theme of Garland.

base theme = garland

More details are available on the page Sub-themes, their structure and inheritance.

regions

The block regions available to the theme are defined by specifying the key of 'regions' followed by the internal "machine" readable name in square brackets and the human readable name as the value, e.g., regions[theRegion] = The region name.

If no regions are defined, the following values are assumed.

Drupal 6 default regions:

regions[left] = Left sidebar regions[right] = Right sidebar regions[content] = Content regions[header] = Header regions[footer] = Footer

Drupal 7 default regions:

Page 12: Drupal 7

12

regions[header] = Header regions[highlighted] = Highlighted regions[help] = Help regions[content] = Content regions[sidebar_first] = Left sidebar regions[sidebar_second] = Right sidebar regions[footer] = Footer

You can override the values for your specific needs.

If you override regions in D7, You are obliged to declare the line regions[content] = Content. More details are available on the page Blocks, content and their regions.

features

Various page elements output by the theme can be toggled on and off on the theme's configuration page. The "features" keys control which of these check boxes display on the theme's configuration page. This is useful for suppressing check boxes for elements not defined or used by a theme. To suppress a check box, omit the entry for it. However, if none are defined, all the check boxes will display due to the assumed defaults.

The example below lists all the available elements controlled by the features key.

Drupal 6 features

By commenting out the primary_links and secondary_links elements, their check boxes are suppressed and are not seen by site administrators.

features[] = logo features[] = name features[] = slogan features[] = mission features[] = node_user_picture features[] = comment_user_picture features[] = search features[] = favicon ; These last two disabled by redefining the ; above defaults with only the needed features. ; features[] = primary_links ; features[] = secondary_links

Drupal 7 features

features[] = logo features[] = name features[] = slogan features[] = node_user_picture features[] = comment_user_picture features[] = favicon features[] = main_menu

Page 13: Drupal 7

13

features[] = secondary_menu

More details are available on the page Custom theme settings.

stylesheets

Traditionally, themes default to using style.css automatically and could add additional stylesheets by calling drupal_add_css() in their template.php file. Starting in Drupal 6, themes can also add style sheets through their .info file.

stylesheets[all][] = theStyle.css

Starting in Drupal 7, themes no longer default to using style.css if it is not specified in the .info file.

More details are available in the style sheets section.

scripts

Traditionally, themes could add Javascripts by calling drupal_add_js() in their template.php file. Starting in 6.x, if a file named script.js exists in the theme directory then it is automatically included. However, in Drupal 7, this behavior has been changed again so that script.js is only included if it has been specified in the .info file:

scripts[] = myscript.js

More details are available in the JavaScript & jQuery section.

php

This defines the minimum PHP version the theme will support. The default value is derived from the DRUPAL_MINIMUM_PHP constant, which is the minimum required version for the rest of core. This can be redefined for a newer version if needed. For most themes, this should not be added.

php = 4.3.3

Example .info files from core themes

Page 14: Drupal 7

14

Garland:

name = Garland description = Tableless, recolorable, multi-column, fluid width theme (default). version = VERSION core = 6.x engine = phptemplate stylesheets[all][] = style.css stylesheets[print][] = print.css ; Information added by drupal.org packaging script on 2008-02-13 version = "6.0" project = "drupal" datestamp = "1202913006"

Minnelli sub-theme of Garland.:

name = Minnelli description = Tableless, recolorable, multi-column, fixed width theme. version = VERSION core = 6.x base theme = garland stylesheets[all][] = minnelli.css ; Information added by drupal.org packaging script on 2008-02-13 version = "6.0" project = "drupal" datestamp = "1202913006"

Note that everything from the line "; Information added by drupal.org packaging script on 2008-02-13" and down is added by the drupal.org packaging script. You should never manually add the project and datestamp keys. The version key added manually (in the first section) allows sites to use your theme when taken directly from git.

Page 15: Drupal 7

15

Default .info values The following are the assumed defaults. When they are not defined, the theme will automatically take these values.

Note: These defaults apply as a group. In other words, overriding a region with regions[sub_header] = Sub-header will omit the rest of the default regions. To gain them back, they must be redefined. This also applies to stylesheets. Even though it's not technically in a group, defining another stylesheet will prevent "style.css" from being included unless it is redefined.

regions Drupal 6 regions[left] = Left sidebar regions[right] = Right sidebar regions[content] = Content regions[header] = Header regions[footer] = Footer

Drupal 7

regions[sidebar_first] = Left sidebar regions[sidebar_second] = Right sidebar regions[content] = Content regions[header] = Header regions[footer] = Footer regions[highlighted] = Highlighted regions[help] = Help regions[page_top] = Page Top regions[page_bottom] = Page Bottom

engine Drupal 7 engine = phptemplate

features Drupal 6 features[] = logo features[] = name features[] = slogan features[] = mission features[] = node_user_picture features[] = comment_user_picture features[] = search features[] = favicon features[] = primary_links features[] = secondary_links

Drupal 7

features[] = logo features[] = name features[] = slogan features[] = node_user_picture

Page 16: Drupal 7

16

features[] = comment_user_picture features[] = favicon features[] = main_menu features[] = secondary_menu

screenshot screenshot = screenshot.png

Stylesheets and JavaScript defaults The style.css and script.js files are only defaults in Drupal 6. In Drupal 7 you must define all CSS and JavaScript files you want the theme to use.

stylesheets stylesheets[all][] = style.css

scripts scripts[] = script.js

php (minimum support) DRUPAL_MINIMUM_PHP is a constant. It points to the minimum requirements for Drupal core to run. php = DRUPAL_MINIMUM_PHP

Page 17: Drupal 7

17

Assigning content to regions Regions are areas in a theme that are available for adding blocks and content to. The regions available in the theme are defined within .info files. They are specified with the key of 'regions' followed by the internal "machine" readable name in square brackets and the human readable name as the value, e.g., regions[theRegion] = The region label.

If none are defined, the following values are assumed in Drupal 6.

regions[left] = Left sidebar regions[right] = Right sidebar regions[content] = Content regions[header] = Header regions[footer] = Footer

Drupal 7 adds Highlighted and Help as default regions. By default, the textual content of the Help region is the same as the $help variable was in page.tpl.php for Drupal 6. The "machine" readable names of the sidebars have also changed names.

regions[sidebar_first] = Left sidebar regions[sidebar_second] = Right sidebar regions[content] = Content regions[header] = Header regions[footer] = Footer regions[highlighted] = Highlighted regions[help] = Help

Drupal 7 bartik theme has following default regions -

regions[header] = Header regions[help] = Help regions[page_top] = Page top regions[page_bottom] = Page bottom regions[highlighted] = Highlighted regions[featured] = Featured regions[content] = Content regions[sidebar_first] = Sidebar first regions[sidebar_second] = Sidebar second regions[triptych_first] = Triptych first regions[triptych_middle] = Triptych middle regions[triptych_last] = Triptych last regions[footer_firstcolumn] = Footer first column regions[footer_secondcolumn] = Footer second column regions[footer_thirdcolumn] = Footer third column regions[footer_fourthcolumn] = Footer fourth column regions[footer] = Footer

Keep in mind that the internal names are converted into region variables inside the "page.tpl.php" template automatically. In the above example, the [header] region will output all the blocks assigned to it through the $header variable in Drupal 6, or $page['header'] in Drupal 7. There are a few restrictions on naming variables in PHP, so make sure the

Page 18: Drupal 7

18

internal/machine names conform to the same restrictions. Basically, your internal region names can only contain alphanumeric characters and underscores, and they should start with a letter.

The human readable names outside the square brackets are used for labeling the region in the block administration page located at "Administer > Site building > Blocks" and in Drupal 7 the block administration page is located at "Administration > Structure > Blocks" .

Here is the block administration table for Garland:

Drupal 7 - Block administration table

Page 19: Drupal 7

19 for

Page 20: Drupal 7

20

Bartik:

A few notes:

There are template (.tpl.php) files available for rendering individual blocks. Adding a custom region prevents the defaults from being used. If you want to keep the

defaults in addition to custom regions, manually add in the defaults. The order in which the regions are defined will be reflected in the block configuration

table. Garland, for example, uses the default regions. Notice the order of the regions listed in the image.

The content of the .info file is cached in the database, so altering it will not be noticed by Drupal. (Do not confuse this with the theme registry.) To learn how to clear it, check out the options in Clearing the theme cache.

Upgrade notes:

The $footer_message region variable has been removed in Drupal 7.

The $content region In Drupal 6 and before the $content variable in page.tpl.php contained the main page content appended with the blocks positioned into the content region (if you had that region defined).

In Drupal 7, $content became a full region and is now mandatory in all themes. This new requirement was set up so that when enabling new themes, Drupal knows where to put the main page content by default.

In Drupal 6, it was only possible to put blocks after the main page content in this region. The only way to put blocks before the main page content was to define a specific region for that purpose. Drupal 7 now makes the main page content its own block. This makes it possible to put blocks before or after the main page content in the region without hacking in a new region.

Manually assigning content to regions:

Content can be manually assigned to regions with drupal_set_content(). For example, drupal_set_content('header', 'Welcome!') would assign the text 'Welcome!' to the header region.

Here is a more useful example for building a summary of all the comments into the "right" region. Rename the "drop" prefix with the name of your theme. More information on preprocessors is available.

<?php function drop_preprocess_comment(&$variables) { // Setup a few variables. $comment = $variables['comment']; $title = l( $comment->subject, comment_node_url(), array('fragment' => "comment-$comment->cid")

Page 21: Drupal 7

21

); $new_marker = $comment->new ? t('new') : ''; $by_line = t('by') .' '. theme('username', $comment); // Form the markup. $summary = '<div class="comment-sidebar">'; $summary .= '<span class="title">' . "$title $new_marker</span>"; $summary .= '<span class="credit">' . "$by_line</span>"; $summary .= '</div>'; // Set the comment into the right region. drupal_set_content('right', $summary); } ?>

Note that setting content through this function should happen before the block regions are retrieved and that is done with a call from template_preprocess_page > theme_blocks > drupal_get_content.

Page 22: Drupal 7

22

Checking to see if a region is occupied While theming page.tpl.php it's possible to check to see whether a region is empty, by checking the content of the relevant variable which contains the region's contents.

For example; Drupal 6

<?php if($left) { // do something } ?> Drupal 7 <?php if($page['sidebar_first']) { // do something } ?>

However, region variables haven't been defined for templates at the block and node and view levels.

To deal with this case, part of the block.module code could be adapted to create a function which can be inserted in your theme template.php file.

The function takes one parameter (a region name), and returns 1 if the region is empty or 0 if the region is occupied. The function examines the block visibility setting for the current path to work out if the region is occupied.

<?php function region_empty($test_region) { /* Check to see if a region is occupied * returns 1 if it's empty */ $test_empty = 1; $result = db_query_range('SELECT n.pages, n.visibility FROM {blocks} n WHERE n.region="%s" AND n.theme="%s"', $test_region, $GLOBALS['theme'], 0, 10); if (count($result) > 0) { while ($node = db_fetch_object($result)) { if ($node->visibility < 2) { $path = drupal_get_path_alias($_GET['q']); // Compare with the internal and path alias (if any). $page_match = drupal_match_path($path, $node->pages); if ($path != $_GET['q']) { $page_match = $page_match || drupal_match_path($_GET['q'], $node->pages); }

Page 23: Drupal 7

23

// When $block->visibility has a value of 0, the block is displayed on // all pages except those listed in $block->pages. When set to 1, it // is displayed only on those pages listed in $block->pages. $page_match = !($node->visibility xor $page_match); } else { $page_match = drupal_eval($block->pages); } if ($page_match) $test_empty = 0; } } return $test_empty; } ?>

Page 24: Drupal 7

24

Theme settings Various page elements output by the theme can be toggled on and off on the theme's configuration page.

Drupal 7

You can locate these settings at "Administer > Appearance > Settings > themeName". For example, the site's slogan can be suppressed by unchecking the "Site slogan" check box on that page.

These checkboxes show themselves depending on the features enabled inside the .info file. It must be specified with the key of 'features' followed by empty brackets then the feature itself, e.g., features[] = the_feature. If none are defined, the following values are assumed.

features[] = logo features[] = name features[] = slogan features[] = node_user_picture features[] = comment_user_picture features[] = comment_user_verification features[] = favicon features[] = main_menu features[] = secondary_menu

Drupal 7 removes the previously available mission and search as theme features (they can be created and controlled as blocks instead) and adds a toggle for "User verification status in comments".

To disable any features, only add the ones you want into the .info file. Defining only the features needed for the theme will omit the rest. Some of the features will also enable related form fields. For example, 'logo' will enable an upload field for the image along with the checkbox.

Page 25: Drupal 7

25

A few notes:

The contents of the .info file are cached in the database so altering it will not be noticed by Drupal. (Do not confuse this with the theme registry.) To learn how to clear it, check out the options in Clearing the theme cache.

hook_features() is no longer supported.

Drupal 6

In Drupal 6, these settings were located at "Administer > Site building > Themes > themeName"

These were the default values in Drupal 6:

features[] = logo features[] = name features[] = slogan features[] = mission features[] = node_user_picture features[] = comment_user_picture features[] = search features[] = favicon features[] = primary_links features[] = secondary_links

Page 26: Drupal 7

26

Global Settings Next to List, select configure. This will show the global theme settings option where you can toggle the display of features supported by themes. Note that these are the global settings and can be overridden on a per-theme basis by configuring each theme individually.

This is a list of the default features with a brief explanation and tips to help you get started.

Note that not all themes support all features, and some may not be displayed at all (themes can hide the display of features they don't support).

Logo Most themes print a clickable logo, this setting toggles it on or off. You can upload your own logo using the form on the configuration page or replace logo.png in the theme folder (most themes use the logo.png convention).

Site name, Site slogan Toggle the Site name or slogan on or off. These are both set in the "Site Information" admin page. Most themes print the Site name and slogan in the header.

Mission statement Many themes support a Mission statement which you can add in the "Site Information" admin page. Mission statements only print on the front page by default.

User picture in posts, User picture in comments If a user has uploaded a user picture, or you have set a default user picture, you can toggle the display on or off for both nodes and comments. If these settings are grayed out, you first need to visit the User Settings admin page and enable support for user pictures.

Search box Many themes print a search form, often in the header or a sidebar. If this option is grayed out you need to enable the Search module.

Primary links, Secondary links These are both menus, which you can add links to either from node forms or in the Menu admin section. Many themes display Primary and Secondary links as horizontal menus in the header. Not all themes support Secondary links, but most support Primary links.

Page 27: Drupal 7

27

Themes - Garland

For starting out Garland has the ability for you to select several areas of its color scheme. Drupal 6 comes with fifteen preset color choices and your own custom choices. Feel free to experiment. It is required that the color module is enabled, which it is by default.

Page 28: Drupal 7

28

Page 29: Drupal 7

29

Integrating color module Color.module allows the admin to change the color scheme of a theme completely. By selecting a palette of 5 colors (either from a set or by hand), you can change the colors of an entire theme.

The module can alter the stylesheet and re-render images. However, the theme must provide specific hooks to allow this, and the design must be created specifically to accomodate this.

This document explains the basics of making a colorizable theme.

Page 30: Drupal 7

30

Design

It is important to realize that due to the way color.module works, not every design can be colorized.

Page 31: Drupal 7

31

We take a transparent image of the design (the base), which includes everything except the background. We then compose this image on top of a colored background, to get the colored versions. Finally we slice up this composite image into smaller images and save them to separate image files.

We also process the stylesheet and change all the colors based on the ones you defined. The module smoothly changes the colors using the palette as a reference. Colors that don't appear literally in the palette are adjusted relative to the closest matching palette color (whether it is a link, text or background color).

So, the photoshop mockup of the design should consist of a layered file that has one or more colored layers at the bottom of the layer stack, with everything else blended on top. When you save the base image, you have to merge all layers together, while keeping the colored layers invisible. Take a look at Garland's base.png file to see an example (open it in an image editor to see the transparencies). There is a video showing how to create your own base.png file using Photoshop.

All the files generated in this process are written to /files/css and used instead of the default images. This means that a colorizable theme should still work out of the box without color.module, in the default color scheme.

In Practice

Let's use Garland as an example. The most important files are in the themes/garland/color subdirectory:

base.png This contains the base design of the theme, which is composed and sliced into the images.

color.inc This file contains all the necessary parameters to color the theme. See below.

preview.css This stylesheet is used to generate the preview on the color changer.

preview.png This image is used to generate the preview on the color changer.

The presence of color/color.inc makes the color picker appear on the theme's settings. It is a regular PHP file which contains an $info array with the following values:

Schemes <?php array('schemes' => array(

Page 32: Drupal 7

32

'#0072b9,#027ac6,#2385c2,#5ab5ee,#494949' => t('Blue Lagoon (Default)'), '#464849,#2f416f,#2a2b2d,#5d6779,#494949' => t('Ash'), '#55c0e2,#000000,#085360,#007e94,#696969' => t('Aquamarine'), '#d5b048,#6c420e,#331900,#971702,#494949' => t('Belgian Chocolate'), '#3f3f3f,#336699,#6598cb,#6598cb,#000000' => t('Bluemarine'), '#d0cb9a,#917803,#efde01,#e6fb2d,#494949' => t('Citrus Blast'), '#0f005c,#434f8c,#4d91ff,#1a1575,#000000' => t('Cold Day'), '#c9c497,#0c7a00,#03961e,#7be000,#494949' => t('Greenbeam'), '#ffe23d,#a9290a,#fc6d1d,#a30f42,#494949' => t('Mediterrano'), '#788597,#3f728d,#a9adbc,#d4d4d4,#707070' => t('Mercury'), '#5b5fa9,#5b5faa,#0a2352,#9fa8d5,#494949' => t('Nocturnal'), '#7db323,#6a9915,#b5d52a,#7db323,#191a19' => t('Olivia'), '#12020b,#1b1a13,#f391c6,#f41063,#898080' => t('Pink Plastic'), '#b7a0ba,#c70000,#a1443a,#f21107,#515d52' => t('Shiny Tomato'), '#18583d,#1b5f42,#34775a,#52bf90,#2d2d2d' => t('Teal Top'), )); ?>

This entry contains a straightforward array of pre-defined color schemes. Each entry must have 5 colors (which, in order, are base color, link color, top header, bottom header, and text color) formatted as above, and a title.

The first scheme is used as a reference and must match the colors used in the theme's default images and stylesheet closely. Otherwise, the final colors might not be what the user intended. See the 'stylesheets' section for more information about how the colors are calculated.

Images to copy <?php array('copy' => array( 'images/menu-collapsed.gif', 'images/menu-expanded.gif', 'images/menu-leaf.gif', )); ?>

This array contains a list of images which should not be altered. They are copied to the location of the generated images and stylesheet.

Fill areas and Gradients

To color the image, we create a target image that is the same size as the base image, and draw colored areas and a gradient. For full flexibility, the location of these areas is defined by specifying their coordinates using (x, y, width, height):

<?php array('gradient' => array(0, 37, 760, 121)); ?>

You can specify one vertical two-color gradient.

<?php array('fill' => array( 'base' => array(0, 0, 760, 568), 'link' => array(107, 533, 41, 23),

Page 33: Drupal 7

33

)); ?>

You can specify regions for each of the palette colors. The region will be filled in with the selected color. Available colors are 'base', 'link', 'top', 'bottom' and 'text'.

Image slices

Next, you need to define the areas of the base image to slice out for each of the images. Again, you specify coordinates as (x, y, width, height) along with the filename of the image, as used in the stylesheet. The logo and screenshot slices are special and always take the same filename. The screenshot will be resized to 150x90 pixels.

<?php array('slices' => array( 'images/body.png' => array(0, 37, 1, 280), 'images/bg-bar.png' => array(202, 530, 76, 14), 'images/bg-bar-white.png' => array(202, 506, 76, 14), 'images/bg-tab.png' => array(107, 533, 41, 23), 'images/bg-navigation.png' => array(0, 0, 7, 37), 'images/bg-content-left.png' => array(40, 117, 50, 352), 'images/bg-content-right.png' => array(510, 117, 50, 352), 'images/bg-content.png' => array(299, 117, 7, 200), 'images/bg-navigation-item.png' => array(32, 37, 17, 12), 'images/bg-navigation-item-hover.png' => array(54, 37, 17, 12), 'images/gradient-inner.png' => array(646, 307, 112, 42), 'logo.png' => array(622, 51, 64, 73), 'screenshot.png' => array(0, 37, 400, 240), )); ?>

Files

Finally you need to specify the location of the files for your theme. You need an image and a stylesheet for the preview, as well as the base image*:

<?php array( 'preview_image' => 'color/preview.png', 'preview_css' => 'color/preview.css', 'base_image' => 'color/base.png', ); ?> * As of drupal 6, Color.module will no longer require base_image, meaning it is possible to utilize the module without images.

Stylesheets

The color.module will read in a theme's style.css file as well as any other styles that are imported with @import statements and create a new style.css file. It will change the colors in the CSS using one of the chosen palette colors as a reference, depending on the context:

Links: the 'link' color is used, for rules that apply to a elements.

Page 34: Drupal 7

34

Text: the 'text' color is used, for rules that appear in color: styles. Base: the 'base' color is used for everything else.

However, if a color in the stylesheet matches one of the reference colors exactly, the context will be ignored, and the matching replacement color will be used instead.

For example, suppose your reference color is dark blue by default, but you change it to red. Your default stylesheet contains both light blue and gray purple, both relative to this reference color.

The resulting colors (mauve and brown) are similarly different from red as the original colors were from blue. In technical terms: the relative difference in hue, saturation and luminance is preserved.

If you find color.module is using the wrong reference color, try separating the different pieces into separate CSS rules, each in their own selector { ... } section, so there is no confusion about the context.

Note that if you edit your stylesheet after changing the color scheme, you must resubmit the color scheme to regenerate the color-shifted version.

If you wish for certain colors in the stylesheet not to be altered, you should place their CSS below the following marker:

/******************************************************************* * Color Module: Don't touch * *******************************************************************/

You can only use this marker once in your style.css file. It applies globally, so if you use it inside an imported stylesheet, all colors below the @import statement will be left alone too.

Making colors match

It is important that the generated images match with the shifted colors in the generated stylesheet. Otherwise, ugly edges might appear.

To make this work, pixels in the base image must all be a simple color in areas where they have to match with CSS-defined colors. Because we don't know where CSS-defined colors appear in the base image, we use a global blending color which must be the same in the whole design. Garland uses white. Note that the Garland base does include e.g. gray and black pixels, but only in areas where only images are used as backgrounds (e.g. the header). Other than white, black or grey are good candidates too.

Page 35: Drupal 7

35

<?php array('blend_target' => '#ffffff'); ?>

Masochists can take a peek at color.module's innards, particularly the _color_shift() function if you're interested in the how and why of this.

PHPTemplate changes

Finally, you need to hook color.module into your theme. We'll use a PHPTemplate theme as an example, but this applies to other engines as well.

In your theme's template.php file, add the following snippet (for Drupal 6.x):

<?php /** * Override or insert PHPTemplate variables into the templates. */ function phptemplate_preprocess_page(&$vars) { // Hook into color.module if (module_exists('color')) { _color_page_alter($vars); } } ?>

In Drupal 5.x, you’ll need to add the following snippet:

<?php /** * Override or insert PHPTemplate variables into the templates. */ function _phptemplate_variables($hook, $vars) { if ($hook == 'page') { // Hook into color.module if (module_exists('color')) { _color_page_alter($vars); } return $vars; } return array(); } ?>

This will allow the module to override your theme's logo, stylesheet and screenshot. If you perform other changes in _phptemplate_variables, you need to merge in this snippet.

Page 36: Drupal 7

36

Clearing the theme cache The contents of the .info file is cached in the database, so altering it will not be noticed by Drupal. Do not confuse the cache with the theme registry. To clear the cache, do one of the following:

Click the "Clear all caches" button located under Performance: o Drupal 7: Administration > Configuration > Development > Performance

(admin/config/development/performance) o Drupal 6: Administer > Site configuration > Performance

(admin/settings/performance) Contributed modules:

o Admin menu has clear cache links beneath the home icon. o Devel Block module of the Devel project provides an "Empty cache" link. o The API function drupal_rebuild_theme. o Some themes (Zen, Fusion, ...) provide a checkbox to rebuild the theme cache

on every page, with a nice warning so you don't forget to turn this off. o Drush has a command line command: drush cache-clear theme or the shortcut

can be used as drush cc all to clear all cache. Visiting the theme selection page will also clear the .info file cache.

o Drupal 7: Administration > Appearance (admin/appearance) o Drupal 6: Administer > Site building > Themes (admin/build/themes)

Page 37: Drupal 7

37

Creating a sub-theme Drupal version: Drupal 6.x, Drupal 7.x Audience: Themers Last modified: September 21, 2011

Sub-themes are just like any other theme, with one difference: They inherit the parent theme's resources. There are no limits on the chaining capabilities connecting sub-themes to their parents. A sub-theme can be a child of another sub-theme, and it can be branched and organized however you see fit. This is what gives sub-themes great potential.

Imagine starting with a base theme designed as wireframes, then applying and refining all the details from a sub-theme. Then, from the same wireframe, testing out alternate designs by branching out another sub-theme. Working on a multi-site installation but you need a cohesive look and feel? With sub-theming, a lot of the design resources can be shared. Site-specific changes can be set to a specific sub-theme, but any shared resources can be edited once to be applied across all the installations. With careful planning, the possibilities are endless.

Creating a sub-theme

The sub-theme to-be should be located in its own directory. Prior to Drupal 6, this directory had to be a subdirectory of its base theme; in Drupal 6 and 7 it can be placed outside of the base theme's directory.

To declare your theme to be a sub-theme of another, it is necessary to alter the sub-theme's .info file. Add the following line to the sub-theme's .info to declare its parent or "base theme." Change "themeName" to the internal name of the parent theme (that is, the name of the parent theme's .info file, usually all lower case).

base theme = themeName

Style sheet inheritance

All style sheets defined in the parent theme will be inherited, as long as you declare at least one stylesheet in your sub-theme's .info file. You must declare at least one stylesheet in your sub-theme for any of the parent theme's stylesheets to be inherited.

Page 38: Drupal 7

38

Overriding inherited style sheets: Specify a style sheet with the same filename in the sub-theme. For instance, to override style.css inherited from a parent theme, add the following line to your sub-theme's .info file:

stylesheets[all][] = style.css

You will also need to create the style.css stylesheet file; if you simply wish to disable the imported styles, you can create an empty file.

JavaScript inheritance

All JavaScripts defined in the parent theme will be inherited.

Overriding inherited JavaScript: Specify a JavaScript file with the same filename in the sub-theme's .info file. For instance, to override script.js inherited from a parent theme, add the following line to your sub-theme's .info file:

scripts[] = script.js

You will also need to create the script.js stylesheet file; if you simply wish to disable the imported styles, you can create an empty file.

Template.php function inheritance

Anything defined in the parent theme's template.php file will be inherited. This includes theme function overrides, preprocess functions and anything else in that file. Each sub-theme should also have its own template.php file, where you can add additional functions or override functions from the parent theme.

There are two main types of functions in template.php: theme function overrides and preprocess functions. The template system handles these two types in very different ways.

Theme functions are called through theme('[hook]', $var, ...). When a sub-theme overrides a theme function, no other version of that theme function is called.

On the other hand, preprocess functions are called before processing a .tpl file. For instance, [theme]_preprocess_page is called before page.tpl.php is rendered. Unlike theme functions, preprocess functions are not overridden in a sub-theme. Instead, the parent theme preprocess function will be called first, and the sub-theme preprocess function will be called next.

There is no way to prevent all functions in the parent theme from being inherited. As stated above, it is possible to override parent theme functions. However, the only way to remove a parent theme's preprocess function is through hook_theme_registry_alter().

Page, node, block and other template (.tpl.php) file inheritance

Drupal 7 Any .tpl.php files from the parent theme will be inherited. You can add template files with more specificity — for instance, node--blog.tpl.php building on an inherited node.tpl.php.

Page 39: Drupal 7

39

A single hyphen is still used to separate words: for example, "user-picture.tpl.php" or "node--long-content-type-name.tpl.php", so the double hyphen always indicates a more targeted override of what comes before the "--". See Converting 6.x themes to 7.x for more info.

Drupal 6: Any .tpl.php files from the parent theme will be inherited. However, to add template files with more specificity, you must also copy over the more general template file from the parent theme manually. For instance, to add a node-blog.tpl.php template in a sub-theme, you must also copy over node.tpl.php from the parent theme. This bug has been fixed in Drupal 7 but will not be fixed in Drupal 6.

Overriding inherited .tpl.php templates: Add a template file with the same name in your sub-theme folder to have it override the template from the parent theme.

Screen shots and logo inheritance

The parent theme's screen shot will be inherited. The parent theme's logo (logo.png/logo.jpg) will not be inherited.

Overriding inherited screen shots: Specify a new image file in your sub-theme's .info file.

Region inheritance

Sub-themes do not inherit custom regions from a parent theme. If you are using custom regions, you should copy the region declarations from the parent theme's .info file. Be sure your sub-theme's page.tpl.php file matches the sub-theme's region settings.

Color and theme settings inheritance

Color.module support within the color directory is not inherited.

Theme settings set via advanced theme settings' theme-settings.php are not inherited.

Page 40: Drupal 7

40

Working with CSS Most modern web pages use external style sheets to control the presentation of a page. In a traditional static HTML page, a pointer to a style sheet must be manually placed within the HTML code (usually within the page header).

Here's an example:

<link rel="stylesheet" type="text/css" href="/mytheme.css" />

This code simply tells the browser where to find one of the style sheets (mytheme.css) that controls the appearance of the page.

To the browser, a page from a Drupal site might look exactly the same: the HTML header has the same kind of pointers to external style sheets. The key difference is that behind the scene, those pointers are added to the HTML automatically. Some styles might come from the theme itself and others might be supplied by various Drupal modules (to provide default styling for the module output).

You can add new style sheets to a theme and you can override a default style sheet from a Drupal core or contributed module. If you are using a sub-theme, you can override style sheets from the base theme.

Overriding style sheets from modules and base themes Adding style sheets Adding styles through the API Standard Drupal core styles and classes Supporting "right to left" (RTL) languages

Page 41: Drupal 7

41

Overriding style sheets from modules and base themes You can override the style sheet provided by a core or contributed module.

Most modules provide defaults for the presentation of its output. This includes the markup itself and an associated style sheet. (see the explanation on the overriding behavior for the markup.) These default styles can be overridden by making changes within your theme.

Overriding core and contributed module style sheets

To override a core or contributed module style sheet, it must be specified in your theme's .info file. For example, system-menus.css is located at "modules/system/system-menus.css". If you place a file with the same name in your theme's folder and add the following entry to the .info file, the original system-menus.css file will be ignored and your version will be loaded in its place.

stylesheets[all][] = system-menus.css

Adding the override for a style sheet that does not exist inside the theme will omit the core or module style sheet. This is by design and this behavior has been corrected since the release of 6.0 in 6.3.

A few notes:

Overriding core CSS files will prevent the default "style.css" file from loading. Remember to explicitly define any defaults when needed. In Drupal 7, style.css does not load unless it is specified in the .info file.

The themes override must have a matching media type of the original style. URLs within the replacement style sheet may need to be corrected. Check the file for

any 'url()' properties or '@import' rules to make sure they are pointing to the right resource.

The order of style sheets listed in the head of the page will change. This may cause some cascading rules to change with it.

Some core and module style sheets are loaded conditionally. Overriding through .info files will force the file to be always used.

If only minor changes are required, consider using CSS selectors to override the styles instead of overriding the whole file.

In Drupal 7, if you would like to override some css files please use hook_css_alter in template.php (see example in seven theme).

Remember to clear your cache after making this change!

Overriding a base theme style sheet

The following applies to sub-themes. To prevent a style sheet from a base theme from being carried over to a sub-theme, you can redefine the style sheet inside the .info file. This works the same way as overriding module or core style sheets.

Page 42: Drupal 7

42

The base theme and the sub-theme must have the same entry: stylesheets[all][] = masterStyle.css

If the file exists inside the sub-theme, it will be used; omitting the file in the sub-theme will prevent the file from loading from the base theme as well.

Page 43: Drupal 7

43

Adding style sheets This page explains how to add a style sheet using the .info file of a theme. To add a style sheet programmatically, see the API functions page. Styling themes purely through CSS is possible with the information provided here.

Notes:

When working with style sheets, make sure that CSS Optimization is disabled. It is located in "Administer > Site configuration > Performance". CSS Optimization aggregates all of the style sheets for a site in order to improve performance. When CSS Optimization is enabled, no changes to your style sheets will be reflected on your site until the aggregated styles are cleared. You can enable CSS Optimization again when you're done modifying your style sheets.

The .info file is cached. Adding or removing any styles will not be detected until the cache is cleared and the revised .info is read. (Do not confuse this with the theme registry.) You must clear the cache to see the changes.

Adding style sheets:

In Drupal 6, by default, a "style.css" file will be used from your theme when no other styles are defined inside the .info file. In Drupal 7, the style.css file will be used only if it is specified in the .info file. Adding other styles is as simple as defining a new 'stylesheets' key with its media property and the name of the style sheet. Keep in mind that defining custom styles will prevent the default "style.css" from loading. Remember to explicitly define the default style sheet if your theme uses it.

; Add a style sheet for all media stylesheets[all][] = theStyle.css ; Add a style sheet for screen and projection media stylesheets[screen, projection][] = theScreenProjectionStyle.css ; Add a style sheet for print media stylesheets[print][] = thePrintStyle.css ; Add a style sheet with media query stylesheets[screen and (max-width: 600px)][] = theStyle600.css

A few notes:

Note the empty square brackets between the [media] and = styleName.css. These are always empty and denote that each stylesheet is appended to an array, as in php.

The order in which the styles are listed in the head of the page will reflect the order it is defined here.

The style sheets can be placed in sub-directories, i.e., stylesheets[all][] = stylesheets/styleName.css. Useful for organizing style sheets.

However, it is recommended that sub-directories be kept to one level, i.e.,stylesheets[all][] = css/foo/styleName.css may cause a problem

Page 44: Drupal 7

44

with some templates. Safer is stylesheets[all][] = css/styleName.css or stylesheets[all][] = foo/styleName.css.

Adding external stylesheets To use a stylesheet external to your theme, such as one hosted on a CDN, you cannot use the themes .info file. Instead you can add this in template.php. In Drupal 7 do this as follows: <?php function mytheme_preprocess_html(&$variables) { drupal_add_css('http://fonts.googleapis.com/css?family=News+Cycle', array('type' => 'external')); } ?> In Drupal 6 you cannot specify a stylesheet as being external. Instead you will need to use the theme_preprocess_page to add a &lt;link&gt; element as follows: <?php function mytheme_preprocess_page(&$vars, $hook) { $vars['head'] .= '<link '. drupal_attributes(array( 'rel' => 'stylesheet', 'type' => 'text/css', 'href' => 'http://fonts.googleapis.com/css?family=News+Cycle') ) ." />\n"; } ?>

Page 45: Drupal 7

45

Adding styles through the API Adding styles through the .info file should be sufficient for most themes. Since the .info file is static, style sheets cannot be added dynamically. Depending on how the theme handles style sheets, it may not matter altogether. When in doubt, use the .info file.

There are two API functions for working with style sheets, drupal_add_css and drupal_get_css. Here is an example to dynamically add styles sheets.

Change the "drop" prefix to the name of your theme.

<?php function drop_preprocess_page(&$variables) { $front_style = path_to_theme() .'/front-page.css'; $path_style = path_to_theme() .'/path-'. arg(0) .'.css'; if (file_exists($front_style) && $variables['is_front']) { $include_style = $front_style; } elseif (file_exists($path_style)) { $include_style = $path_style; } if (isset($include_style)) { drupal_add_css($include_style, 'theme', 'all', FALSE); $vars['styles'] = drupal_get_css(); } } ?>

The above example would include the style sheet "front-page.css" on the front page or many others based on the internal path. For example, http://example.com/admin would pickup on "path-admin.css".

A few notes:

Depending on where and when the style is added, drupal_get_css may need to be called in order to include the added styles. They are initially retrieved in template_preprocess_page. See Preprocessors and variables for details on the order of the preprocessors.

There is a parameter in drupal_add_css to aggregate the added file. Consider disabling it like the above example when the inclusion of the style sheet is very dynamic, since files added to the larger aggregate will force a new aggregated CSS file to be recreated. In effect, it can slow down the retrieval of the page and consume more bandwidth.

Where To Add Code

This code can be added in the template.php file in your theme directory. There may already be a function there called "phptemplate_preprocess_page". Just include it within the body of your XXX__preprocess_page function.

Page 46: Drupal 7

46

You may also need to change "variables" to "vars" to make it work.

Page 47: Drupal 7

47

Standard Drupal core styles and classes Drupal core takes a modular approach to CSS classes for standard page elements. A number of classes occur throughout a Drupal site. This list is meant as a quick crib sheet for remembering which classes mean what and occur where. A complete list of classes in Drupal 6 core can be found in the Zen starter kit (STARTERKIT/css/drupal6-reference.css) and should be merged into this document.

Note: themes you download may alter these classes, and add further ones.

Page elements

.menu

All menu trees get this class, such as the navigation menu.

.block

All blocks. See http://drupal.org/node/104319 for more on styling blocks.

.links

Lists of links, including Primary and Secondary links in the page header, and also node links and taxonomy terms (see below).

(Added in D7)

.element-hidden

The role of this class is to hide elements from all users. This class should be used for elements which should not be immediately displayed to any user. An example would be a collapsible fieldset that will be expanded with a click from a user. The effect of this class can be toggled with the jQuery show() and hide() functions.

.element-invisible

The role of this class is to hide elements visually, but keep them available for screen-readers. This class should be used for information required for screen-reader users to understand and use the site where visual display is undesirable. Information provided in this manner should be kept concise, to avoid unnecessary burden on the user. This class must not be used for focusable elements (such as links and form elements) as this causes issues for keyboard only or voice recognition users.

.element-invisible.element-focusable

The .element-focusable class extends the .element-invisible class to allow the element to be focusable when navigated to via the keyboard.

Page 48: Drupal 7

48

Node elements

.node

A wrapper div around all of a node, including its title.

.node-title

The title of the node.

.content

The body of the node. This will include additions other modules make, such as uploaded files or CCK fields.

.links

Applied to any UL that is a list of links, including Primary and Secondary links in the page header, and also node links and taxonomy terms (see below). Node links however get the .links class on their enclosing DIV.

.terms

Taxonomy terms, which also get .links and .inline.

.inline

This is a system class for styling UL items into a horizontal line.

.feed-icon

RSS feed icons, usually at the foot of the page content area.

Core Block IDs

Documented Here

Page 49: Drupal 7

49

.clear-block and .clearfix Drupal 6's “clear-block” class was a Drupalism for functionality which is better known by the CSS Community as “clearfix.” Furthermore, using the “block” term was confusing as it is not dependent on Drupal’s block system. The clear-block class has been renamed to clearfix in Drupal 7.

7.x

<div class="clearfix">

6.x

<div class="clear-block">

Page 50: Drupal 7

50

Supporting "right to left" (RTL) languages Drupal version: Drupal 6.x Last modified: January 29, 2011

Adding support for RTL (Right to Left) languages involves overriding the lateral styles through cascades and naming the file based on the style sheet it is paired to. The inclusion of the RTL style sheet is automated. The inclusion of the file depends on the language settings set for the site.

For example, in the core theme Garland, "style.css" is the main style sheet. It also includes "style-rtl.css" for right to left languages like Arabic or Hebrew. The inclusion of the two styles always loads with the main style first and the RTL style second. This allows cascading of all the rulesets within the two files without having to worry about specificity in the selectors used in the RTL style.

There is a coding standard to keep the rules organized. Rules that are dependent on the lateral positioning or dimensions should be commented with /* LTR */ indicating that the property is specific to a left to right layout. This includes floats, margins, padding, etc. Inline text should flow automatically as long as the theme sets the language direction of the document through the "page.tpl.php" template.

Example base style:

ul.primary-links { margin-top: 1em; padding: 0 1em 0 0; /* LTR */ float: left; /* LTR */ position: relative; }

Corresponding RTL style:

ul.primary-links { padding: 0 0 0 1em; float: right; }

While working with the main CSS file, this makes it easier to spot where changes may be needed in the RTL style.

Note that if your theme overrides a module style, the associated RTL style will be omitted unless it is present in your theme.

Page 51: Drupal 7

51

Advanced theming Drupal version: Drupal 6.x, Drupal 7.x Last modified: May 6, 2011

This section goes beyond basic theming concepts, and discusses more ways you can extend your theme.

These topics are good ones to be familiar with to master the art of theming.

Creating advanced theme settings Custom theme for custom form 6.x Theming Custom Entities Theming forms in your theme Targeting different devices JavaScript and jQuery

Page 52: Drupal 7

52

Creating advanced theme settings Drupal 6 In the Drupal administration section, each theme has its own settings page at admin/build/themes/settings/themeName. And this page has a form with standard settings like “Logo image settings” and “Shortcut icon settings.”

Drupal 7 Drupal 7 uses admin/appearance/settings/themeName.

In Drupal 6 and beyond, theme authors can now customize this page by adding additional settings to the form. In Drupal 5, theme authors and theme users will have to install the Theme Settings API module (5.x-2.1 or later) before being able to use the method described below.

Adding form widgets for your custom theme settings

First, create a theme-settings.php file in your theme directory and add a themeName_settings() or themeEngineName_settings() function. The themeEngineName_settings() form is discouraged since it can lead to fatal errors when two functions with the same name are defined in your Drupal installation. The function should use the Forms API to create the additional form widgets.

For example: to add settings to the Garland theme, a garland_settings() or phptemplate_settings() function would be placed in the theme’s theme-settings.php file.

Directly modifying Garland or Minnelli is strongly discouraged, since they are used for the install and upgrade process.

If a user has previously saved the theme settings form, the saved values will be passed to this function in the $saved_settings parameter. The widgets to add to the form should be returned as a Forms API array.

The comments in the following example explain the details:

<?php // An example themes/garland/theme-settings.php file. /** * Implementation of THEMEHOOK_settings() function. * * @param $saved_settings * array An array of saved settings for this theme. * @return * array A form array. */ function garland_settings($saved_settings) { /* * The default values for the theme variables. Make sure $defaults exactly * matches the $defaults in the template.php file. */

Page 53: Drupal 7

53

$defaults = array( 'garland_happy' => 1, 'garland_shoes' => 0, ); // Merge the saved variables and their default values $settings = array_merge($defaults, $saved_settings); // Create the form widgets using Forms API $form['garland_happy'] = array( '#type' => 'checkbox', '#title' => t('Get happy'), '#default_value' => $settings['garland_happy'], ); $form['garland_shoes'] = array( '#type' => 'checkbox', '#title' => t('Use ruby red slippers'), '#default_value' => $settings['garland_shoes'], ); // Return the additional form widgets return $form; } ?>

Note that theme authors can create complex, dynamic forms using advanced Forms API (auto-completion, collapsible fieldsets) and the JavaScript library, jQuery.

Getting the settings’ values in your theme files

In order to retrieve the settings in the theme’s template.php or .tpl.php files, simply use theme_get_setting('varname'). See the Drupal API for details: http://api.drupal.org/api/6/function/theme_get_setting

For example:

<?php $happy = theme_get_setting('garland_happy'); ?> or in Drupal 7: <?php $vars['happy'] = theme_get_setting('garland_happy'); ?>

Initializing the default values

Since we can’t guarantee that a user will ever go to the admin/build/themes/settings/themeName page, we have to ensure that the default values for our custom settings get initialized.

The theme settings variables aren’t set until we submit the admin/build/themes/settings/themeName form for the first time, so in our template.php file we need to check whether the variables are set or not. If they aren’t set, we need to set them to the default values. We accomplish that by retrieving one of the variables and seeing if

Page 54: Drupal 7

54

it is null. If it is null, we save the defaults using variable_set() and then force the refresh of the settings in Drupal’s internals using theme_get_setting('', TRUE).

Add the following code near the top of your template.php file:

<?php /* * Initialize theme settings */ if (is_null(theme_get_setting('garland_happy'))) { // <-- change this line global $theme_key; /* * The default values for the theme variables. Make sure $defaults exactly * matches the $defaults in the theme-settings.php file. */ $defaults = array( // <-- change this array 'garland_happy' => 1, 'garland_shoes' => 0, ); // Get default theme settings. $settings = theme_get_settings($theme_key); // Don't save the toggle_node_info_ variables. if (module_exists('node')) { // NOTE: node_get_types() is renamed to node_type_get_types() in Drupal 7 foreach (node_type_get_types() as $type => $name) { unset($settings['toggle_node_info_' . $type]); } } // Save default theme settings. variable_set( str_replace('/', '_', 'theme_'. $theme_key .'_settings'), array_merge($defaults, $settings) ); // Force refresh of Drupal internals. theme_get_setting('', TRUE); } ?>

Note that the variable name “garland_happy” in the first line of the above code would be replaced with a variable name from your custom theme settings and the $defaults array would need to be copied from your theme-settings.php file.

Adding additional settings to a new version of your theme

After you have released a 1.0 version of your theme, you will eventually want to add some additional custom settings to the 2.0 version. The process is mostly straight-forward. But pay close attention to the initialization code in the third step:

1. In your theme-settings.php file, add the new settings to the $defaults and $form variables.

2. In your template.php file, add the settings to the $defaults variable in the Initialize theme settings code.

Page 55: Drupal 7

55

3. In your template.php file, update the initialization code to check for the existence of one of your new settings. For example, if you added several settings, including a garland_slippers setting, you would change the first line of the Initialize theme settings code to read: if (is_null(theme_get_setting('garland_slippers'))) {

This will ensure that the defaults for your newly-added custom settings get added to the saved values of the old custom settings.

Drupal 7

In Drupal 7, themes can more flexibly modify the entire theme settings form. In a theme’s theme-settings.php, themes should now use a THEMENAME_form_system_theme_settings_alter(&$form, $form_state) function. This gives the same power to themes that modules have if they use hook_form_system_theme_settings_alter(). See the “Forms API Quickstart Guide” and “Forms API Reference” on http://api.drupal.org/api/7, as well as the hook_form_FORM_ID_alter() docs to learn the full flexibility of Forms API. Note that themes can no longer use the phptemplate_ prefix to the function; you’ll need to use the actual name of your theme as the prefix.

Here’s an example if you had a “foo” theme and wanted to add a textfield whose default value was “blue bikeshed”:

<?php function foo_form_system_theme_settings_alter(&$form, $form_state) { $form['foo_example'] = array( '#type' => 'textfield', '#title' => t('Widget'), '#default_value' => theme_get_setting('foo_example'), '#description' => t("Place this text in the widget spot on your site."), ); } ?>

In order to set the default value for any form element you add, you’ll need to add a simple line to your .info file: settings[SETTING_NAME] = DEFAULT_VALUE. For our foo theme, you’d need to edit the foo.info file and add this line:

settings[foo_example] = blue bikeshed

In any of your theme’s PHP files, you can retrieve the value the user set by calling:

<?php $foo_example = theme_get_setting('foo_example'); ?>

Page 56: Drupal 7

56

Custom theme for custom form 6.x This page is about how to create custom layout for your custom form.

Assumptions:

Experience with PHP Experience with module writing Experience with theme writing

This document has following structure:

Module o mymodule.module: function mymodule_menu() o mymodule.module: function myformgene()

Theme o template.php: mytheme_theme() o template.php: mytheme_preprocess_myformname() o mytemplate.tpl.php

Some strings, functions, etc are prefixed by 'my' to clearly show what you have to define yourself.

During development remember to clear Drupal cache so that it would reread your hooks and theme.

Module

Assume you have created module mymodule. Here you create structure of your form and method of displaying it.

mymodule.module: mymodule_menu()

By default you create menu entry for the form, which further makes structure builder function callback.

function mymodule_menu() { return array( 'mypath' => array( 'title' => 'My title', 'page callback' => 'drupal_get_form', 'page arguments' => array('myformgene'), 'access callback' => true, 'type' => MENU_CALLBACK, ), ); }

This module lets you see the form at http://myhost/mypath

Page 57: Drupal 7

57

See also: hook_menu()

mymodule.module: myformgene()

Then you create function which actually defines the structure of your form. It will be called by drupal_get_form().

function myformgene( &$form_state, &$obj ) { return array( 'mysecret' => array( '#type' => 'hidden', ... ), 'myitem1' => array( ... ), 'myitem2' => array( ... ), '#theme' => 'myformname', ); }

This form will be themed by identifier myformname.

See also: drupal_get_form(), Forms API reference

Theme

template.php: mytheme_theme()

Announce that your theme handles theming of myformname object.

function mytheme_theme( $existing, $type, $theme, $path ) { return array( 'myformname' => array( 'arguments' => array( 'form' => NULL ), 'template' => 'mytemplate', ), ); }

By convention myformname and mytemplate are identical, except usage of _underscores_ in former and -dashes- in latter. For example: my_cool_form becomes my-cool-form.

See also: hook_theme()

template.php: mytheme_preprocess_myformname()

Next you have to do some preprocessing for that form. Specifically you specify which elements you render yourself.

function mytheme_preprocess_myformname( &$vars ) { foreach( $vars['form'] as $k => $v ) {

Page 58: Drupal 7

58

if( preg_match( '/^[a-z]/', $k ) ) { $vars[$k] = $vars['form'][$k]; unset( $vars['form'][$k] ); } } }

Usually you want to place visible elements yourself in your theme. If you need only some elements to render yourself, then you have to adjust this function and "manually" unset these.

See also: preprocessing

mytemplate.tpl.php

Lastly, finally, we reach to theming

<table> <tr> <td> <?php print drupal_render( $myitem1 ); ?> </td> <td> <?php print drupal_render( $myitem2 ); ?> </td> </tr> </table> <?php print drupal_render( $form ); // probably useless ?>

Remember:

If your template file is empty (zero length file), then your template will be ignored. If your template contains only print drupal_render( $form ); then all elements will be

rendered. If you do render some of the fields yourself, then this addition does nothing.

Note that hidden fields and form tags will be added around your form outside of your template, its automatic. Note that print drupal_render( $form ); DOES NOT print out form tags.

<form ...> <input type="hidden" ... /> ... your-rendered-template ... </form>

During development remember to clear Drupal cache so that it would reread your hooks and theme.

Page 59: Drupal 7

59

Theming Custom Entities There are three pieces, or more specifically functions, that are required to theme the output of a custom entity: hook_menu(), Page Callback, and the MODULE_theme() Hook.

hook_menu()

In order to access our entities, Menu paths must be created for tasks such as adding, editing, viewing and deleting entities. Adding, Editing and Deleting entities are related to Drupal's Forms API and beyond the scope of this tutorial. While these other menu definitions are required, we will focus solely on the menu definition for the 'view' task.

The menu hook allows us to define the path necessary to view a particular entity, the callback function that handles the request, the page title and the access requirements (among others).

See also: hook_menu()

In the code below, pay particular attention to the page callback key/value pair. This entry in the array tells Drupal that requests for http://mysite.com/my_entity/$id should be handled by the function entity_page_view. Drupal will simply hand the request off to this function along with the variables we specify.

<?php /** * Defines the menu callback for viewing our entity. Note that * 'page callback' will point to a function name in our module. * When a user requests the "entity/$id" path * (e.g., <a href="http://mysite.com/entity/1" title="http://mysite.com/entity/1" rel="nofollow">http://mysite.com/entity/1</a>), this callback will * be executed. */ function entity_menu() { $items['entity/%entity'] = array( 'title callback' => 'entity_page_title', 'title arguments' => array(1), 'page callback' => 'entity_page_view', 'page arguments' => array(1), 'access arguments' => array('view entitys'), 'type' => MENU_CALLBACK, ); return $items; } ?>

Page Callback

In the entity_menu() hook above, we indicated that requests to view an individual entity will be handled by a callback named entity_page_view. Now we need to define that callback. The code below is pretty simple and most of what you see is comments that explain what each section of the code does.

Page 60: Drupal 7

60

The code takes the entity identified by $id as the first argument and the view mode (full or teaser) as the second. Drupal will pass the view mode arguement and you can use its value to make decisions about what to display and how to display it but for now we will ignore it for the sake of simplicity. What we really want to understand is how to tell Drupal to use a custom template file to theme the output of the entity.

The first section of the code sets the $entity->content property to an empty array to clear out any previously rendered content. This property is where the what gets rendered in the template will be stored. In other words, this is the Render Array that Drupal will use to build the output of the entity.

In the middle section of the code, we use Drupal's hooks to add any custom fields to the entity. When you add fields to an entity through the Admin GUI, this is the code that retrieves the field definitions and their values and attaches them to the entity for display. During this step, the output for the fields is rendered and stored in the #markup property of the field. It is possible to create custom templates for the output of each field but an explanation of how to do so is beyond the scope of this article.

And finally, in the last section of the code, we tell Drupal which themplate file to use to render the output of the entity. By default, Drupal will use the BLOCK template to theme the output. We need to over-ride the default and point drupal to the name of our custom template. It is not necessary to indicate the full path or full name of the file. Drupal can figure that out on its own. We simply point to the theme in the Theme Registry we wish to use.

It is important to note that the order in which these tasks are performed appears to be important. The fields must be rendered and attached to the entity before the theme is specified.

<?php /** * This is the callback we defined to be executed when a user * requests <a href="http://mysite.com/entity/1" title="http://mysite.com/entity/1" rel="nofollow">http://mysite.com/entity/1</a> (1 is just an example ID, * it could be anything). This function will set up the data and * prepare the render array(s). You will specify the template to * use in this callback. The critical thing to note below is the * order in which field_attach_prepare_view, entity_prepare_view * and field_attach_view are called. These functions must be called * in this order and they must be called before you specify which * theme to use. */ function entity_page_view($entity, $view_mode='full') { /* * Remove previously built content, if exists */ $entity->content = array(); $title = filter_xss($entity->title); /* * Build the fields content */

Page 61: Drupal 7

61

field_attach_prepare_view('entity', array($entity->aid => $entity), $view_mode); entity_prepare_view('entity', array($entity->aid => $entity)); $entity->content += field_attach_view('entity', $entity, $view_mode); /* * Specify the theme to use and set the #element. Note that the key * you use to pass the entity object must match the key you set in the * variables in entity_theme(). So in the case below, we use the key * named #element because in entity_theme() we set the following code: * * array( * 'entity' => array( * 'variables' => array('element' => null), * 'template' => 'entity' * ), * ); */ $entity->content += array( '#theme' => 'entity', '#element' => $entity, '#view_mode' => 'full', '#language' => NULL, ); return $entity->content; } ?>

MODULE_theme() Hook

So far we have defined the menu item path and callback for our entity. The only two pieces remaining are to create an entry in the Theme Registry which points to our template, then to create the template file.

The function below implements the MODULE_theme() hook to create this module's Theme Registry entries. The array that is returned has as its keys, the name of the Theme Registry entry, which must match the value specified in:

<?php $entity->content += array( '#theme' => 'entity' ); ?> <?php /** * Adds our theme specificiations to the Theme Registry. */ function entity_theme($existing, $type, $theme, $path) { return array( 'entity' => array( 'variables' => array('element' => null), 'template' => 'entity' ), ); } ?>

Page 62: Drupal 7

62

So the #theme key in $entity->content points to the Theme Registry entry. The template key in the Theme Registry entry points to the name of the actual template file. The value of this key should be set to the {name}.tpl.php portion of the template file. It is simply the name of the file minus the .tpl.php extension.

After adding the code above, be sure to empty all caches and rebuild the Theme Registry. I use Devel to do this very easily from a sidebar link.

The Template File

Now all that remains is creating the template file. Just create a new file in /sites/all/themes/your_theme/{entity_type}.tpl.php. When someone requests http://yoursite.com/entity/$id, Drupal will use your template to render the output. The code below is some sample code from the entity theme but it can be whatever you want it to be.

<?php $content = $element->content; $aid = isset($element->aid) ? $aid = $element->aid : "" ; ?> <?php if (user_is_logged_in()) : ?> <p style="float: right;"><a href="?q={entity_name}/<?php echo $aid; ?>/edit">Edit</a></p> <?php endif; ?> <?php echo render($content['title']); ?> <p class="meta"> <?php echo render($content['field_date']); ?><br /> <?php echo render($content['field_author']); ?> </p> <?php echo render($content['field_image']); ?> <?php echo render($content['field_description']); ?>

Page 63: Drupal 7

63

Theming forms in your theme An example about how to theme Drupal Commerce Check Out form:

template.php <?php /** * Implements hook_theme(). */ function yourtheme_theme($existing, $type, $theme, $path) { $base = array( 'render element' => 'form', 'path' => drupal_get_path('theme', 'yourtheme') . '/templates/forms', ); return array( 'commerce_checkout_form_checkout' => $base + array( 'template' => 'commerce-checkout-form-checkout', ), ); } /** * Preprocessor for commerce_checkout_form_checkout theme. */ function yourtheme_preprocess_commerce_checkout_form_checkout(&$variables) { /* Add or modify your variables */ } ?>

[yourtheme]/templates/forms/commerce-checkout-form-checkout.tpl.php <?php // Render or hide parts of $form: var_export($form); // Example given: hide($form['title']; print render($form['first']); // Render remaining form elements as usual. print drupal_render_children($form); ?>

Don't forget clearing the theme registry cache.

Page 64: Drupal 7

64

Targeting different devices This section provides information on targeting different devices with your theme.

Adding browser-specific style sheets You can add a browser-specific style sheet.

Drupal 6

Below is a Drupal 6 theme example for including IE6< targeted CSS files. (Examples are taken from the Garland theme found in the Drupal core installation.)

page.tpl.php:

<?php ... <?php print $styles ?> <!--[if lt IE 7]> <?php print phptemplate_get_ie_styles(); ?> <![endif]--> ... ?>

template.php:

<?php ... function phptemplate_get_ie_styles() { global $language; $iecss = '<link type="text/css" rel="stylesheet" media="all" href="'. base_path() . path_to_theme() .'/fix-ie.css" />'; if ($language->direction == LANGUAGE_RTL) { $iecss .= '<style type="text/css" media="all">@import "'. base_path() . path_to_theme() .'/fix-ie-rtl.css";</style>'; } return $iecss; } ... ?>

Drupal 7

Drupal 7 adds the ability to specify a 'browsers' key when calling drupal_add_css().

template.php:

<?php ... function yourthemename_preprocess_html(&$vars) { ... drupal_add_css(path_to_theme() . '/fix-ie.css', array('weight' =>

Page 65: Drupal 7

65

CSS_THEME, 'browsers' => array('IE' => 'lt IE 7', '!IE' => FALSE), 'preprocess' => FALSE)); } ... ?>

See the API documentation for drupal_pre_render_conditional_comments() for details on the 'IE' and '!IE' keys.

It is recommended for themes to always use drupal_add_css() for adding a CSS file, so that Drupal code knows the exact total number of CSS files being added. This is information that might be needed for working around Internet Explorer's limitation of only being able to load the first 31 LINK/STYLE tags.

Page 66: Drupal 7

66

JavaScript and jQuery Using JavaScript adds dynamic presentation effects to a theme. In addition to custom Javascript files many Drupal developers find jQuery useful. jQuery is a lightweight JavaScript library which is built into Drupal. jQuery contains all the common DOM, event, effects, and Ajax functions.

Drupal 7 includes jQuery 1.4.4 and jQuery UI 1.8.7. Drupal 6.0 to 6.2 included jQuery 1.2.3 while Drupal 6.3 includes an update to jQuery 1.2.6. For use in module development which requires a later version of jQuery, apply the jQuery update module. When JavaScript is added to a page through Drupal, jQuery is automatically added to the page.

For more information jQuery visit the jQuery API and Documentation site.

Adding JavaScript

There are two ways for Themes to easily add JavaScript to a page.

In .info File

In the .info file for a theme scripts can be added using the script tag. For example, to add the script foo.js to every page on a Drupal site add the following to the themes .info file.

scripts[] = foo.js

Scripts added in a themes .info file are added at the theme level of ordering and will come after core/library JavaScript and module JavaScript. This ordering is important because it allows the theme JavaScript an opportunity to act on the page after the JavaScript providing the functionality within the page.

In Drupal 6 there is a default script file, named script.js that can be added to a theme without specifying it in the .info file. If that script is included in the theme it will be automatically added to all pages. In Drupal 7 all script files need to be specified.

In template.php

Alternately scripts can be added in the template.php file using drupal_add_js() or, in Drupal 7, drupal_add_library(). For example, adding a script in the root directory of a theme named foo.js would like like:

In Drupal 6:

<?php function example_preprocess_page(&$variables) { drupal_add_js(drupal_get_path('theme', 'example'). '/foo.js', 'theme'); // We need to rebuild the scripts variable with the new script included. $variables['scripts'] = drupal_get_js(); } ?>

Page 67: Drupal 7

67

In Drupal 7:

<?php function example_preprocess_html(&$variables) { $options = array( 'group' => JS_THEME, ); drupal_add_js(drupal_get_path('theme', 'example'). '/foo.js', $options); } ?>

Note, in Drupal 7 the $scripts variable does not need to be rebuilt. $scripts is built in template_process_html which happens after this function.

Drupal 7 includes library management. Libraries are collections of JavaScript, CSS, and dependent libraries. For example, Drupal 7 includes jQuery UI. jQuery UI is a component library with internal dependencies. When ui.autocomplete is included it needs ui.core and ui.position to be included as well. Drupal libraries takes care of this for us. Adding ui.autocomplete with all of it's CSS, JS, and dependencies can be accomplished with the following code:

<?php drupal_add_library('system', 'ui.autocomplete'); ?>

This one command includes jquery.ui.autocomplete.js, jquery.ui.autocomplete.css, and the dependencies of jquery.ui.position.js, jquery.ui.widget.js, jquery.ui.core.js, jquery.ui.core.css, and jquery.ui.theme.css. For more information on drupal_add_library see the API documentation.

JavaScript closures

It's best practice to wrap your code in a closure. A closure is nothing more than a function that helps limit the scope of variables so you don't accidentally overwrite global variables.

// Define a new function. (function () { // Variables defined in here will not affect the global scope. var window = "Whoops, at least I only broke my code."; console.log(window); // The extra set of parenthesis here says run the function we just defined. }()); // Our wacky code inside the closure doesn't affect everyone else. console.log(window);

A closure can have one other benefit, if we pass jQuery in as a parameter we can map it to the $ shortcut allowing us to use use $() without worrying if jQuery.noConflict() has been called.

// We define a function that takes one parameter named $. (function ($) { // Use jQuery with the shortcut: console.log($.browser); // Here we immediately call the function with jQuery as the parameter. }(jQuery));

Page 68: Drupal 7

68

In Drupal 7 jQuery.noConflict() is called to make it easier to use other JS libraries, so you'll either have to type out jQuery() or have the closure rename it for you.

JavaScript behaviors

Drupal uses a "behaviors" system to provide a single mechanism for attaching JavaScript functionality to elements on a page. The benefit of having a single place for the behaviors is that they can be applied consistently when the page is first loaded and then when new content is added during AHAH/AJAX requests. In Drupal 7 behaviors have two functions, one called when content is added to the page and the other called when it is removed.

Behaviors are registered by setting them as properties of Drupal.behaviors. Drupal will call each and pass in a DOM element as the first parameter (in Drupal 7 a settings object will be passed as the second parameter). For the sake of efficiency the behavior function should do two things:

Limit the scope of searches to the context element and its children. This is done by passing context parameter along to jQuery: jQuery('.foo', context);

Avoid processing the same element multiple times. In Drupal 6 assign a marker class to the element and use that class to restrict selectors: jQuery('.foo:not(.foo-processed)', context).addClass('foo-processed').css('color', 'red');

In Drupal 7 use the jQuery Once plugin that's bundled with core: jQuery('.foo', context).once('foo').css('color', 'red');

As a simple example lets look at how you'd go about finding all the https links on a page and adding some additional text marking them as secure, turning <a href="https://example.com">Example</a> into <a href="https://example.com">Example (Secure!)</a>. This should make the importance of only running the code once apparent, if our code ran twice the link would end up reading "Example (Secure!) (Secure!)".

In Drupal 6 it would be done like this:

// Using the closure to map jQuery to $. (function ($) { // Store our function as a property of Drupal.behaviors. Drupal.behaviors.myModuleSecureLink = function (context) { // Find all the secure links inside context that do not have our processed // class. $('a[href^="https://"]:not(.secureLink-processed)', context) // Add the class to any matched elements so we avoid them in the future. .addClass('secureLink-processed') // Then stick some text into the link denoting it as secure. .append(' (Secure!)'); }; // You could add additional behaviors here.

Page 69: Drupal 7

69

Drupal.behaviors.myModuleMagic = function(context) {}; }(jQuery));

In Drupal 7 it's a little different because behaviors can be attached when content is added to the page and detached when it is removed:

// Using the closure to map jQuery to $. (function ($) { // Store our function as a property of Drupal.behaviors. Drupal.behaviors.myModuleSecureLink = { attach: function (context, settings) { // Find all the secure links inside context that do not have our processed // class. $('a[href^="https://"]', context) // Only process elements once. .once('secureLink') // Then stick some text into the link denoting it as secure. .append(' (Secure!)'); } } // You could add additional behaviors here. Drupal.behaviors.myModuleMagic = { attach: function (context, settings) { }, detach: function (context, settings) { } }; }(jQuery));

JavaScript theming

Drupal provides a theming mechanism for JavaScript code in a similar manner to the way theming works within the rest of Drupal. This enables themes to customize the markup generated by JavaScript.

Modules provide theme functions for their markup. For example, the following code uses the theme function powered (This displays a "powered by Drupal" icon):

Drupal.theme.prototype.powered = function(color, height, width) { return '<img src="/misc/powered-'+ color +'-'+ height +'x'+ width +'.png" />"; }

When a module wants to insert the markup it would do so in a matter like:

$('.footer').append(Drupal.theme('powered', 'black', 135, 42));

This will place an image in any elements with the class footer with the following markup:

<img src="http://drupal.org/misc/powered-black-135x42.png" />

When a theme wants to provide a different markup it can do so by providing an alternate theme function. Following our example the following function provides an a theme function for the theme.

Page 70: Drupal 7

70

Drupal.theme.powered = function(color, height, width) { return '<div class="powered-'+ color +'-'+ height +'x'+ width '"></div>'; }

While the modules theme function is at Drupal.theme.prototype.powered the themes is at Drupal.theme.powered. Including this function in the themes JavaScript will will cause the markup generated by the snippet:

$('.footer').append(Drupal.theme('powered', 'black', 135, 42));

to be

<div class="powered-black-135x42"></div>

JavaScript theme functions are entirely free in their return value. They can vary from simple strings to complex data types like objects, arrays, and jQuery elements. Refer to the original (default) theme function to see what your custom theme function should return.

For more information on using JavaScript and jQuery in Drupal, see the JavaScript section of the developer guide. Also, join the Javascript group on Groups.Drupal.org to get advice on Javascript and jQuery: http://groups.drupal.org/javascript

Page 71: Drupal 7

71

Using Newer Versions of jQuery with Drupal 6 There are a lot of forum posts and issue queues revolving around the need to use a newer version of jQuery on a Drupal 6.x site. There are several ways to achieve this, such as using the jQuery Update module, but I'm going to focus on the recommended method of running 2 versions of jQuery side-by-side - the jQuery noConflict() function.

The Problem Your custom theme, dropdown menu, slideshow, or other jQuery dependant scripts require jQuery 1.4, jQuery UI 1.8, or some other jQuery version or component. Drupal 6 ships with jQuery 1.2.6. You could just replace the jquery.js file in example.com/misc with a newer version, but you'd run into some issues, such as:

Useful UI features like draggable tables and ajax search will stop working Contributed modules that use jQuery assume you are using 1.2.6 After updating Drupal to a newer version, your custom jquery.js file would be

overwritten with 1.2.6

The Solution jQuery already has the functionality to run along side a different version of jQuery (or, really, along side any other JavaScript library that uses the $ symbol as a function or variable). This is the noConflict() function. You can view the API page here: http://api.jquery.com/jQuery.noConflict/

To use this functionality within your project, simply tell the browser about your new jQuery library and that you'll be referencing it via your custom noConflict identifier:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script> <script type="text/javascript"> var $jq = jQuery.noConflict(); </script>

Example: my-theme/page.tpl.php <head> <title><?php print $head_title; ?></title> <?php print $head; ?> <?php print $styles; ?> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script> <script type="text/javascript"> var $jq = jQuery.noConflict(); </script> <?php print $scripts; ?> </head>

With this method jQuery 1.2.6 is loading with every page, and can be used by Views, Draggable.js, any contributed Ajax calls, etc. Also, jQuery 1.4.4 (as in this example) is also

Page 72: Drupal 7

72

loading and the 2 are not interfering with each other. Another point worth adding is that 1.4.4 is being loaded from the Google CDN - one less file for your audience to download from your server.

Using noConflict in your scripts

To execute scripts with the new version of jQuery you simply need to use $jq instead of $ in your scripts, for example:

$jq(document).ready(function(){ $jq('#my-div').function(); });

Using noConflict in contributed scripts

You can easily modify an external script to use your custom noConflict identifier. For example, the Qtip library depends on jQuery 1.5. You could include it via the above methods and then alter jquery.qtip.js, replacing the first line

(function($, window, undefined) {

with

(function($jq, window, undefined) {

Page 73: Drupal 7

73

Core templates and suggestions Core comes with a number of default template files. In order to override these templates, all you need to do is copy them into your theme folder and clear the theme registry.

You can also override these templates in a more targeted way. This section lists the default templates in Drupal core and explains how to create more targeted template files.

In Drupal 6, some template files could be overridden in a targeted way. For example, the theme could contain a "node-article.tpl.php" file which would be used for nodes of the article type only.

In Drupal 7, these need to be renamed to use a double-hyphen. For example, "node--article.tpl.php". A single hyphen is still used to separate words: for example, "user-picture.tpl.php" or "node--long-content-type-name.tpl.php", so the double hyphen always indicates a more targeted override of what comes before the "--".

See Converting 6.x themes to 7.x for more info.

Page 74: Drupal 7

74

Core templates Default templates:

These are the default template (.tpl.php) files provided by core in Drupal 7. Documentation on the variables and purpose of these templates are located inside the templates. There is a default set of variables available to all templates.

In order to override these templates, all you need to do is copy them into your theme folder and clear the theme registry.

To override templates in a more targeted way, see Drupal 7 Template Suggestions or Drupal 6 Template Suggestions.

Aggregator "modules/aggregator/..."

aggregator-feed-source.tpl.php aggregator-item.tpl.php aggregator-summary-item.tpl.php aggregator-summary-items.tpl.php aggregator-wrapper.tpl.php

Block "modules/block/..."

block-admin-display-form.tpl.php block.tpl.php

Note: the Drupal 6 version of block.tpl.php used to be part of "modules/system/...".

Book "modules/book/..."

book-all-books-block.tpl.php book-export-html.tpl.php book-navigation.tpl.php book-node-export-html.tpl.php

Comment "modules/comment/..."

comment-wrapper.tpl.php comment.tpl.php

Note: comment-folded.tpl.php has been deprecated in Drupal 7.

Field "modules/field/theme/..."

Page 75: Drupal 7

75

field.tpl.php

Note: field.tpl.php is a new template in Drupal 7.

Forum "modules/forum/..."

forum-icon.tpl.php forum-list.tpl.php forum-submitted.tpl.php forum-topic-list.tpl.php forums.tpl.php

Note: forum-topic-navigation.tpl.php has been deprecated in Drupal 7.

Node "modules/node/..."

node.tpl.php

Overlay "modules/overlay/..."

overlay.tpl.php

Note: overlay.tpl.php is a new template in Drupal 7.

Poll "modules/poll/..."

poll-bar--block.tpl.php poll-bar.tpl.php poll-results--block.tpl.php poll-results.tpl.php poll-vote.tpl.php

Note: Former templates poll-results-block.tpl.php and poll-bar-block.tpl.php have been renamed with a double dash in Drupal 7.

Profile "modules/profile/..."

profile-block.tpl.php profile-listing.tpl.php profile-wrapper.tpl.php

Search "modules/search/..."

search-block-form.tpl.php

Page 76: Drupal 7

76

search-result.tpl.php search-results.tpl.php

Note: search-theme-form.tpl.php has been deprecated in Drupal 7.

System "modules/system/..."

page.tpl.php maintenance-page.tpl.php region.tpl.php html.tpl.php

Note: box.tpl.php has been deprecated in Drupal 7. html.tpl.php and region.tpl.php have been added.

Taxonomy "modules/taxonomy/..."

taxonomy-term.tpl.php

Note: taxonomy-term.tpl.php is a new template in Drupal 7.

Toolbar "modules/toolbar/..."

toolbar.tpl.php

Note: toolbar.tpl.php is a new template in Drupal 7.

User "modules/user/..."

user-picture.tpl.php user-profile-category.tpl.php user-profile-item.tpl.php user-profile.tpl.php

Page 77: Drupal 7

77

Drupal 7 Template Suggestions Template suggestions:

What's a template suggestion? It's an alternate template (.tpl.php) file that you have created to override the base or original template file. Suggestions only work when they are placed in the same directory as the base templates.

Custom Template Suggestions

Custom suggestions beyond the ones listed below can be created. See the page Working with template suggestions.

Default Template Suggestions in Core block--[region|[module|--delta]].tpl.php base template: block.tpl.php

Template suggestions are made based on these factors, listed from the most specific template to the least. Drupal will use the most specific template it finds:

1. block--module--delta.tpl.php 2. block--module.tpl.php 3. block--region.tpl.php

"module" being the name of the module and "delta", the internal id assigned to the block by the module. For example, "block--block--1.tpl.php" would be used for the first user-submitted block added from the block administration screen since it was created by the block module with the id of 1. "region" will take effect for specific regions. An example of a region-specific template would be block--sidebar_first.tpl.php. If you had a block created by a custom module called "custom" and a delta of "my-block", the template suggestion would be called "block--custom--my-block.tpl.php."

comment--[node-type].tpl.php base template: comment.tpl.php

Support was added to create comment--type.tpl.php files, to format comments of a certain node type differently than other comments in the site. For example, a comment made on an article-type node would be "comment--node-article.tpl.php".

comment-wrapper--[node-type].tpl.php base template: comment-wrapper.tpl.php

Similar to the above but for the wrapper template.

field--[type|name[--content-type]|content-type].tpl.php base template: field.tpl.php

Page 78: Drupal 7

78

Template suggestions are made based on these factors, listed from the most specific template to the least. Drupal will use the most specific template it finds:

1. field--field-name--content-type.tpl.php 2. field--content-type.tpl.php 3. field--field-name.tpl.php 4. field--field-type.tpl.php

Remember to include "field-" in custom field names, e.g: field--field-phone.tpl.php

forums--[[container|topic]--forumID].tpl.php base template: forums.tpl.php

Template suggestions are made based on these factors, listed from the most specific template to the least. Drupal will use the most specific template it finds:

For forum containers:

1. forums--containers--forumID.tpl.php 2. forums--forumID.tpl.php 3. forums--containers.tpl.php

For forum topics:

1. forums--topics--forumID.tpl.php 2. forums--forumID.tpl.php 3. forums--topics.tpl.php

maintenance-page--[offline].tpl.php base template: maintenance-page.tpl.php

This applies when the database fails. Useful for presenting a friendlier page without error messages. Theming the maintenance page must be properly setup first.

node--[type|nodeid].tpl.php base template: node.tpl.php

Template suggestions are made based on these factors, listed from the most specific template to the least. Drupal will use the most specific template it finds:

1. node--nodeid.tpl.php 2. node--type.tpl.php 3. node.tpl.php

See node.tpl.php in the Drupal API documentation for more information.

page--[front|internal/path].tpl.php base template: page.tpl.php

Page 79: Drupal 7

79

The suggestions are numerous. The one that takes precedence is for the front page. The rest are based on the internal path of the current page. Do not confuse the internal path to path aliases which are not accounted for. Keep in mind that the commonly used Path auto module works its magic through path aliases.

The front page can be set at "Administration > Configuration > System > Site information." In Drupal 6, at "Administrator > Site configuration > Site information." Anything set there will trigger the suggestion of "page--front.tpl.php" for it.

The list of suggested template files is in order of specificity based on internal paths. One suggestion is made for every element of the current path, though numeric elements are not carried to subsequent suggestions. For example, "http://www.example.com/node/1/edit" would result in the following suggestions:

1. page--node--edit.tpl.php 2. page--node--1.tpl.php 3. page--node.tpl.php 4. page.tpl.php

Also see page.tpl.php in the Drupal API documentation for more information.

How Drupal determines page template suggestions based on path

Here is another explanation based on the theme_get_suggestions() function:

The list of possible templates for a given page is generated by Drupal through the theme_get_suggestions() function, which is called by the template_preprocess_page() function.

The Drupal path of the page is first broken up into its components. As mentioned above, the Drupal path is not any of its aliases: there is one and only one Drupal path for a page. For the examples "http://www.example.com/node/1/edit" and "http://www.example.com/mysitename?q=node/1/edit", the Drupal path is node/1/edit, and its components are "node", 1, and "edit".

Next, the prefix is set to "page". Then, for every component, the following logic is followed:

1. If the component is a number, add the prefix plus "__%" to the list of suggestions.

2. Regardless of whether the component is a number or not, add the prefix plus "__" plus the component to the list of suggestions.

3. If the component is not a number, append "__" plus the component to the prefix.

After the list of components is iterated through, if the page is the front page (as set through "Administration > Configuration > System > Site information."), then "page__front" is added to the list of suggestions.

Page 80: Drupal 7

80

Note that eventually, to turn a suggestion into an actual file name, "__" gets turned into "--", and ".tpl.php" gets appended to the suggestion. Thus, for node/1/edit, we get the following list of suggestions:

1. page.tpl.php (this is always a suggestion) 2. page--node.tpl.php (and prefix is set to page__node) 3. page--node--%.tpl.php 4. page--node--1.tpl.php (prefix is not changed because the component is a

number) 5. page--node--edit.tpl.php (and prefix is set to page__node__edit) 6. page--front.tpl.php (but only if node/1/edit is the front page)

When the page is actually rendered, the last suggestion is checked. If it exists, that suggestion is used. Otherwise the next suggestion up is checked, and so on. Of course, if none of the overriding suggestions exist, page.tpl.php is the final suggestion. This also explains why page--front.tpl.php, if it exists, overrides any other suggestion for the front page: it is always the last suggestion for the page designated as the front page.

poll-results--[block].tpl.php base template: poll-results.tpl.php

The theme function that generates poll results are shared for nodes and blocks. The default is to use it for nodes but a suggestion is made for rendering them inside block regions. This suggestion is used by default and the template file is located at "modules/poll/poll-results--block.tpl.php".

poll-vote--[block].tpl.php base template: poll-vote.tpl.php

Similar to poll-results--[block].tpl.php but for generating the voting form. You must provide your own template for it to take effect.

poll-bar--[block].tpl.php base template: poll-bar.tpl.php

Same as poll-vote--[block].tpl.php but for generating individual bars.

profile-wrapper--[field].tpl.php base template: profile-wrapper.tpl.php

The profile wrapper template is used when browsing the member listings page. When browsing specific fields, a suggestion is made with the field name. For example, http://drupal.org/profile/country/Belgium would suggest "profile-wrapper--country.tpl.php".

region--[region].tpl.php base template: region.tpl.php

Page 81: Drupal 7

81

The region template is used when a page region has content, either from the Block system or a function like hook_page_build(). Possible region names are determined by the theme's .info file.

search-results--[searchType].tpl.php base template: search-results.tpl.php

search-results.tpl.php is the default wrapper for search results. Depending on type of search different suggestions are made. For example, "example.com/search/node/Search+Term" would result in "search-results--node.tpl.php" being used. Compare that with "example.com/search/user/bob" resulting in "search-results--user.tpl.php". Modules can extend search types adding more suggestions of their type.

search-result--[searchType].tpl.php base template: search-result.tpl.php

The same as above but for individual search results.

Cache issue

When working with template suggestion, there is a possibility that Drupal use its cache rather than the new templates as suggested. Remove the cache if you experience this problem. To clear the cache, choose one of the methods described in Clearing Drupal's cache.

Page 82: Drupal 7

82

Drupal 6 Template Suggestions Template suggestions:

What's a template suggestion? It's an alternate template (.tpl.php) file that you have created to override the base or original template file. Suggestions only work when they are placed in the same directory as the base templates. In other words, if you want comment-blog.tpl.php to display in your browser, comment.tpl.php also needs to exist inside your theme and the two files must be in the same directory.

Custom suggestions beyond the ones listed below can be created. See the page Working with template suggestions.

Note: There is a bug which prevents derivative template files from being detected if the base template file is not also present. See #279573: Themes can't use node-story.tpl.php without node.tpl.php for more information.

block-[region|[module|-delta]].tpl.php base template: block.tpl.php

Suggestions made based on these factors in this order:

1. block-module-delta.tpl.php 2. block-module.tpl.php 3. block-region.tpl.php

"module" being the name of the module and "delta", the internal id assigned to the block by the module. For example, "block-user-1.tpl.php" would be used for the default user navigation block since it was created by the user module with the id of 1. "region" will take effect for specific regions.

comment-[type].tpl.php base template: comment.tpl.php

Support was added to create comment-type.tpl.php files, to format comments of a certain node type differently than other comments in the site. Similar to node-[type].tpl.php but for comments.

comment-wrapper-[type].tpl.php base template: comment-wrapper.tpl.php

Similar to the above but for the wrapper template.

forums-[[container|topic]-forumID].tpl.php base template: forums.tpl.php

Template suggestions added based on these factors, in this order.

For forum containers:

Page 83: Drupal 7

83

1. forums-containers-forumID.tpl.php 2. forums-forumID.tpl.php 3. forums-containers.tpl.php

For forum topics:

1. forums-topics-forumID.tpl.php 2. forums-forumID.tpl.php 3. forums-topics.tpl.php

maintenance-page-[offline].tpl.php base template: maintenance-page.tpl.php

This applies when the database fails. Useful for presenting a friendlier page without error messages. Theming the maintenance page must be properly setup first.

node-[type].tpl.php base template: node.tpl.php

In Drupal 7, templates for specific content types are created slightly different than in Drupal 6.

Drupal 6: node-mytype.tpl.php Drupal 7: node--mytype.tpl.php

See node.tpl.php in the Drupal API documentation for more information.

Note that in order to override the template for a specific node type, the theme must also implement the base node.tpl.php file. If this file is omitted, the theme will not detect the presence of node-[type].tpl.php files.

page-[front|internal/path].tpl.php base template: page.tpl.php

The suggestions are numerous. The one that takes precedence is for the front page. The rest are based on the internal path of the current page. Do not confuse the internal path to path aliases which are not accounted for. Keep in mind that the commonly used Path auto module works its magic through path aliases.

The front page can be set at "Administration > Configuration > System > Site information." In Drupal 6, at "Administrator > Site configuration > Site information." Anything set there will trigger the suggestion of "page-front.tpl.php" for it.

The list of suggested template files is in order of specificity based on internal paths. One suggestion is made for every element of the current path, though numeric elements are not carried to subsequent suggestions. For example, "http://www.example.com/node/1/edit" would result in the following suggestions:

1. page-node-edit.tpl.php 2. page-node-1.tpl.php

Page 84: Drupal 7

84

3. page-node.tpl.php 4. page.tpl.php

poll-results-[block].tpl.php base template: poll-results.tpl.php

The theme function that generates poll results are shared for nodes and blocks. The default is to use it for nodes but a suggestion is made for rendering them inside block regions. This suggestion is used by default and the template file is located at "modules/poll/poll-results-block.tpl.php".

poll-vote-[block].tpl.php base template: poll-vote.tpl.php

Similar to poll-results-[block].tpl.php but for generating the voting form. You must provide your own template for it to take effect.

poll-bar-[block].tpl.php base template: poll-bar.tpl.php

Same as poll-vote-[block].tpl.php but for generating individual bars.

profile-wrapper-[field].tpl.php base template: profile-wrapper.tpl.php

The profile wrapper template is used when browsing the member listings page. When browsing specific fields, a suggestion is made with the field name. For example, http://drupal.org/profile/country/Belgium would "suggest profile-wrapper-country.tpl.php".

search-results-[searchType].tpl.php base template: search-results.tpl.php

search-results.tpl.php is the default wrapper for search results. Depending on type of search different suggestions are made. For example, "example.com/search/node/Search+Term" would result in "search-results-node.tpl.php" being used. Compare that with "example.com/search/user/bob" resulting in "search-results-user.tpl.php". Modules can extend search types adding more suggestions of their type.

search-result-[searchType].tpl.php base template: search-result.tpl.php

The same as above but for individual search results.

Page 85: Drupal 7

85

Overriding themable output Depending on how your site is configured, the HTML code that makes up each page in your site is compiled from the output of various Drupal modules.

About modifying HTML markup

If the default HTML markup supplied by any module does not suit the requirements of your theme, you can override some or all of it, so that the resulting page is exactly what you need for your design.

For example, you might decide that for your site, the default search box should have an image of a magnifying glass and that the label on the search button should read "Discover great stuff!" rather than the default label of "Search". You can completely override the default markup, so that the markup calls for your image file and that the button is labeled the way you want.

Don't hack core

It's very important to understand the concept of "overriding". While it is technically possible to just find the module responsible for the search field and edit the code directly, this is definitely NOT recommended. In the short term, it will seem to solve your problem, but it will soon make it difficult to keep your site up to date. Whenever you update the module, perhaps for a general security release, you would need to remember to redo any of your customizations.

How to change HTML the Drupal way

The "Drupal Way" is to do an override. This involves four basic steps:

1. Locate the module responsible for the markup 2. Do one of the following:

If the module provides a template (tpl.php file), copy the template to your theme directory. See Core Templates and Suggestions for a list of core templates. OR...

In the module code, identify the theme or preprocess function that is generating the markup you want to change and copy the function to your theme's template.php file. You will need to change the "theme_" or "template_" prefix to match the name of your theme. For example, "theme_breadcrumb" would become "mythemename_breadcrumb"; "template_preprocess_page" would become "mythemename_preprocess_page".

Within the copied function or template, change the HTML code to suit your needs. Refresh the theme cache.

These steps are explained in greater detail in the following pages.

Page 86: Drupal 7

86

Using PHP for theming

If you are not already familiar with PHP, the process might seem somewhat intimidating, but it generally does not require that you understand anything about actually writing PHP. As long as you know what HTML you want to be displayed, it's simply a matter of modifying that part of the code. As with any theming work, you should not make changes to a live or production site until you have thoroughly tested the changes on a development site.

Overriding CSS

Many modules also provide style sheets (.css files) which specify the default look and feel of the markup. These style sheets can also be overridden. For more information, see Overriding style sheets from modules and base themes.

Popular Contributed Modules that override the default HTML output

Some contributed modules have their own theming guides. Also see overriding style sheets from modules and base themes.

Modules for which theming-specific documentation exists are listed below by Drupal version.

Drupal 7–compatible modules

Panels Date and Calendar Views 3 Slideshow Print Menu Block

Drupal 6-compatible modules

CCK Views 2 Views 2 Slideshow Panels Flag Print Nice Menus Menu Block Simplenews Browser Theme Settings

Beginners guide to overriding themable output Introduction to PHP for theming About overriding themable output Setting up variables for use in a template (preprocess and process functions) Default baseline variables Customizing and Overriding User Login page, Register, and Password Reset in Drupal

6

Page 87: Drupal 7

87

Example: Themable output Identifying Core Components Menu theming The theme registry for special cases Working with template suggestions Architectural view of theming

Page 88: Drupal 7

88

Beginners guide to overriding themable output Drupal theming is based on the concept of overriding the output from Drupal core and modules. Instead of having to modify the module to change the output (which you don't want to do, because it will make upgrades difficult later), you can modify it by adding functions or files to your theme. The functions or files will take precedence over the module's choice for how to produce the HTML.

Overriding is done one of two ways: overriding theme functions, or overriding theme files, depending on how the output from Drupal is being created.

Avoiding having to override at all

Before you decide that you need to modify the output coming from Drupal or one of the modules on your site, check the following possibilities -- you may be able to avoid the whole process:

Can you just change a setting? For instance, you can click on a "Configure" link for any block, and it will allow you to change the text of the block title, or even remove the title completely (you can also move any block to a different region of the page on the blocks configuration screen). Other modules will let you change aspects of their output through settings too (labels for text fields, order of output, etc.). To find settings pages, click on Administer, and then By module, and check out all the settings pages offered by your module. Some settings are also on your theme's configuration page, and some are in various tabs of your content types' settings pages (under Content management >> Content types).

Can you accomplish your goal through CSS? Nearly all output in Drupal is enclosed in DIV elements with specific classes or IDs, so you can easily customize a particular piece of output. CSS will let you change fonts, sizes, placement, background images, etc. To override the CSS provided by Drupal or a module, just add to one of your theme's CSS files.

If neither of these options lets you change what you want to customize, then you do actually need to customize the HTML output of Drupal or one of your modules -- read on.

Overriding theme functions

A theme function is a PHP function in Drupal whose name begins with 'theme_', such as 'theme_username'. To override a theme function:

1. Open your theme's template.php file in a plain text editor. 2. Copy the theme or template function you found into your template.php file. (One way

of finding the function is searching for the function name at http://api.drupal.org. There is also a Function reference in the menu provided by the Devel module.)

3. Rename the function you copied in (see note below). If your theme's base prefix is "wonderful", and the theme function you are trying to override is called "theme_xyz", then you need to rename the function "wonderful_xyz". If you are overriding

Page 89: Drupal 7

89

"template_preprocess_xyz", then the new name should be "wonderful_preprocess_xyz".

To change a function name, e.g. from "theme_xyz" to "wonderful_xyz", find a line that looks something like this:

function theme_xyz( $a, $b, $c) {

and change it to:

function wonderful_xyz( $a, $b, $c) {

Make sure you leave the part inside the parentheses unchanged!

4. Modify the function so that it does what you want it to do. 5. Upload the template.php file to your web site, in your theme directory. 6. Refresh the theme cache (see http://drupal.org/node/173880#theme-registry).

Overriding a theme file

If the HTML output you want to override is in a theme file, here are the steps to follow to override it:

1. Copy the theme file to your theme directory, if your theme directory does not already have a file by that name.

2. If you are implementing a suggestion, rename the file to the suggestion name it should have. Note that some suggestion files will not be recognized unless their base file is also present -- for instance, if you want to make a node content type suggestion file for your content type, you need to have node.tpl.php present in your theme directory order for your node-my_content_type.tpl.php file to be recognized. However, suggestions for Views do not require that the base Views template file be present in your template directory.

3. Modify the file so it does what you want it to do. 4. Upload the file to your web site, including the new base theme file if necessary, into

your theme's directory. 5. Refresh the theme cache (see http://drupal.org/node/173880#theme-registry)

Finding where the themable output is coming from

The first step in overriding Drupal's HTML output is to figure out where the HTML output you want to modify is coming from. What you are looking for is either a theme function (a PHP function called "theme_xyz", such as "theme_search" or "theme_aggregator_block_item"), a template preprocess function (a PHP function called "template_preprocess_xyz"), or a theme file (a PHP file whose name ends in .tpl.php, such as "views-more.tpl.php").

To find the right file or function, start by finding the module directory for the module that is producing the output (under "modules", "sites/all/modules", or "sites/your_sub_dir/modules").

Page 90: Drupal 7

90

Theme files will generally be in either the top-level module directory, a sub-module directory underneath, or a directory called "theme" underneath. Theme and template preprocess functions will generally be in either a module file (ending in .module) or an include file (usually ending in .inc); the file will be located in the module directory or a sub-directory. A good way to find the right function or file is to search for a specific CSS ID or class on the HTML element you are trying to modify, or some other unique text that the module is producing.

Another method of finding the appropriate override function is using the Theme developer module, which is an add-on to the popular Devel project. When enabled, Theme Developer allows the site builder to hover over the elements of the page and click the ones where more theme information is required.

Some modules also will honor theme file suggestions. For instance, in Views 2, you can get theme information when you are editing a View -- it tells you which theme files will be used to produce the view, and the first one in each list is a theme file that is located in the "themes" sub-directory of your views module; this is the theme file you will want to override. The other names in the list are possibilities for what you will want to name the file when you put it into your theme, depending on how specific you want your override to be (e.g., do you want to override how all views are displayed, or just your specific view?).

You can also use suggestions to override core Drupal module theming. For instance, if you want to override how a particular node content type is displayed, you can use the node.tpl.php file from your theme (or Drupal core) as the overrideable file, and rename the copy node-content_type_name.tpl.php. There is more information about the core module files and suggestion file names you can use on the Core templates and suggestions page.

Once you have found the right function or file to override, follow the directions in one of the next two sections to override it.

However, if you find what is producing the HTML output you want to override, and it is not inside a theme function, template preprocess function, or theme file, then you will probably not be able to modify the output with theme functions alone. You may need to check the support forums or contact the module developer to find out how to modify the output.

Page 91: Drupal 7

91

Introduction to PHP for theming Teaching PHP is beyond the scope of the Drupal documentation, but this page will introduce you to some of the most basic techniques which are important to working with themes.

Discovering your data

Use the Theme Developer module

The easiest way to see the variables that a template file has to work with is to use the Theme Developer module. Not only will this give you access to a live, interactive display of the variables that were used to create any part of a page, but it also includes numerous functions for outputting debugging data while you are working.

Do it the hard way

If for some reason you can't (or won't) use the Theme Developer module, it is possible to use a PHP function to see all of the variables that have been passed to your template file. To do so, you can add the following code to the top of any template (.tpl.php) file in your theme.

<?php $vars = get_defined_vars(); print_r($vars); ?>

Both of the techniques described above use a lot of resources and can expose internal information to the public. Therefore, you should never use either technique on a production site.

Using the information you get

Once you have used one of the techniques above, you will be able to see numerous variables and arrays(). If you wish, you can refer directly to any of these in your template.

For example, to display the title, you would add the following code to your .tpl.php file:

<?php print $title; ?>

To display the node title along with a link to the node and some heading formatting, add the following code:

<h2 class="title"> <a href="<?php print $node_url; ?>" title="<?php print $title; ?>"><?php print $title; ?></a> </h2>

Arrays

The output from the print_r technique shown above will probably contain a number of arrays. For example, if you are using a taxonomy you might see something like this:

Page 92: Drupal 7

92

[taxonomy] => Array

An array allows a group of related data to be stored in an organized way. If you only want one item from an array to be displayed you can specify that item using its related "key".

For example, suppose the print_r technique showed you the following array:

[location] => Array ( [lid] => 3 [name] => My Place [street] => 235 King Edward Avenue [additional] => [city] => Ottawa [province] => ON [postal_code] => K1N 7L8 [country] => ca [latitude] => 45.431993 [longitude] => -75.688390 [source] => 3 [is_primary] => 0 [province_name] => Ontario [country_name] => Canada )

If you just wanted to display the city, you could add the following code to your .tpl.php file:

<?php print $location['city']; ?>

There are many other ways to manipulate your content using PHP. For more information, consult one of the PHP references on the web.

Page 93: Drupal 7

93

About overriding themable output The following only applies when the default markup needs changes. This section can be skipped if the presentation is handled only through style sheets.

There are three aspects to overriding the themed output. The first is knowing where the source originates, the second is providing the override, and third is understanding its type.

Note that Drupal maintains cached theming data through the theme registry. It must be cleared when setting up overrides.

1. Finding the source:

Finding the source can be difficult to track down due to the hierarchy of theming calls whose source can be spread throughout the whole system.

Links to 520KB PDF. Provided for illustration only.

Most of the page elements are typically pulled with theme('page') and placed inside the page.tpl.php template after rendering navigation bits, the bits within the navigation bits, block regions, the blocks within the block regions, etc. Each chunk of themed data is often referred to as a theming "hook".

Page 94: Drupal 7

94

Note: Theming functions and templates will now be referred to as theming hooks. There are many hooks unrelated to theming throughout the system. Any mention of it here only relates to theming.

Getting to the source of any specific chunk of markup can now be tracked with the theme developer module. It includes a theming tool to easily visualize the source of any output, its type and tons of other theming data. See the screencast for a demonstration. This is not available for Drupal versions less than 6 due to technical limitations.

2. System of overrides:

The system of overrides has a specific cascading order with few exceptions. Drupal core and modules provide a reasonable default for its markup handled by the theming hook. If it does not suit the requirements of the theme, then an alternate can be provided preventing the defaults from being used. This way, the defaults are left alone and all the theme specific changes are localized to the theme. You should never change code outside your theme to alter default output. (Here are some reasons why you should not "hack core.")

Page 95: Drupal 7

95

If you need more control beyond this convention of overrides, the theme registry can be manipulated for more control.

Note: Although it is still possible, PHPTemplate.engine in Drupal 6 no longer overrides theme functions. In 5, that is what allowed templates to be used for a handful of the theming hooks. It is no longer necessary.

3. Functions vs. templates:

There are two ways of implementing a theming hook, either a function or a template. The type that is used should depend on what you're trying to achieve. Drupal core and modules can use either type to construct the output, and themes can use the same type or change it.

Links to PDF. Flow map for 5 also available for comparison.

Functions are marginally faster than templates, but they can be difficult for designers who may be only familiar with XHTML. The speed depends on the nature of the hook multiplied by the number of times it is called on a page. For example, using templates for the theming hook "links" could cause a noticeable performance hit for complex and busy sites due to its repeated use.

Here are two examples on overriding with the help of devel themer.

Overriding functions:

Page 96: Drupal 7

96

The theme function theme_menu_local_tasks is a simple function for outputting both primary and secondary tabs. The theming hook in this case is "menu_local_tasks". To override, the "theme" prefix in the function name needs to be changed to the name of your theme. (In Drupal 6 you could also use the name of the theme engine the theme is running under, but this was not a recommended practice. That option has been removed in Drupal 7.)

The example is from Drupal 6 and shows Garland is using the name of the theme engine for the override. Again, in Drupal 6 it si recommended to use the theme name and in Drupal 7 it is mandatory to use the theme name.

Placing the following inside the theme's template.php file will override the default after clearing the theme registry. Change "mytheme" in the function name to the name of your theme.

<?php function mytheme_menu_local_tasks() { $output = ''; if ($primary = menu_primary_local_tasks()) { $output .= "<ol class=\"tabs primary\">\n". $primary ."</ol>\n"; } if ($secondary = menu_secondary_local_tasks()) { $output .= "<ol class=\"tabs secondary\">\n". $secondary ."</ol>\n"; } return $output; } ?>

The only change here is the markup from an unordered list to an ordered list.

A listing of all the theme functions can be found on api.drupal.org.

Overriding templates:

If the default implementation is done as a template then simply copying the source template file into the theme will automatically override it after clearing the theme registry. Here is an example for search-theme-form.tpl.php. Note that the theming hook in this case is "search_theme_form" with the template using hyphens instead of underscores.

Page 97: Drupal 7

97

That is all you need to do. Open the copied template in your editor to make your alterations. All the core .tpl.php files are documented. It should give a good indication on what can be done with the output.

Note: templates can be placed in any directory within the theme. This allows for better management and less clutter in the base level of the theme directory.

Related pages:

To customize the variables inside templates, see the sub-page on Preprocess functions.

A listing of all the theme templates can be found on the sub-page, Core templates and suggestions.

Converting from functions to templates

Converting a theme function into a template takes some initial work but once it is done, it's easier to work with. If you are collaborating with a designer, the conversion will enable them to focus on designing, not coding. There are many templates already available in core and many more will be converted in future versions. Contributed modules that follow best practices should also have templates available. These instructions are provided for the theming hooks not already made available as templates.

Getting Drupal to recognize the theming hook as a template is automated. These are the only requirements to trigger this change:

Name of the template must match the theming hook. The underscores in the hook must be changed to hyphens. The template name must have an extension of ".tpl.php". (It can vary based on

the theme engine.)

Consider the theming function theme_user_signature. The theme hook here is "user_signature". Creating a file named "user-signature.tpl.php" will tell Drupal that the hook is now a template after clearing the registry. Any content in this file will now take the place of the function. The part that takes more work is setting up the variables to be used in this file and that is done through preprocess functions.

Page 98: Drupal 7

98

A few notes:

While it is possible to code directly inside the template, it is not considered good practice. All the complex logic should be separated from the .tpl.php file and placed within preprocess functions. This keeps it clean and easy to manage.

There is also the issue of security. The separation can minimize the chance of cross-site scripting attacks by cleaning out potentially malicious user generated content. When handing over template files to your designer, all the output should be clean so they do not have to concern themselves with security issues.

If your theme implements both a theme function and a template for a given hook, the theme function will always be used.

The auto discovery of theme overrides are done by PHPTemplate engine so make sure your theme has set the engine from the .info file.

Compare the theming functions in 5 to the template conversions in 6 for forums. You can use that as an example on converting between the two types.

The theme registry:

Drupal's theme registry maintains cached data on the available theming hooks and how to handle them.

For most theme developers, the registry does not have to be dealt with directly. Just remember to clear it when adding or removing theme functions and templates. Editing existing functions and templates does not require a registry rebuild.

To clear the theme registry, do one of the following things:

On the "Administer > Site configuration > Performance" page, click on the "Clear cached data" button.

With devel block enabled (comes with devel module), click the "Empty cache" link.

The API function drupal_rebuild_theme_registry (D6) or drupal_theme_rebuild (D7).

The theme registry is cached data instructing Drupal on the available theming hooks and how to handle it by indicating its type. In previous versions all theming calls were handled on the fly. Since a lot more work is being done under the hood, the cached instructions speeds up the process especially for templates. The theme engine your theme is running under should automatically register all the theming hooks for you.

There are special cases where you may need to work with the registry directly. When your theme requires a new hook to be registered that was not already implemented on the layers below it (core, modules, engine). This includes some forms when they are not explicitly themed by core or modules but instead rely on the default form presentation.

More details can be found in the sub-page, The theme registry for special cases.

Page 99: Drupal 7

99

Do not confuse the theme registry with the theme's .info file which is also cached. Points 1 and 2 on clearing the registry will clear both.

Your theme must be using phptemplate engine for its templates and functions to be discovered. Other engines should behave the same way. For engineless themes, it must be done manually. See phptemplate_theme to see how it is done.

Page 100: Drupal 7

100

Setting up variables for use in a template (preprocess and process functions) (Since 7 they apply to templates and functions). The main role of the preprocessor is to set up variables to be placed within the template (.tpl.php) files. Plain theme functions do not interact with preprocessors.

Notes:

Preprocessors are also used for providing template suggestions. In versions 5 and below, the function _phptemplate_variables served the same

purpose. It has been deprecated in 6. Prior to Drupal 6.7, for your theme to have its preprocessors recognized, the template

associated with the hook had to exist inside the theme. When a default template exists, copy it to your theme and clear the registry (or you should really be upgrading to a later version of Drupal anyway for security reasons, at which point you don't have to worry about this).

There can be numerous preprocessors for each theming hook. Every layer from core, modules, engines and themes can have one, each progressively building upon the variables before being rendered through template files. This keeps the markup clean and easy to work with inside templates by placing most of the logic inside these preprocessors.

Here are the expected preprocessors. They are run in this order when they exist:

1. template_preprocess - This is supplied by core and always added. The variables generated here are used for every templated hook.

2. template_preprocess_hook - The module or core file that implements the theming hook supplies this. The initial generation of all the variables specific to the hook is usually done here.

3. moduleName_preprocess - Do not confuse this with the preprocessor before it. This allows modules that did not originally implement the hook to influence the variables. Applies to all hooks.

4. moduleName_preprocess_hook - Same idea as the previous preprocessor but for specific hooks.

5. engineName_engine_preprocess - The preprocessor for theming engines. Applies to all hooks.

6. engineName_engine_preprocess_hook - Another preprocessor for theming engines but specific to a single hook.

7. engineName_preprocess - NOT RECOMMENDED. This is the first preprocessor that can be used inside the theme. It can be named after the theme engine the theme is running under. Applies to all hooks.

8. engineName_preprocess_hook - NOT RECOMMENDED. Another preprocessor named after the engine but specific to a single hook.

Page 101: Drupal 7

101

9. themeName_preprocess - This one is named after the theme itself. Applies to all hooks.

10. themeName_preprocess_hook - Same as the previous preprocessor but for a specific hook.

There are many possibilities here for modifying the variables. In most cases, it is only the first two preprocessors that exist. The first adds the default baseline variables and the second adds a set specific to the theming hook. Contributed modules taking advantage of the preprocessor slots (3 & 4) should document their behavior. It will not be covered extensively here.

While it is possible, the default PHPTemplate does not inject itself to this list. (5 & 6)

Themes can start adding their preprocessors seventh in the list. The preprocess function should be added to the theme's template.php file. However, due to the problems listed below, it is recommended that themes use their own names as the prefix (9 & 10). It is possible to grow this list beyond the ten shown above by having sub-themes add preprocessors strictly through their theme name as it is shown in the last two examples.

A few notes:

There is an unfortunate design flaw/bug in the theme system when a theme is part of a base theme/sub-theme hierarchy. Any preprocess functions prefixed with engineName_preprocess will be run multiple times (once for each theme in the hierarchy) instead of just once. Not only is this wasting resources, it can also cause difficult-to-debug errors.

The theme name (9 & 10) should always be used for themes. This minimizes any chance of a sub-theme redeclaring a function with an engineName_preprocess prefix and causing fatal PHP errors due to duplicate functions.

Note that nothing should be returned from these functions and the variables have to be passed by reference indicated by the ampersand before variables, e.g., &$variables.

Since the variables set early on are run through all the latter preprocessors due to references, be careful that you do not inadvertently reset any added before your theme. It is fine to reset them, but doing so accidentally can keep you guessing on what went wrong.

Example set from a module implementing the hook of "foo":

<?php function template_preprocess_foo(&$variables) { $variables['foo_list'] = array( 'list item 1', 'list item 2', 'list item 3', ); } ?>

And the preprocessor created from the theme to add to the variable set above:

<?php function drop_preprocess_foo(&$variables) {

Page 102: Drupal 7

102

// Do not do this unless you mean to: $variables['foo_list'] = array('list item 4'); // Instead do this: $variables['foo_list'][] = 'list item 4'; } ?>

The variables that end up in the template file are the keys set within $variables. So, with the above example, the variable in the template would result in $foo_list.

When using a preprocessor not specific to a theming hook, a second parameter can be used which always passes the current hook. Using a more specialized preprocess function like the one shown above is easier to maintain but if there will be shared code for multiple theming hooks, you may want to opt for this instead.

<?php function drop_preprocess(&$variables, $hook) { // Shared between the 'foo' and 'bar' theming hooks. if ($hook == 'foo' || $hook == 'bar') { $variables['foobar_item'] = 'foobar item'; } // Specific to 'foo'. if ($hook == 'foo') { $variables['foo_item'] = 'foo item'; } // Specific to 'bar'. elseif ($hook == 'bar') { $variables['bar_items'] = 'bar item'; } } ?>

In Drupal 7, there are two sets of variable process functions. The first is the existing "preprocess" functions. The second is "process" functions which are run after preprocessors. All the various prefixes and suffixes apply to this second phase in the exact same way. This is useful when certain variables need to be worked on in two phases. For example, adding classes into an array for the "preprocess" phase then flattening them into a string in the "process" phase, so it's ready to print within a template.

Page 103: Drupal 7

103

Default baseline variables The following are the baseline variables available to all template files. They are generated through the preprocessor function, template_preprocess. Variables specific to the template are documented inside the file.

New Variables available in D7 $attributes_array $title_attributes_array $content_attributes_array $classes_array

Array of html class attribute values. It is flattened into a string within the variable $classes.

$title_prefix An array containing additional output populated by modules, intended to be displayed in front of the main title tag that appears in the template.

$title_suffix An array containing additional output populated by modules, intended to be displayed after the main title tag that appears in the template.

Variables available in both D6 and D7 $id

The placement of the template. Each time the template is used, it is incremented by one.

$zebra Either "odd" or "even". Alternate each time the template is used.

$directory The theme path relative to the base install. example: "sites/all/themes/myTheme"

$is_admin Boolean returns TRUE when the visitor is a site administrator.

$is_front Boolean returns TRUE when viewing the front page of the site.

$logged_in Boolean returns TRUE when the visitor is a member of the site, logged in and authenticated.

$db_is_active Boolean returns TRUE when the database is active and running. This is only useful for theming in maintenance mode where the site may run into database problems.

$user The user object containing data for the current visitor. Some of the data contained here may not be safe. Be sure to pass potentially dangerous strings through check_plain.

Page 104: Drupal 7

104

Customizing and Overriding User Login page, Register, and Password Reset in Drupal 6 Customizing the user login, register, and password reset pages is fairly simple, and uses the following concepts:

preprocessing to set variables registration of functions in the theme registry creation of one or more theme templates.

Step 1. In the site theme directory, create or edit your template.php file.

Step 2. Look for a function named yourtheme_theme() and either modify it to add these return values or if it doesn't exist add the following:

<?php /** * Registers overrides for various functions. * * In this case, overrides three user functions */ function yourtheme_theme() { return array( 'user_login' => array( 'template' => 'user-login', 'arguments' => array('form' => NULL), ), 'user_register' => array( 'template' => 'user-register', 'arguments' => array('form' => NULL), ), 'user_pass' => array( 'template' => 'user-pass', 'arguments' => array('form' => NULL), ), ); } ?>

Notes about that code:

Change the function name by replacing "yourtheme" with the name of your theme The template can be the same for all three. The example above uses a different

template for each case: user-login, user-register, and user-pass The template names must use a dash, not an underscore Note: It's user_pass not user_password

Page 105: Drupal 7

105

Step 3. Now you implement three preprocess functions. There may be more concise ways to code this, but this works very well and is easy to read, so here we go!

<?php function yourtheme_preprocess_user_login(&$variables) { $variables['intro_text'] = t('This is my awesome login form'); $variables['rendered'] = drupal_render($variables['form']); } function yourtheme_preprocess_user_register(&$variables) { $variables['intro_text'] = t('This is my super awesome reg form'); $variables['rendered'] = drupal_render($variables['form']); } function yourtheme_preprocess_user_pass(&$variables) { $variables['intro_text'] = t('This is my super awesome insane password form'); $variables['rendered'] = drupal_render($variables['form']); } ?>

Notes about that code:

Change the function name by replacing "yourtheme" with the name of your theme The line $variables['intro_text'] adds the text that follows to the $variables

array, which gets passed to the template as $intro_text The second line renders the form and adds that code to the $variables array, which

gets passed to the template as $rendered

Step 4. Create template files to match the 'template' values defined above. In this case, we only need two: user-login.tpl.php and user-register.tpl.php (make sure to use a dash, not an underscore)

Step 5. Paste the following into user-login.tpl.php. Modify as necessary for user-login.tpl.php and user-register.tpl.php:

<p><?php print $intro_text; ?></p> <div class="my-form-wrapper"> <?php print $rendered; ?> </div>

Step 6. Save this file to the theme's main directory

Step 7. Rebuild the cache. Go to Administration > Performance and click on "Rebuild Cache" on the bottom of the page.

Now, the user login page will contain the new text from the preprocess function, and the tpl.php file(s) can be modified to suit the site's needs.

Page 106: Drupal 7

106

Example: Themable output Audience: Developers and coders Last modified: March 1, 2011

How to customize HTML for the search block

This example explains how to customize the HTML for the default search block. The default search block is created by the search module.

Using the .tpl.php file method:

1. From your site root, go into the modules/search folder 2. Copy modules/search/search-block-form.tpl.php to sites/all/themes/yourtheme/search-

block-form.tpl.php

You can now edit the version of the file that is in your theme folder.

search-block-form.tpl.php has this code:

<div class="container-inline"> <?php print $search_form; ?> </div>

This example removes the simple form print statement there, and creates new HTML output, following the documentation at the top of the file:

<div class="container-inline"> <?php $search['search_block_form'] = ' <div class="form-item" id="edit-search-block-form-1-wrapper"> <input type="text" maxlength="128" name="search_block_form" id="edit-search-block-form-1" size="15" value="" title="Enter the terms you wish to search for." class="form-text" /> <br /> <label for="edit-search-block-form-1">Search posts and comments</label> </div>'; print $search['search_block_form']; print $search['submit']; print $search['hidden']; ?> </div>

The HTML was simply grabbed from the original output by using view source (or Firebug), and then the elements can be re-arranged. You could add in other tags or whatever content you need. Add a little CSS to the mix and just about anything is possible. Don't change the names and IDs of the form elements, otherwise the form won't be processed correctly.

The variables that are being printed in this modified code are explained in the original search-block-form.tpl.php file (located in your_site_root/modules/search/). Here you can see which keys are available in the comments:

Page 107: Drupal 7

107

Default keys within $search:

$search['search_block_form']: Text input area wrapped in a div. $search['submit']: Form submit button. $search['hidden']: Hidden form elements. Used to validate forms when submitted.

Page 108: Drupal 7

108

Identifying Core Components A list of Drupal core components and how to override them.

Core Block CSS IDs Mission statement and highlighted region Navigation Taxonomy

Page 109: Drupal 7

109

Core Block CSS IDs Drupal Core generates a number of blocks, each with a unique CSS ID. In Drupal 7, many of the core block IDs have changed so they more clearly indicate the purpose of the block.

Active forum topics Drupal 6: block-forum-0 Drupal 7: block-forum-active

Author information Drupal 6: block-profile-0 Drupal 7: block-profile-author-information

Book navigation Drupal 6: block-book-0 Drupal 7: block-book-navigation

Language switcher Drupal 6: block-locale-0 Drupal 7: block-locale-language-switcher

Most recent poll Drupal 6: block-poll-0 Drupal 7: block-poll-recent

Navigation Drupal 6: block-user-1 Drupal 7: block-system-navigation

New forum topics Drupal 6: block-forum-1 Drupal 7: block-forum-new

Popular content Drupal 6: block-statistics-0 Drupal 7: block-statistics-popular

Powered by Drupal Drupal 6: block-system-0 Drupal 7: block-system-powered-by

Recent blog posts Drupal 6: block-blog-0 Drupal 7: block-blog-recent

Recent comments Drupal 6: block-comment-0 Drupal 7: block-comment-recent

Page 110: Drupal 7

110

Search form Drupal 6: block-search-0 Drupal 7: block-search-form

Syndicate Drupal 6: block-node-0 Drupal 7: block-node-syndicate

User login Drupal 6: block-user-0 Drupal 7: block-user-login

Who's new Drupal 6: block-user-2 Drupal 7: block-user-new

Who's online Drupal 6: block-user-3 Drupal 7: block-user-online

Drupal 6 CSS style declaration:

/* Make the text in the user login block bigger. */ #block-user-0 { font-size: 1.5em; }

Drupal 7 CSS style declaration:

/* Make the text in the user login block bigger. */ #block-user-login { font-size: 1.5em; }

Page 111: Drupal 7

111

Mission statement and highlighted region Drupal 6.x

In Drupal 6, the page template receives a special variable called $mission. This contains the mission statement, displaying it on the front page. Drupal 6 themes also had an option on the theme settings page to toggle this functionality.

In .info:

features[] = mission

In page.tpl.php:

<?php print $mission; ?>

Drupal 7.x

Drupal 7 removes the mission setting (and the option toggle) in favor of the more general custom block placement in regions. Drupal 7 core themes now include a region named 'highlighted' which uses the same display as D6's mission statement area. Whether this region has content now depends on administrators setting block placement, and is no longer limited to the front page.

If your theme defines custom regions, and does not include a "highlighted" region, you may define the region by adding it to the existing list of regions your .info file. If the theme does not define regions in the .info file, the regions provided by core will be inherited automatically, and you'll only need to ensure that you print it in page.tpl.php.

in .info:

regions[highlighted] = Highlighted

in page.tpl.php:

<?php print render($page['highlighted']); ?>

Page 112: Drupal 7

112

Navigation Primary and Secondary links have been renamed to Main and Secondary menu. Themes which support these options will need to be updated to use the new variable names:

6.x: page.tpl.php

<div id="menu"> <?php if (isset($secondary_links)) { ?><?php print theme('links', $secondary_links, array('class' => 'links', 'id' => 'subnavlist')); ?><?php } ?> <?php if (isset($primary_links)) { ?><?php print theme('links', $primary_links, array('class' => 'links', 'id' => 'navlist')) ?><?php } ?> </div>

7.x: page.tpl.php

<?php if ($main_menu || $secondary_menu): ?> <div id="navigation"><div class="section"> <?php print theme('links__system_main_menu', array('links' => $main_menu, 'attributes' => array('id' => 'main-menu', 'class' => array('links', 'inline', 'clearfix')), 'heading' => t('Main menu'))); ?> <?php print theme('links__system_secondary_menu', array('links' => $secondary_menu, 'attributes' => array('id' => 'secondary-menu', 'class' => array('links', 'inline', 'clearfix')), 'heading' => t('Secondary menu'))); ?> </div></div> <!-- /.section, /#navigation --> <?php endif; ?>

You will also need to make the appropriate variable name changes if your theme's theme.info is defining features[]. Defining renamed or replaced features may cause all features to render as blank or empty arrays.

6.x: theme.info - features[]

features[] = primary_links features[] = secondary_links

7.x: theme.info - features[]

features[] = main_menu features[] = secondary_menu

Also, if your theme.info is defining features[] = mission please note that this feature has been removed and replaced with a variable named $mission which can be output in your page template.

Page 113: Drupal 7

113

Taxonomy Drupal offers Taxonomy, a core feature allowing users to tag content.

Unrendered taxonomy links no longer available as a separate variable in node.tpl.php files In Drupal 6, node.tpl.php files could use the $taxonomy variable if they needed access to an array of unrendered taxonomy links associated with the current node.

In Drupal 7, this is no longer the case. Instead, all links have been moved into the $node object. The array of unrendered taxonomy links can now be found in $node->content['links']['terms']['#value'] instead. (Note that this array should be used with caution, since the text contained within it has not been escaped to prevent XSS attacks.)

Rendered taxonomy links are not affected; node.tpl.php files can continue to access these via the $terms variable, as before. In many cases, the $terms variable is what you want to use in your theme anyway, and you might be able to replace references to $taxonomy with it, as in the following example:

Drupal 6.x <?php if ($taxonomy): ?> <div class="terms"><?php print $terms ?></div> <?php endif;?>

Drupal 7.x <?php if ($terms): ?> <div class="terms"><?php print $terms ?></div> <?php endif;?>

Page 114: Drupal 7

114

Menu theming The new theme_links($variables) function in D7 receives only an associative array to build the entire link structure. As you can read in the documentation, your links should be passed through the links argument as an associative array, but does not describe where and/or how you get this array.

The following is an example of how to do it with the user menu:

<?php $user_menu = menu_navigation_links('user-menu'); print theme('links', array( 'links' => $user_menu, 'attributes' => array( 'id' => 'user-menu', 'class' => array('links', 'clearfix'), ), 'heading' => array( 'text' => t('User menu'), 'level' => 'h2', 'class' => array('element-invisible'), ), )); ?>

If you are wondering what the "user-menu" argument is in the menu_navigation_links($menu_name, $level = 0) function, it is the name of the menu to be printed. You can find the menu name for this example on the User menu administration page (Administration > Structure > Menus > User menu or http://example.com/admin/structure/menu/manage/user-menu/edit) on your site.

In this example, the $user_menu variable is not mandatory, but doing it this way, you can later use it in a conditional statement that is quite common in the templates.

Page 115: Drupal 7

115

Overriding a menu in a block Create an override function in your theme's template.php file:

<?php function themename_links__system_MENUNAME_menu($variables) {} ?>

The $variables parameter is passed in, and it contains an index called 'links'. Each link contains an 'href' and a 'title', among other things.

So inside your override function, loop over the links:

<?php foreach ($variables['links'] as $link) {}?>

If we just want to output an <a> tag, outside of Drupal we'd normally do: <?php echo "<a href="{$link['href']}">{$link['title']}</a>"; ?>

However, drupal has the l() function to create links for you. This is preferable because there are some special cases such as the <front> link.

So instead, we'd do something like <?php echo l($link['title'], $link['href'], $link); ?>

The l() function will take care of adding an active class to a link if that link is to the page currently being viewed.

Your final function might look something like:

<?php function themename_links__system_MENUNAME_menu($variables) { $output = ''; foreach ($variables['links'] as $link) { $output .= l($link['title'], $link['href'], $link); } return $output; } ?>

Page 116: Drupal 7

116

The theme registry for special cases You should be familiar with the purpose of the theme registry before continuing on this page. The instructions here will cover how to manually register a theming hook and explain how it can be manipulated.

The most common case for manually registering hooks is in forms. Form elements are themable but there is another dimension to how they are processed. There are the generic elements such as checkboxes, radio selects, submit button, drop down menus, etc. Each element is themable on its own and overriding it does not require manually registering the hooks associated with it. The extra dimension comes in with custom forms where each element is arranged in very specific ways. In some forms, they are already arranged, themed and registered. For these forms, manually registering is not required. The forms that are not explicitly themed will default to how form API renders them.

Registered form example: Here's an example for two of the search forms registered by search.module, the search box and search block. Every form has a unique ID associated with it. Registering the IDs also serves as the theming hook. In this case, it is "search_theme_form" and "search_block_form". <?php function search_theme() { return array( 'search_theme_form' => array( 'arguments' => array('form' => NULL), 'template' => 'search-theme-form', ), 'search_block_form' => array( 'arguments' => array('form' => NULL), 'template' => 'search-block-form', ), ... ); } ?>

The Form API passes control of its presentation along to the handler of the registered hook. In this example, it is registered with the type of template with its default arguments. The output can be altered easily from the theme since it was already registered as a template. Auto discovery of the hook only happens with existing theming hooks registered below the theming layer. (see image)

Registering an unregistered form: There is another search form that is not registered. It is the form with the ID of "search_form" used on the main search page. The data for the form is constructed in a function just like any other form but it leaves it up to the Form API to deal with the presentation based on its data structure.

To extend this so it can be overridden, you have to register it from your theme with hook_theme. Place the following inside your template.php file replacing the "drop" prefix with the name of your theme. The theming hook is the ID of the form:

Page 117: Drupal 7

117

<?php function drop_theme() { return array( 'search_form' => array( 'arguments' => array('form' => NULL), ), ); } ?>

Themed forms always have an argument of "form". Since the template was not specified, the hook will be seen as a theme function, not a template. The theme function has to have a matching prefix of the theme that registered it. phptemplate_* will not work. So, with the above entry, the theme function would look like this:

<?php function drop_search_form($form) { $advanced = ''; $simple = ''; foreach (element_children($form) as $element) { if ($element == 'advanced') { $advanced .= drupal_render($form[$element]); } else { $simple .= drupal_render($form[$element]); } } return $advanced . $simple; } ?>

The only thing changed here is the positioning of the advanced search form.

Sub-themes overriding a form that was registered in its base theme does not have to manually re-register the form. Remember, the registering allows auto discovery for the layers above it and sub-themes are just another layer on top of base themes.

This may be more automated in future versions. The developers are aware of the burden it places on themers.

Manual manipulating

Manual manipulation of the theme registry is an advanced feature. You can read about it by clicking the "Theme registry" in the block provided by devel module. It can also be returned with theme_get_registry.

To be continued...

Working with template suggestions

Page 118: Drupal 7

118

Template suggestions are alternate templates based on existing .tpl.php files. These suggestions are used when a specific condition is met and a matching file exists. All layers from core, modules, theme engines and themes can provide the suggestions. You can think of them as naming hints telling the system to pick and choose based on the right circumstances. The idea is simple but it is a powerful feature providing another layer of customization.

Theme Developer module showing template suggestions for the possible "page" templates. The image shows Drupal 6 template suggestions.

These naming suggestions are set from preprocess functions. There are plenty already provided by core. If you need to extend it further, add a preprocessor for the theming hook into your template.php file. The examples below add suggestions on the "node" and "page" theming hooks. It can be added to any hook implemented as a template.

Drupal 7

A listing of all the suggestions for Drupal 7 core can be found in Drupal 7 Template Suggestions.

To use the examples below, paste the function definitions into a template.php file in your theme directory, ex: "sites/all/themes/drop/template.php". If a template.php file does not exist in your theme, you may create one. The prefix of "drop" should be the name of your theme.

<?php // Add a single suggestion for nodes that have the "Promoted to front page" box checked. function drop_preprocess_node(&$variables) { if ($variables['promote']) { // looks for node--promoted.tpl.php in your theme directory $variables['theme_hook_suggestion'] = 'node__promoted'; } } // Add multiple suggestions for pages based on the logged in user's roles. function drop_preprocess_page(&$variables) { global $user; if (!empty($user->roles)) { foreach ($user->roles as $role) { $filter = '![^abcdefghijklmnopqrstuvwxyz0-9-_]+!s'; $string_clean = preg_replace($filter, '-', drupal_strtolower($role)); // looks for page--[role].tpl.php in your theme directory // ex: page--administrator.tpl.php $variables['theme_hook_suggestions'][] = 'page__'. $string_clean;

Page 119: Drupal 7

119

} } } ?>

There are two ways to add these suggestions.

1. The key of 'theme_hook_suggestion' accepts a single suggestion and it takes precedence. If the condition is met and the file exists, it will be used ignoring all others.

2. The key of 'theme_hook_suggestions' (plural) can accept an array of suggestions. They are processed in FILO order (first in last out order). Adding to the array should be done with a general condition first, progressively getting more specific so it cascades based on specificity.

A few notes:

When adding to 'theme_hook_suggestions', add to the array. Do not reset it since the variables are passed by reference. All the suggestions set before it in core and modules would be lost.

<?php // Do not do this: $variables['theme_hook_suggestion'] = array('hook__suggestion'); // Instead do this: $variables['theme_hook_suggestions'][] = 'hook__suggestion'; ?>

Prefix the suggestion with the name of the hook it is associated with, such as "node" or "page". This keeps it clear and the files grouped together. It also minimizes any chance of Drupal registering the template with a different hook.

Use two hyphens to separate the hook name from the rest of the suggestion. This is consistent with template naming suggestions in core.

Drupal 6

A listing of all the suggestions for core can be found in Drupal 6 Template Suggestions.

The prefix of "drop" should be the name of your theme.

<?php function drop_preprocess_page(&$variables) { global $user; // Add a single suggestion based on the throttle module in core. if (module_invoke('throttle', 'status') && isset($user->roles[1])) { $variables['template_file'] = 'page-busy'; } // Add multiple suggestions for pages based on the logged in user's roles. if (!empty($user->roles)) { foreach ($user->roles as $role) {

Page 120: Drupal 7

120

$filter = '![^abcdefghijklmnopqrstuvwxyz0-9-_]+!s'; $string_clean = preg_replace($filter, '-', drupal_strtolower($role)); $variables['template_files'][] = 'page-'. $string_clean; } } } ?>

Theming a page by arbitrary content types

You could improve above code to have the theme suggestion for all content types in your site.

<?php function drop_preprocess_page(&$variables) { if ($variables['node']->type != "") { $variables['template_files'][] = "page-node-" . $variables['node']->type; } } ?>

Now just make your template in form page-node-your_content_type. If it's missing, the default page template will be used.

There are two ways to add these suggestions.

1. The key of 'template_file' accepts a single suggestion and it takes precedence. If the condition is met and the file exists, it will be used ignoring all others.

2. The key of 'template_files' (plural) can accept an array of suggestions. They are processed in FILO order (first in last out order). Adding to the array should be done with a general condition first, progressively getting more specific so it cascades based on specificity.

With the above example, Drupal will attempt to use a file named "page-busy.tpl.php" when the throttling threshold is met for anonymous users (anonymous role id typically set to 1). The others inform Drupal to look for templates based on the roles assigned to the current user, e.g., "page-authenticated-user.tpl.php". If none apply, the base template of "page.tpl.php" is used.

These are simply examples. You can set any context based on any data available to you.

A few notes:

When adding to 'template_files', add to the array. Do not reset it since the variables are passed by reference. All the suggestions set before it in core and modules would be lost.

<?php // Do not do this: $variables['template_files'] = array('hook-suggestion'); // Instead do this:

Page 121: Drupal 7

121

$variables['template_files'][] = 'hook-suggestion'; ?>

Prefix the suggestion with the name of the hook it is associated with. This keeps it clear and the files grouped together. It also minimizes any chance of Drupal registering the template with a different hook.

Use hyphens instead of underscores for consistency. The main template will never use underscores.

Suggestions work only when it is placed in the same directory as the base template. Templates can be placed in any sub-directory of the theme. They must be paired into the same location.

The theme registry does not have to be cleared for suggestions. It's only the base template that needs to be registered. Suggestions are discovered on the fly.

Page 122: Drupal 7

122

Architectural view of theming This page provides background information on the underlying architecture of theming. It will be of interest to people who need a deeper understanding of how overrides work.

A common software practice is to compartmentalize the programming layer from the presentation layer. There are numerous reasons for this, the most obvious being that the skill set required for programming the back-end business logic is very different from creating a visually appealing and effective user interface. As a theme developer, you can control certain aspects of the available data, but it is limited to the output and presentation. Only Drupal core and modules should work with input. For example, a module can implement a form with a default look and feel and handle user input, saving it to the database. The theme's role is only to override the look and feel.

This abstraction in Drupal is achieved through the theme function. It is a conduit to the theming subsystem. It allows theme engines to provide an optional middle layer for tagging languages such as PHPTAL or Smarty. It also allows themes to control all presentation markup. Theme engines are optional as are tagging languages. PHPTemplate is the default engine. As the name suggests, it uses the language PHP when outputting variables mixed in with the xHTML markup.

Starting with Drupal 6, the requirements for creating theme engines have been lowered substantially.

All layers can implement a themed representation of the output, but (with a few exceptions) only within the theming layers can overrides occur. Theme engines can override themed output from core and modules, while themes can override everything else.

Note that the PHPTemplate engine does not override any output but other theme engines can. There is a special case where a module can influence the output or override absolutely everything, but it is for very special cases and should not interfere in most situations. For example, the Theme Developer module does this to aid theme development. More details will be discussed in a later section.

You can ignore most of this if your theme will be styled entirely through CSS, but when the markup needs to be altered it is important to know how to get to the source of the output so it can be altered.

Page 123: Drupal 7

123

Note that the Drupal core and countless modules always use themable functions and template files to output the presentation markup. Never hack on files outside your theme folder as that would lead to complications when there is a need to update. Doing this is known as "forking". The power of open source lies in having the community manage bug fixes and add new features. Once you start a fork you create a closed system, and you lose the leverage of the community. Drupal provides all the functionality to override the presentation layer. If you ever have to hack on files outside the theme, either you are doing something wrong or you have found a bug. In the latter case, please file a bug report. Or even better, provide a patch file to fix the problem.

For those familiar with the PHPTemplate engine in previous incarnations, almost all the functionality has been moved deeper into core. The job of PHPTemplate now is to only discover theme functions and templates on behalf of the theme. It is less of an engine and more of a theme helper. PHPTemplate was originally written by Adrian Rossouw for 4.7. The changes in 6 were the work of Earl Miles. An extended forum discussion provides some of the reasoning behind the initial creation of the engine and the issue queue for the new direction in 6.

Page 124: Drupal 7

124