Commit 2df43894 authored by webchick's avatar webchick
Browse files

#306358 by dvessel, JohnAlbin, and flobruit: Add a single $classes string (and...

#306358 by dvessel, JohnAlbin, and flobruit: Add a single $classes string (and corresponding $classes_array) for all dynamic classes in template files.
parent cb756bcf
......@@ -287,6 +287,7 @@ function drupal_theme_rebuild() {
* _theme_process_registry() is called for this theme hook, either the
* 'path' key from hook_theme() (if defined) or $path is added.
* - 'preprocess functions': See theme() for detailed documentation.
* - 'process functions': See theme() for detailed documentation.
* @param $name
* The name of the module, theme engine, base theme engine, theme or base
* theme implementing hook_theme().
......@@ -311,6 +312,14 @@ function drupal_theme_rebuild() {
function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
$result = array();
$function = $name . '_theme';
// Template functions work in two distinct phases with the process
// functions always being executed after the preprocess functions.
$template_phases = array(
'preprocess functions' => 'preprocess',
'process functions' => 'process',
);
if (function_exists($function)) {
$result = $function($cache, $type, $theme, $path);
......@@ -335,87 +344,96 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
include_once DRUPAL_ROOT . '/' . $info['path'] . '/' . $info['file'];
}
if (isset($info['template']) && !isset($info['path'])) {
$result[$hook]['template'] = $path . '/' . $info['template'];
}
// If 'arguments' have been defined previously, carry them forward.
// This should happen if a theme overrides a Drupal defined theme
// function, for example.
if (!isset($info['arguments']) && isset($cache[$hook])) {
$result[$hook]['arguments'] = $cache[$hook]['arguments'];
}
// Likewise with theme paths. These are used for template naming suggestions.
// Theme implementations can occur in multiple paths. Suggestions should follow.
if (!isset($info['theme paths']) && isset($cache[$hook])) {
$result[$hook]['theme paths'] = $cache[$hook]['theme paths'];
}
// Check for sub-directories.
$result[$hook]['theme paths'][] = isset($info['path']) ? $info['path'] : $path;
// Check for default _preprocess_ functions. Ensure arrayness.
if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) {
$info['preprocess functions'] = array();
$prefixes = array();
if ($type == 'module') {
// Default preprocessor prefix.
$prefixes[] = 'template';
// Add all modules so they can intervene with their own preprocessors. This allows them
// to provide preprocess functions even if they are not the owner of the current hook.
$prefixes += module_list();
}
elseif ($type == 'theme_engine' || $type == 'base_theme_engine') {
// Theme engines get an extra set that come before the normally named preprocessors.
$prefixes[] = $name . '_engine';
// The theme engine also registers on behalf of the theme. The theme or engine name can be used.
$prefixes[] = $name;
$prefixes[] = $theme;
}
else {
// This applies when the theme manually registers their own preprocessors.
$prefixes[] = $name;
// The following apply only to theming hooks implemented as templates.
if (isset($info['template'])) {
// Prepend the current theming path when none is set.
if (!isset($info['path'])) {
$result[$hook]['template'] = $path . '/' . $info['template'];
}
foreach ($prefixes as $prefix) {
if (function_exists($prefix . '_preprocess')) {
$info['preprocess functions'][] = $prefix . '_preprocess';
// These are used for template naming suggestions. Theme implementations
// can occur in multiple paths. Suggestions should follow.
if (!isset($info['theme paths']) && isset($cache[$hook])) {
$result[$hook]['theme paths'] = $cache[$hook]['theme paths'];
}
// Check for sub-directories.
$result[$hook]['theme paths'][] = isset($info['path']) ? $info['path'] : $path;
foreach ($template_phases as $phase_key => $template_phase) {
// Check for existing variable processors. Ensure arrayness.
if (!isset($info[$phase_key]) || !is_array($info[$phase_key])) {
$info[$phase_key] = array();
$prefixes = array();
if ($type == 'module') {
// Default variable processor prefix.
$prefixes[] = 'template';
// Add all modules so they can intervene with their own variable processors. This allows them
// to provide variable processors even if they are not the owner of the current hook.
$prefixes += module_list();
}
elseif ($type == 'theme_engine' || $type == 'base_theme_engine') {
// Theme engines get an extra set that come before the normally named variable processors.
$prefixes[] = $name . '_engine';
// The theme engine also registers on behalf of the theme. The theme or engine name can be used.
$prefixes[] = $name;
$prefixes[] = $theme;
}
else {
// This applies when the theme manually registers their own variable processors.
$prefixes[] = $name;
}
foreach ($prefixes as $prefix) {
if (function_exists($prefix . '_' . $template_phase)) {
$info[$phase_key][] = $prefix . '_' . $template_phase;
}
if (function_exists($prefix . '_' . $template_phase . '_' . $hook)) {
$info[$phase_key][] = $prefix . '_' . $template_phase . '_' . $hook;
}
}
}
if (function_exists($prefix . '_preprocess_' . $hook)) {
$info['preprocess functions'][] = $prefix . '_preprocess_' . $hook;
// Check for the override flag and prevent the cached variable processors from being used.
// This allows themes or theme engines to remove variable processors set earlier in the registry build.
if (!empty($info['override ' . $phase_key])) {
// Flag not needed inside the registry.
unset($result[$hook]['override ' . $phase_key]);
}
elseif (isset($cache[$hook][$phase_key]) && is_array($cache[$hook][$phase_key])) {
$info[$phase_key] = array_merge($cache[$hook][$phase_key], $info[$phase_key]);
}
$result[$hook][$phase_key] = $info[$phase_key];
}
}
// Check for the override flag and prevent the cached preprocess functions from being used.
// This allows themes or theme engines to remove preprocessors set earlier in the registry build.
if (!empty($info['override preprocess functions'])) {
// Flag not needed inside the registry.
unset($result[$hook]['override preprocess functions']);
}
elseif (isset($cache[$hook]['preprocess functions']) && is_array($cache[$hook]['preprocess functions'])) {
$info['preprocess functions'] = array_merge($cache[$hook]['preprocess functions'], $info['preprocess functions']);
}
$result[$hook]['preprocess functions'] = $info['preprocess functions'];
}
// Merge the newly created theme hooks into the existing cache.
$cache = array_merge($cache, $result);
}
// Let themes have preprocess functions even if they didn't register a template.
// Let themes have variable processors even if they didn't register a template.
if ($type == 'theme' || $type == 'base_theme') {
foreach ($cache as $hook => $info) {
// Check only if it's a template and not registered by the theme or engine.
if (!empty($info['template']) && empty($result[$hook])) {
if (!isset($info['preprocess functions'])) {
$cache[$hook]['preprocess functions'] = array();
}
if (function_exists($name . '_preprocess')) {
$cache[$hook]['preprocess functions'][] = $name . '_preprocess';
}
if (function_exists($name . '_preprocess_' . $hook)) {
$cache[$hook]['preprocess functions'][] = $name . '_preprocess_' . $hook;
foreach ($template_phases as $phase_key => $template_phase) {
if (!isset($info[$phase_key])) {
$cache[$hook][$phase_key] = array();
}
if (function_exists($name . '_' . $template_phase)) {
$cache[$hook][$phase_key][] = $name . '_' . $template_phase;
}
if (function_exists($name . '_' . $template_phase . '_' . $hook)) {
$cache[$hook][$phase_key][] = $name . '_' . $template_phase . '_' . $hook;
}
// Ensure uniqueness.
$cache[$hook][$phase_key] = array_unique($cache[$hook][$phase_key]);
}
// Ensure uniqueness.
$cache[$hook]['preprocess functions'] = array_unique($cache[$hook]['preprocess functions']);
}
}
}
......@@ -558,7 +576,8 @@ function list_themes($refresh = FALSE) {
* $variables array. This array is then modified by the module implementing
* the hook, theme engine (if applicable) and the theme. The following
* functions may be used to modify the $variables array. They are processed in
* this order when available:
* two distinct phases; "preprocess" and "process" functions. The order it is
* listed here is the order in which it will execute.
*
* - template_preprocess(&$variables)
* This sets a default set of variables for all template implementations.
......@@ -611,6 +630,57 @@ function list_themes($refresh = FALSE) {
* The same applies from the previous function, but it is called for a
* specific hook.
*
* - template_process(&$variables)
* This sets a default set of variables for all template implementations.
*
* - template_process_HOOK(&$variables)
* This is the first processor called specific to the hook; it should be
* implemented by the module that registers it.
*
* - MODULE_process(&$variables)
* This will be called for all templates; it should only be used if there
* is a real need. It's purpose is similar to template_process().
*
* - MODULE_process_HOOK(&$variables)
* This is for modules that want to alter or provide extra variables for
* theming hooks not registered to itself. For example, if a module named
* "foo" wanted to alter the $submitted variable for the hook "node" a
* process function of foo_process_node() can be created to intercept
* and alter the variable.
*
* - ENGINE_engine_process(&$variables)
* This function should only be implemented by theme engines and exists
* so that it can set necessary variables for all hooks.
*
* - ENGINE_engine_process_HOOK(&$variables)
* This is the same as the previous function, but it is called for a single
* theming hook.
*
* - ENGINE_process(&$variables)
* This is meant to be used by themes that utilize a theme engine. It is
* provided so that the processor is not locked into a specific theme.
* This makes it easy to share and transport code but theme authors must be
* careful to prevent fatal re-declaration errors when using sub-themes that
* have their own processor named exactly the same as its base theme. In
* the default theme engine (PHPTemplate), sub-themes will load their own
* template.php file in addition to the one used for its parent theme. This
* increases the risk for these errors. A good practice is to use the engine
* name for the base theme and the theme name for the sub-themes to minimize
* this possibility.
*
* - ENGINE_process_HOOK(&$variables)
* The same applies from the previous function, but it is called for a
* specific hook.
*
* - THEME_process(&$variables)
* These functions are based upon the raw theme; they should primarily be
* used by themes that do not use an engine or by sub-themes. It serves the
* same purpose as ENGINE_process().
*
* - THEME_process_HOOK(&$variables)
* The same applies from the previous function, but it is called for a
* specific hook.
*
* There are two special variables that these hooks can set:
* 'template_file' and 'template_files'. These will be merged together
* to form a list of 'suggested' alternate template files to use, in
......@@ -659,7 +729,7 @@ function theme() {
// point path_to_theme() to the currently used theme path:
$theme_path = $info['theme path'];
// Include a file if the theme function or preprocess function is held elsewhere.
// Include a file if the theme function or variable processor is held elsewhere.
if (!empty($info['file'])) {
$include_file = $info['file'];
if (isset($info['path'])) {
......@@ -706,13 +776,14 @@ function theme() {
}
}
if (isset($info['preprocess functions']) && is_array($info['preprocess functions'])) {
// This construct ensures that we can keep a reference through
// call_user_func_array.
$args = array(&$variables, $hook);
foreach ($info['preprocess functions'] as $preprocess_function) {
if (drupal_function_exists($preprocess_function)) {
call_user_func_array($preprocess_function, $args);
// This construct ensures that we can keep a reference through
// call_user_func_array.
$args = array(&$variables, $hook);
// Template functions in two phases.
foreach (array('preprocess functions', 'process functions') as $template_phase) {
foreach ($info[$template_phase] as $template_function) {
if (drupal_function_exists($template_function)) {
call_user_func_array($template_function, $args);
}
}
}
......@@ -1064,7 +1135,7 @@ function theme_get_setting($setting_name, $refresh = FALSE) {
*
* @param $template_file
* The filename of the template to render. Note that this will overwrite
* anything stored in $variables['template_file'] if using a preprocess hook.
* anything stored in $variables['template_file'] if using a variable processor hook.
* @param $variables
* A keyed array of variables that will appear in the output.
*
......@@ -1786,10 +1857,9 @@ function _theme_table_cell($cell, $header = FALSE) {
}
/**
* Adds a default set of helper variables for preprocess functions and
* templates. This comes in before any other preprocess function which makes
* it possible to be used in default theme implementations (non-overriden
* theme functions).
* Adds a default set of helper variables for variable processors and templates.
* This comes in before any other preprocess function which makes it possible to
* be used in default theme implementations (non-overriden theme functions).
*/
function template_preprocess(&$variables, $hook) {
global $user;
......@@ -1804,6 +1874,9 @@ function template_preprocess(&$variables, $hook) {
// Tell all templates where they are located.
$variables['directory'] = path_to_theme();
// Initialize html class attribute for the current hook.
$variables['classes_array'] = array($hook);
// Set default variables that depend on the database.
$variables['is_admin'] = FALSE;
$variables['is_front'] = FALSE;
......@@ -1822,6 +1895,14 @@ function template_preprocess(&$variables, $hook) {
}
}
/**
* A default process function used to alter variables as late as possible.
*/
function template_process(&$variables, $hook) {
// Flatten out classes.
$variables['classes'] = implode(' ', $variables['classes_array']);
}
/**
* Process variables for page.tpl.php
*
......@@ -1916,26 +1997,10 @@ function template_preprocess_page(&$variables) {
// Compile a list of classes that are going to be applied to the body element.
// This allows advanced theming based on context (home page, node of certain type, etc.).
$body_classes = array();
// Add a class that tells us whether we're on the front page or not.
$body_classes[] = $variables['is_front'] ? 'front' : 'not-front';
$variables['classes_array'][] = $variables['is_front'] ? 'front' : 'not-front';
// Add a class that tells us whether the page is viewed by an authenticated user or not.
$body_classes[] = $variables['logged_in'] ? 'logged-in' : 'not-logged-in';
// If on an individual node page, add the node type to body classes.
if (isset($variables['node']) && $variables['node']->type) {
$body_classes[] = 'node-type-' . form_clean_id($variables['node']->type);
}
// Add information about the number of sidebars.
if ($variables['layout'] == 'both') {
$body_classes[] = 'two-sidebars';
}
elseif ($variables['layout'] == 'none') {
$body_classes[] = 'no-sidebars';
}
else {
$body_classes[] = 'one-sidebar sidebar-' . $variables['layout'];
}
$variables['classes_array'][] = $variables['logged_in'] ? 'logged-in' : 'not-logged-in';
// Populate the page template suggestions.
if ($suggestions = template_page_suggestions(arg())) {
......@@ -1947,13 +2012,25 @@ function template_preprocess_page(&$variables) {
// more specific data like node-12 or node-edit. To avoid illegal characters in
// the class, we're removing everything disallowed. We are not using 'a-z' as
// that might leave in certain international characters (e.g. German umlauts).
$body_classes[] = preg_replace('![^abcdefghijklmnopqrstuvwxyz0-9-_]+!s', '', form_clean_id(drupal_strtolower($suggestion)));
$variables['classes_array'][] = preg_replace('![^abcdefghijklmnopqrstuvwxyz0-9-_]+!s', '', form_clean_id(drupal_strtolower($suggestion)));
}
}
}
// Implode with spaces.
$variables['body_classes'] = implode(' ', $body_classes);
// If on an individual node page, add the node type to body classes.
if (isset($variables['node']) && $variables['node']->type) {
$variables['classes_array'][] = 'node-type-' . form_clean_id($variables['node']->type);
}
// Add information about the number of sidebars.
if ($variables['layout'] == 'both') {
$variables['classes_array'][] = 'two-sidebars';
}
elseif ($variables['layout'] == 'none') {
$variables['classes_array'][] = 'no-sidebars';
}
else {
$variables['classes_array'][] = 'one-sidebar sidebar-' . $variables['layout'];
}
}
/**
......@@ -1994,4 +2071,3 @@ function template_page_suggestions($args) {
return $suggestions;
}
......@@ -114,8 +114,11 @@ function theme_install_page($content) {
$variables['content'] = $content;
// Delay setting the message variable so it can be processed below.
$variables['show_messages'] = FALSE;
// The maintenance preprocess function is recycled here.
// Variable processors invoked manually since this function and theme_update_page()
// are exceptions in how it works within the theme system.
template_preprocess($variables, 'install_page');
template_preprocess_maintenance_page($variables);
template_process($variables, 'install_page');
// Special handling of error messages
$messages = drupal_set_message();
......@@ -167,8 +170,11 @@ function theme_update_page($content, $show_messages = TRUE) {
// Assign content and show message flag.
$variables['content'] = $content;
$variables['show_messages'] = $show_messages;
// The maintenance preprocess function is recycled here.
// Variable processors invoked manually since this function and theme_install_page()
// are exceptions in how it works within the theme system.
template_preprocess($variables, 'update_page');
template_preprocess_maintenance_page($variables);
template_process($variables, 'update_page');
// Special handling of warning messages.
$messages = drupal_set_message();
......@@ -270,21 +276,19 @@ function template_preprocess_maintenance_page(&$variables) {
$variables['closure'] = '';
// Compile a list of classes that are going to be applied to the body element.
$body_classes = array();
$body_classes[] = 'in-maintenance';
$variables['classes_array'][] = 'in-maintenance';
if (isset($variables['db_is_active']) && !$variables['db_is_active']) {
$body_classes[] = 'db-offline';
$variables['classes_array'][] = 'db-offline';
}
if ($variables['layout'] == 'both') {
$body_classes[] = 'two-sidebars';
$variables['classes_array'][] = 'two-sidebars';
}
elseif ($variables['layout'] == 'none') {
$body_classes[] = 'no-sidebars';
$variables['classes_array'][] = 'no-sidebars';
}
else {
$body_classes[] = 'one-sidebar sidebar-' . $variables['layout'];
$variables['classes_array'][] = 'one-sidebar sidebar-' . $variables['layout'];
}
$variables['body_classes'] = implode(' ', $body_classes);
// Dead databases will show error messages so supplying this template will
// allow themers to override the page and the content completely.
......
......@@ -787,7 +787,9 @@ function template_preprocess_block(&$variables) {
$variables['block']->content = drupal_render($variables['block']->content);
}
$variables['classes_array'][] = 'block-' . $variables['block']->module;
$variables['template_files'][] = 'block-' . $variables['block']->region;
$variables['template_files'][] = 'block-' . $variables['block']->module;
$variables['template_files'][] = 'block-' . $variables['block']->module . '-' . $variables['block']->delta;
}
\ No newline at end of file
}
......@@ -11,8 +11,17 @@
* - $block->module: Module that generated the block.
* - $block->delta: An ID for the block, unique within each module.
* - $block->region: The block region embedding the current block.
* - $classes: String of classes that can be used to style contextually through
* CSS. It can be manipulated through the variable $classes_array from
* preprocess functions. The default values can be one or more of the following:
* - block: The current template type, i.e., "theming hook".
* - block-[module]: The module generating the block. For example, the user module
* is responsible for handling the default user navigation block. In that case
* the class would be "block-user".
*
* Helper variables:
* - $classes_array: Array of html class attribute values. It is flattened
* into a string within the variable $classes.
* - $block_zebra: Outputs 'odd' and 'even' dependent on each block region.
* - $zebra: Same output as $block_zebra but independent of any block region.
* - $block_id: Counter dependent on each block region.
......@@ -23,9 +32,10 @@
*
* @see template_preprocess()
* @see template_preprocess_block()
* @see template_process()
*/
?>
<div id="block-<?php print $block->module . '-' . $block->delta; ?>" class="block block-<?php print $block->module ?>">
<div id="block-<?php print $block->module . '-' . $block->delta; ?>" class="<?php print $classes; ?>">
<?php if ($block->subject): ?>
<h2><?php print $block->subject ?></h2>
<?php endif;?>
......
......@@ -11,11 +11,29 @@
* - $author: Comment author. Can be link or plain text.
* - $date: Date and time of posting.
* - $comment: Full comment object.
* - $classes: String of classes that can be used to style contextually through
* CSS. It can be manipulated through the variable $classes_array from
* preprocess functions. The default values can be one or more of the following:
* - comment-folded: The current template type, i.e., "theming hook".
* - comment-by-anonymous: Comment by an unregistered user.
* - comment-by-node-author: Comment by the author of the parent node.
* The following applies only to viewers who are registered users:
* - comment-unpublished: An unpublished comment visible only to administrators.
* - comment-by-viewer: Comment by the user currently viewing the page.
* - comment-new: New comment since last the visit.
*
* These two variables are provided for context:
* - $comment: Full comment object.
* - $node: Node object the comments are attached to.
*
* Other variables:
* - $classes_array: Array of html class attribute values. It is flattened
* into a string within the variable $classes.
*
* @see template_preprocess_comment_folded()
* @see theme_comment_folded()
*/
?>
<div class="comment-folded">
<div class="<?php print $classes; ?>">
<span class="subject"><?php print $title . ' ' . $new; ?></span><span class="credit"><?php print t('by') . ' ' . $author; ?></span>
</div>
......@@ -8,6 +8,10 @@
* Available variables:
* - $content: All comments for a given page. Also contains comment form
* if enabled.
* - $classes: String of classes that can be used to style contextually through
* CSS. It can be manipulated through the variable $classes_array from
* preprocess functions. The default value has the following:
* - comment-wrapper: The current template type, i.e., "theming hook".
*
* The following variables are provided for contextual information.
* - $node: Node object the comments are attached to.
......@@ -18,14 +22,15 @@
* - COMMENT_MODE_FLAT_EXPANDED
* - COMMENT_MODE_THREADED_COLLAPSED
* - COMMENT_MODE_THREADED_EXPANDED
* - $display_order
* - COMMENT_ORDER_NEWEST_FIRST
* - COMMENT_ORDER_OLDEST_FIRST
*
* Other variables:
* - $classes_array: Array of html class attribute values. It is flattened
* into a string within the variable $classes.
*
* @see template_preprocess_comment_wrapper()
* @see theme_comment_wrapper()
*/
?>
<div id="comments">
<div id="comments" class="<?php print $classes; ?>">
<?php print $content; ?>
</div>
......@@ -128,7 +128,7 @@ function comment_theme() {
),
'comment_folded' => array(
'template' => 'comment-folded',
'arguments' => array('comment' => NULL),
'arguments' => array('comment' => NULL, 'node' => NULL),
),
'comment_flat_collapsed' => array(
'arguments' => array('comment' => NULL, 'node' => NULL),
......@@ -1833,7 +1833,7 @@ function theme_comment_view($comment, $node, $links = array(), $visible = TRUE)
$output .= theme('comment', $comment, $node, $links);
}
else {
$output .= theme('comment_folded', $comment);
$output .= theme('comment_folded', $comment, $node);
}
return $output;
......@@ -1865,6 +1865,25 @@ function template_preprocess_comment(&$variables) {
else {
$variables['status'] = ($comment->status == COMMENT_NOT_PUBLISHED) ? 'comment-unpublished' : 'comment-published';
}
// Gather comment classes.
if ($comment->uid === 0) {
$variables['classes_array'][] = 'comment-by-anonymous';
}
else {
// Published class is not needed. It is either 'comment-preview' or 'comment-unpublished'.
if ($variables['status'] != 'comment-published') {
$variables['classes_array'][] = $variables['status'];
}
if ($comment->uid === $variables['node']->uid) {
$variables['classes_array'][] = 'comment-by-node-author';
}
if ($comment->uid === $variables['user']->uid) {
$variables['classes_array'][] = 'comment-by-viewer';
}
if ($comment->new) {
$variables['classes_array'][] = 'comment-new';
}
}
}
/**
......@@ -1879,6 +1898,25 @@ function template_preprocess_comment_folded(&$variables) {
$variables['date'] = format_date($comment->timestamp);
$variables['new'] = $comment->new ? t('new') : '';
$variables['title'] = l($comment->subject, comment_node_url() . '/' . $comment->cid, array('fragment' => "comment-$comment->cid"));
// Gather comment classes.
if ($comment->uid === 0) {
$variables['classes_array'][] = 'comment-by-anonymous';
}
else {
if ($comment->status == COMMENT_NOT_PUBLISHED) {
$variables['classes_array'][] = 'comment-unpublished';
}
if ($comment->uid === $variables['node']->uid) {
$variables['classes_array'][] = 'comment-by-node-author';
}
if ($comment->uid === $variables['user']->uid) {
$variables['classes_array'][] = 'comment-by-viewer';
}
if ($comment->new) {
$variables['classes_array'][] = 'comment-new';
}
}
}
/**
......
......@@ -17,16 +17,33 @@
* comment-unpublished, comment-published or comment-preview.
* - $submitted: By line with date and time.