Beruflich Dokumente
Kultur Dokumente
Theming Guide
Abderrahman Firoud
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
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
Drupal 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)
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.
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.
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 9
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
10
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
11
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[] features[] features[] features[] features[] features[] features[] = = = = = = = logo name slogan node_user_picture comment_user_picture favicon main_menu
12
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
13
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"
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.
14
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[] features[] features[] features[] features[] features[] features[] features[] features[] features[] = = = = = = = = = = logo name slogan mission node_user_picture comment_user_picture search favicon primary_links secondary_links
Drupal 7
features[] features[] features[] features[] = = = = logo name slogan node_user_picture
15
= = = =
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
16
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
17
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:
18
for 19
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 $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")
20
); $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.
21
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); }
22
// 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; } ?>
23
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[] features[] features[] features[] features[] features[] features[] features[] features[] = = = = = = = = = logo name slogan node_user_picture comment_user_picture comment_user_verification favicon main_menu 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. 24
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"
25
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.
26
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.
27
28
29
Design
It is important to realize that due to the way color.module works, not every design can be colorized.
30
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(
31
'#0072b9,#027ac6,#2385c2,#5ab5ee,#494949' (Default)'), '#464849,#2f416f,#2a2b2d,#5d6779,#494949' '#55c0e2,#000000,#085360,#007e94,#696969' '#d5b048,#6c420e,#331900,#971702,#494949' '#3f3f3f,#336699,#6598cb,#6598cb,#000000' '#d0cb9a,#917803,#efde01,#e6fb2d,#494949' '#0f005c,#434f8c,#4d91ff,#1a1575,#000000' '#c9c497,#0c7a00,#03961e,#7be000,#494949' '#ffe23d,#a9290a,#fc6d1d,#a30f42,#494949' '#788597,#3f728d,#a9adbc,#d4d4d4,#707070' '#5b5fa9,#5b5faa,#0a2352,#9fa8d5,#494949' '#7db323,#6a9915,#b5d52a,#7db323,#191a19' '#12020b,#1b1a13,#f391c6,#f41063,#898080' '#b7a0ba,#c70000,#a1443a,#f21107,#515d52' '#18583d,#1b5f42,#34775a,#52bf90,#2d2d2d' )); ?>
=> t('Blue Lagoon => => => => => => => => => => => => => => t('Ash'), t('Aquamarine'), t('Belgian Chocolate'), t('Bluemarine'), t('Citrus Blast'), t('Cold Day'), t('Greenbeam'), t('Mediterrano'), t('Mercury'), t('Nocturnal'), t('Olivia'), t('Pink Plastic'), t('Shiny Tomato'), 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.
32
)); ?>
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' 'images/bg-bar.png' 'images/bg-bar-white.png' 'images/bg-tab.png' 'images/bg-navigation.png' 'images/bg-content-left.png' 'images/bg-content-right.png' 'images/bg-content.png' 'images/bg-navigation-item.png' 'images/bg-navigation-item-hover.png' 'images/gradient-inner.png' 'logo.png' 'screenshot.png' )); ?>
=> => => => => => => => => => =>
array(0, 37, 1, 280), array(202, 530, 76, 14), array(202, 506, 76, 14), array(107, 533, 41, 23), array(0, 0, 7, 37), array(40, 117, 50, 352), array(510, 117, 50, 352), array(299, 117, 7, 200), array(32, 37, 17, 12), array(54, 37, 17, 12), array(646, 307, 112, 42),
=> array(622, 51, 64, 73), => 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.
33
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.
34
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); } } ?>
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.
35
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)
36
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. Sitespecific 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
37
Overriding inherited style sheets: Specify a style sheet with the same filename in the subtheme. 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.
38
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 subtheme, 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.
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.
39
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
40
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).
41
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.
42
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
43
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 <link> 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"; } ?>
44
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.
45
46
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 screenreaders. 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.
47
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.
48
6.x
<div class="clear-block">
49
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; }
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.
50
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
51
52
$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.
or in Drupal 7:
<?php $vars['happy'] = theme_get_setting('garland_happy'); ?>
53
it is null. If it is null, we save the defaults using variable_set() and then force the refresh of the settings in Drupals 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'))) { 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.
54
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 themes 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; youll need to use the actual name of your theme as the prefix. Heres 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, youll need to add a simple line to your .info file: settings[SETTING_NAME] = DEFAULT_VALUE. For our foo theme, youd need to edit the foo.info file and add this line:
settings[foo_example] = blue bikeshed
In any of your themes PHP files, you can retrieve the value the user set by calling:
<?php $foo_example = theme_get_setting('foo_example'); ?>
55
Experience with PHP Experience with module writing Experience with theme writing
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, ), ); }
56
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 ) {
57
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.
58
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. 59
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 */
60
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' ), ); } ?>
61
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.
62
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); ?>
63
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' =>
64
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.
65
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(); } ?>
66
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));
67
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('fooprocessed').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.
68
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" />"; }
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.
69
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
70
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"></scr ipt> <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"></scr ipt> <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
71
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.
with
(function($jq, window, undefined) {
72
73
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/..."
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/..."
Comment "modules/comment/..."
comment-wrapper.tpl.php comment.tpl.php
74
field.tpl.php
node.tpl.php
Overlay "modules/overlay/..."
overlay.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/..."
Search "modules/search/..."
search-block-form.tpl.php
75
search-result.tpl.php search-results.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
toolbar.tpl.php
76
77
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. 2. 3. 4. field--field-name--content-type.tpl.php field--content-type.tpl.php field--field-name.tpl.php 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
78
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. 2. 3. 4. page--node--edit.tpl.php page--node--1.tpl.php page--node.tpl.php page.tpl.php
Also see page.tpl.php in the Drupal API documentation for more information.
79
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. 2. 3. 4. page.tpl.php (this is always a suggestion) page--node.tpl.php (and prefix is set to page__node) page--node--%.tpl.php 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
80
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.
81
82
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.
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
83
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-wrappercountry.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-resultsnode.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.
84
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.
85
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.
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
86
Example: Themable output Identifying Core Components Menu theming The theme registry for special cases Working with template suggestions Architectural view of theming
87
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.
"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) {
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).
89
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 nodecontent_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.
90
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.
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:
91
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.
92
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".
93
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.")
94
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:
95
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.
96
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.
97
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.
98
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.
99
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.
100
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) {
101
// 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.
102
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.
The placement of the template. Each time the template is used, it is incremented by one.
$zebra
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.
103
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), ), ); } ?>
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
104
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']); } ?>
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. 105
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="editsearch-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 searchblock-form.tpl.php file (located in your_site_root/modules/search/). Here you can see which keys are available in the comments:
106
$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.
107
Core Block CSS IDs Mission statement and highlighted region Navigation Taxonomy
108
109
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; }
110
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']); ?>
111
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
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.
112
Taxonomy
Drupal offers Taxonomy, a core feature allowing users to tag content.
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;?>
113
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.
114
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; } ?>
115
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:
116
<?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...
117
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;
118
} } } ?>
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) {
119
$filter = '![^abcdefghijklmnopqrstuvwxyz0-9-_]+!s'; $string_clean = preg_replace($filter, '-', drupal_strtolower($role)); $variables['template_files'][] = 'page-'. $string_clean; } } } ?>
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:
120
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.
121
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.
122
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.
123
124