Imported drupal-5.3
[drupal.git] / includes / theme.inc
blob17e1af5a333227207a0b4b6b53b5896cbc41684e
1 <?php
2 // $Id: theme.inc,v 1.337.2.2 2007/05/31 05:52:42 drumm Exp $
4 /**
5  * @file
6  * The theme system, which controls the output of Drupal.
7  *
8  * The theme system allows for nearly all output of the Drupal system to be
9  * customized by user themes.
10  *
11  * @see <a href="http://drupal.org/node/253">Theme system</a>
12  * @see themeable
13  */
15  /**
16  * @name Content markers
17  * @{
18  * Markers used by theme_mark() and node_mark() to designate content.
19  * @see theme_mark(), node_mark()
20  */
21 define('MARK_READ',    0);
22 define('MARK_NEW',     1);
23 define('MARK_UPDATED', 2);
24 /**
25  * @} End of "Content markers".
26  */
28 /**
29  * Initialize the theme system by loading the theme.
30  *
31  */
32 function init_theme() {
33   global $theme, $user, $custom_theme, $theme_engine, $theme_key;
35   // If $theme is already set, assume the others are set, too, and do nothing
36   if (isset($theme)) {
37     return;
38   }
40   drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
41   $themes = list_themes();
43   // Only select the user selected theme if it is available in the
44   // list of enabled themes.
45   $theme = $user->theme && $themes[$user->theme]->status ? $user->theme : variable_get('theme_default', 'garland');
47   // Allow modules to override the present theme... only select custom theme
48   // if it is available in the list of installed themes.
49   $theme = $custom_theme && $themes[$custom_theme] ? $custom_theme : $theme;
51   // Store the identifier for retrieving theme settings with.
52   $theme_key = $theme;
54   // If we're using a style, load its appropriate theme,
55   // which is stored in the style's description field.
56   // Also add the stylesheet using drupal_add_css().
57   // Otherwise, load the theme.
58   if (strpos($themes[$theme]->filename, '.css')) {
59     // File is a style; loads its CSS.
60     // Set theme to its template/theme
61     drupal_add_css($themes[$theme]->filename, 'theme');
62     $theme = basename(dirname($themes[$theme]->description));
63   }
64   else {
65     // File is a template/theme
66     // Load its CSS, if it exists
67     if (file_exists($stylesheet = dirname($themes[$theme]->filename) .'/style.css')) {
68       drupal_add_css($stylesheet, 'theme');
69     }
70   }
72   if (strpos($themes[$theme]->filename, '.theme')) {
73    // file is a theme; include it
74    include_once './' . $themes[$theme]->filename;
75   }
76   elseif (strpos($themes[$theme]->description, '.engine')) {
77     // file is a template; include its engine
78     include_once './' . $themes[$theme]->description;
79     $theme_engine = basename($themes[$theme]->description, '.engine');
80     if (function_exists($theme_engine .'_init')) {
81       call_user_func($theme_engine .'_init', $themes[$theme]);
82     }
83   }
86 /**
87  * Provides a list of currently available themes.
88  *
89  * @param $refresh
90  *   Whether to reload the list of themes from the database.
91  * @return
92  *   An array of the currently available themes.
93  */
94 function list_themes($refresh = FALSE) {
95   static $list;
97   if ($refresh) {
98     unset($list);
99   }
101   if (!$list) {
102     $list = array();
103     $result = db_query("SELECT * FROM {system} WHERE type = 'theme'");
104     while ($theme = db_fetch_object($result)) {
105       if (file_exists($theme->filename)) {
106         $list[$theme->name] = $theme;
107       }
108     }
109   }
111   return $list;
115  * Provides a list of currently available theme engines
117  * @param $refresh
118  *   Whether to reload the list of themes from the database.
119  * @return
120  *   An array of the currently available theme engines.
121  */
122 function list_theme_engines($refresh = FALSE) {
123   static $list;
125   if ($refresh) {
126     unset($list);
127   }
129   if (!$list) {
130     $list = array();
131     $result = db_query("SELECT * FROM {system} WHERE type = 'theme_engine' AND status = '1' ORDER BY name");
132     while ($engine = db_fetch_object($result)) {
133       if (file_exists($engine->filename)) {
134         $list[$engine->name] = $engine;
135       }
136     }
137   }
139   return $list;
143  * Generate the themed representation of a Drupal object.
145  * All requests for themed functions must go through this function. It examines
146  * the request and routes it to the appropriate theme function. If the current
147  * theme does not implement the requested function, then the current theme
148  * engine is checked. If neither the engine nor theme implement the requested
149  * function, then the base theme function is called.
151  * For example, to retrieve the HTML that is output by theme_page($output), a
152  * module should call theme('page', $output).
154  * @param $function
155  *   The name of the theme function to call.
156  * @param ...
157  *   Additional arguments to pass along to the theme function.
158  * @return
159  *   An HTML string that generates the themed output.
160  */
161 function theme() {
162   static $functions;
163   $args = func_get_args();
164   $function = array_shift($args);
166   if (!isset($functions[$function])) {
167     $functions[$function] = theme_get_function($function);
168   }
169   if ($functions[$function]) {
170     return call_user_func_array($functions[$function], $args);
171   }
175  * Determine if a theme function exists, and if so return which one was found.
177  * @param $function
178  *   The name of the theme function to test.
179  * @return
180  *   The name of the theme function that should be used, or FALSE if no function exists.
181  */
182 function theme_get_function($function) {
183   global $theme, $theme_engine;
185   // Because theme() is called a lot, calling init_theme() only to have it
186   // smartly return is a noticeable performance hit. Don't do it.
187   if (!isset($theme)) {
188     init_theme();
189   }
191   if (($theme != '') && function_exists($theme .'_'. $function)) {
192     // call theme function
193     return $theme .'_'. $function;
194   }
195   elseif (($theme != '') && isset($theme_engine) && function_exists($theme_engine .'_'. $function)) {
196     // call engine function
197     return $theme_engine .'_'. $function;
198   }
199   elseif (function_exists('theme_'. $function)){
200     // call Drupal function
201     return 'theme_'. $function;
202   }
203   return FALSE;
207  * Return the path to the currently selected theme.
208  */
209 function path_to_theme() {
210   global $theme;
212   if (!isset($theme)) {
213     init_theme();
214   }
216   $themes = list_themes();
218   return dirname($themes[$theme]->filename);
222  * Return the path to the currently selected engine.
223  */
224 function path_to_engine() {
225   global $theme, $theme_engine;
227   if (!isset($theme)) {
228     init_theme();
229   }
231   $engines = list_theme_engines();
233   return dirname($engines[$theme_engine]->filename);
237  * Retrieve an associative array containing the settings for a theme.
239  * The final settings are arrived at by merging the default settings,
240  * the site-wide settings, and the settings defined for the specific theme.
241  * If no $key was specified, only the site-wide theme defaults are retrieved.
243  * The default values for each of settings are also defined in this function.
244  * To add new settings, add their default values here, and then add form elements
245  * to system_theme_settings() in system.module.
247  * @param $key
248  *  The template/style value for a given theme.
250  * @return
251  *   An associative array containing theme settings.
252  */
253 function theme_get_settings($key = NULL) {
254   $defaults = array(
255     'mission'                       =>  '',
256     'default_logo'                  =>  1,
257     'logo_path'                     =>  '',
258     'default_favicon'               =>  1,
259     'favicon_path'                  =>  '',
260     'toggle_logo'                   =>  1,
261     'toggle_favicon'                =>  1,
262     'toggle_name'                   =>  1,
263     'toggle_search'                 =>  1,
264     'toggle_slogan'                 =>  0,
265     'toggle_mission'                =>  1,
266     'toggle_node_user_picture'      =>  0,
267     'toggle_comment_user_picture'   =>  0,
268   );
270   if (module_exists('node')) {
271     foreach (node_get_types() as $type => $name) {
272       $defaults['toggle_node_info_' . $type] = 1;
273     }
274   }
275   $settings = array_merge($defaults, variable_get('theme_settings', array()));
277   if ($key) {
278     $settings = array_merge($settings, variable_get(str_replace('/', '_', 'theme_'. $key .'_settings'), array()));
279   }
281   // Only offer search box if search.module is enabled.
282   if (!module_exists('search') || !user_access('search content')) {
283     $settings['toggle_search'] = 0;
284   }
286   return $settings;
290  * Retrieve a setting for the current theme.
291  * This function is designed for use from within themes & engines
292  * to determine theme settings made in the admin interface.
294  * Caches values for speed (use $refresh = TRUE to refresh cache)
296  * @param $setting_name
297  *  The name of the setting to be retrieved.
299  * @param $refresh
300  *  Whether to reload the cache of settings.
302  * @return
303  *   The value of the requested setting, NULL if the setting does not exist.
304  */
305 function theme_get_setting($setting_name, $refresh = FALSE) {
306   global $theme_key;
307   static $settings;
309   if (empty($settings) || $refresh) {
310     $settings = theme_get_settings($theme_key);
312     $themes = list_themes();
313     $theme_object = $themes[$theme_key];
315     if ($settings['mission'] == '') {
316       $settings['mission'] = variable_get('site_mission', '');
317     }
319     if (!$settings['toggle_mission']) {
320       $settings['mission'] = '';
321     }
323     if ($settings['toggle_logo']) {
324       if ($settings['default_logo']) {
325         $settings['logo'] = base_path() . dirname($theme_object->filename) .'/logo.png';
326       }
327       elseif ($settings['logo_path']) {
328         $settings['logo'] = base_path() . $settings['logo_path'];
329       }
330     }
332     if ($settings['toggle_favicon']) {
333       if ($settings['default_favicon']) {
334         if (file_exists($favicon = dirname($theme_object->filename) .'/favicon.ico')) {
335           $settings['favicon'] = base_path() . $favicon;
336         }
337         else {
338           $settings['favicon'] = base_path() . 'misc/favicon.ico';
339         }
340       }
341       elseif ($settings['favicon_path']) {
342         $settings['favicon'] = base_path() . $settings['favicon_path'];
343       }
344       else {
345         $settings['toggle_favicon'] = FALSE;
346       }
347     }
348   }
350   return isset($settings[$setting_name]) ? $settings[$setting_name] : NULL;
354  * @defgroup themeable Themeable functions
355  * @{
356  * Functions that display HTML, and which can be customized by themes.
358  * All functions that produce HTML for display should be themeable. This means
359  * that they should be named with the theme_ prefix, and invoked using theme()
360  * rather than being called directly. This allows themes to override the display
361  * of any Drupal object.
363  * The theme system is described and defined in theme.inc.
364  */
367  * Formats text for emphasized display in a placeholder inside a sentence.
368  * Used automatically by t().
370  * @param $text
371  *   The text to format (plain-text).
372  * @return
373  *   The formatted text (html).
374  */
375 function theme_placeholder($text) {
376   return '<em>'. check_plain($text) .'</em>';
380  * Return an entire Drupal page displaying the supplied content.
382  * @param $content
383  *   A string to display in the main content area of the page.
384  * @return
385  *   A string containing the entire HTML page.
386  */
387 function theme_page($content) {
388   // Get blocks before so that they can alter the header (JavaScript, Stylesheets etc.)
389   $blocks = theme('blocks', 'all');
391   $output = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
392   $output .= '<html xmlns="http://www.w3.org/1999/xhtml">';
393   $output .= '<head>';
394   $output .= ' <title>'. (drupal_get_title() ? strip_tags(drupal_get_title()) : variable_get('site_name', 'Drupal')) .'</title>';
395   $output .= drupal_get_html_head();
396   $output .= drupal_get_css();
397   $output .= drupal_get_js();
399   $output .= ' </head>';
400   $output .= ' <body style="background-color: #fff; color: #000;">';
401   $output .= '<table border="0" cellspacing="4" cellpadding="4"><tr><td style="vertical-align: top; width: 170px;">';
403   $output .= $blocks;
404   $output .= '</td><td style="vertical-align: top;">';
406   $output .= theme('breadcrumb', drupal_get_breadcrumb());
407   $output .= '<h1>' . drupal_get_title() . '</h1>';
409   if ($tabs = theme('menu_local_tasks')) {
410    $output .= $tabs;
411   }
413   $output .= theme('help');
415   $output .= theme('status_messages');
417   $output .= "\n<!-- begin content -->\n";
418   $output .= $content;
419   $output .= drupal_get_feeds();
420   $output .= "\n<!-- end content -->\n";
422   $output .= '</td></tr></table>';
423   $output .= theme('closure');
424   $output .= '</body></html>';
426   return $output;
429 function theme_maintenance_page($content, $messages = TRUE, $partial = FALSE) {
430   drupal_set_header('Content-Type: text/html; charset=utf-8');
431   drupal_set_html_head('<style type="text/css" media="all">@import "'. base_path() .'misc/maintenance.css";</style>');
432   drupal_set_html_head('<style type="text/css" media="all">@import "'. base_path() . drupal_get_path('module', 'system') .'/defaults.css";</style>');
433   drupal_set_html_head('<style type="text/css" media="all">@import "'. base_path() . drupal_get_path('module', 'system') .'/system.css";</style>');
434   drupal_set_html_head('<link rel="shortcut icon" href="'. base_path() .'misc/favicon.ico" type="image/x-icon" />');
436   $output = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
437   $output .= '<html xmlns="http://www.w3.org/1999/xhtml">';
438   $output .= '<head>';
439   $output .= ' <title>'. strip_tags(drupal_get_title()) .'</title>';
440   $output .= drupal_get_html_head();
441   $output .= drupal_get_js();
442   $output .= '</head>';
443   $output .= '<body>';
444   $output .= '<h1>' . drupal_get_title() . '</h1>';
446   if ($messages) {
447     $output .= theme('status_messages');
448   }
450   $output .= "\n<!-- begin content -->\n";
451   $output .= $content;
452   $output .= "\n<!-- end content -->\n";
454   if (!$partial) {
455     $output .= '</body></html>';
456   }
458   return $output;
461 function theme_install_page($content) {
462   drupal_set_header('Content-Type: text/html; charset=utf-8');
463   drupal_add_css('misc/maintenance.css', 'module', 'all', FALSE);
464   drupal_set_html_head('<link rel="shortcut icon" href="'. base_path() .'misc/favicon.ico" type="image/x-icon" />');
465   $output = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
466   $output .= '<html xmlns="http://www.w3.org/1999/xhtml">';
467   $output .= '<head>';
468   $output .= ' <title>'. strip_tags(drupal_get_title()) .'</title>';
469   $output .= drupal_get_html_head();
470   $output .= drupal_get_css();
471   $output .= drupal_get_js();
472   $output .= '</head>';
473   $output .= '<body>';
474   $output .= '<h1>' . drupal_get_title() . '</h1>';
476   $messages = drupal_set_message();
477   if (isset($messages['error'])) {
478     $title = count($messages['error']) > 1 ? st('The following errors must be resolved before you can continue the installation process') : st('The following error must be resolved before you can continue the installation process');
479     $output .= '<h3>' .$title. ':</h3>';
480     $output .= theme('status_messages', 'error');
481   }
483   if (isset($messages['status'])) {
484     $warnings = count($messages['status']) > 1 ? st('The following installation warnings should be carefully reviewed, but in most cases may be safely ignored') : st('The following installation warning should be carefully reviewed, but in most cases may be safely ignored');
485     $output .= '<h4>' .$title. ':</h4>';
486     $output .= theme('status_messages', 'status');
487   }
489   $output .= "\n<!-- begin content -->\n";
490   $output .= $content;
491   $output .= "\n<!-- end content -->\n";
493   $output .= '</body></html>';
495   return $output;
499  * Return a themed set of status and/or error messages. The messages are grouped
500  * by type.
502  * @param $display
503  *   (optional) Set to 'status' or 'error' to display only messages of that type.
505  * @return
506  *   A string containing the messages.
507  */
508 function theme_status_messages($display = NULL) {
509   $output = '';
510   foreach (drupal_get_messages($display) as $type => $messages) {
511     $output .= "<div class=\"messages $type\">\n";
512     if (count($messages) > 1) {
513       $output .= " <ul>\n";
514       foreach ($messages as $message) {
515         $output .= '  <li>'. $message ."</li>\n";
516       }
517       $output .= " </ul>\n";
518     }
519     else {
520       $output .= $messages[0];
521     }
522     $output .= "</div>\n";
523   }
524   return $output;
528  * Return a themed set of links.
530  * @param $links
531  *   A keyed array of links to be themed.
532  * @param $attributes
533  *   A keyed array of attributes
534  * @return
535  *   A string containing an unordered list of links.
536  */
537 function theme_links($links, $attributes = array('class' => 'links')) {
538   $output = '';
540   if (count($links) > 0) {
541     $output = '<ul'. drupal_attributes($attributes) .'>';
543     $num_links = count($links);
544     $i = 1;
546     foreach ($links as $key => $link) {
547       $class = '';
549       // Automatically add a class to each link and also to each LI
550       if (isset($link['attributes']) && isset($link['attributes']['class'])) {
551         $link['attributes']['class'] .= ' ' . $key;
552         $class = $key;
553       }
554       else {
555         $link['attributes']['class'] = $key;
556         $class = $key;
557       }
559       // Add first and last classes to the list of links to help out themers.
560       $extra_class = '';
561       if ($i == 1) {
562         $extra_class .= 'first ';
563       }
564       if ($i == $num_links) {
565         $extra_class .= 'last ';
566       }
567       $output .= '<li class="'. $extra_class . $class .'">';
569       // Is the title HTML?
570       $html = isset($link['html']) && $link['html'];
572       // Initialize fragment and query variables.
573       $link['query'] = isset($link['query']) ? $link['query'] : NULL;
574       $link['fragment'] = isset($link['fragment']) ? $link['fragment'] : NULL;
576       if (isset($link['href'])) {
577         $output .= l($link['title'], $link['href'], $link['attributes'], $link['query'], $link['fragment'], FALSE, $html);
578       }
579       else if ($link['title']) {
580         //Some links are actually not links, but we wrap these in <span> for adding title and class attributes
581         if (!$html) {
582           $link['title'] = check_plain($link['title']);
583         }
584         $output .= '<span'. drupal_attributes($link['attributes']) .'>'. $link['title'] .'</span>';
585       }
587       $i++;
588       $output .= "</li>\n";
589     }
591     $output .= '</ul>';
592   }
594   return $output;
598  * Return a themed image.
600  * @param $path
601  *   Either the path of the image file (relative to base_path()) or a full URL.
602  * @param $alt
603  *   The alternative text for text-based browsers.
604  * @param $title
605  *   The title text is displayed when the image is hovered in some popular browsers.
606  * @param $attributes
607  *   Associative array of attributes to be placed in the img tag.
608  * @param $getsize
609  *   If set to TRUE, the image's dimension are fetched and added as width/height attributes.
610  * @return
611  *   A string containing the image tag.
612  */
613 function theme_image($path, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE) {
614   if (!$getsize || (is_file($path) && (list($width, $height, $type, $image_attributes) = @getimagesize($path)))) {
615     $attributes = drupal_attributes($attributes);
616     $url = (url($path) == $path) ? $path : (base_path() . $path);
617     return '<img src="'. check_url($url) .'" alt="'. check_plain($alt) .'" title="'. check_plain($title) .'" '. $image_attributes . $attributes .' />';
618   }
622  * Return a themed breadcrumb trail.
624  * @param $breadcrumb
625  *   An array containing the breadcrumb links.
626  * @return a string containing the breadcrumb output.
627  */
628 function theme_breadcrumb($breadcrumb) {
629   if (!empty($breadcrumb)) {
630     return '<div class="breadcrumb">'. implode(' Â» ', $breadcrumb) .'</div>';
631   }
635  * Return a themed help message.
637  * @return a string containing the helptext for the current page.
638  */
639 function theme_help() {
640   if ($help = menu_get_active_help()) {
641     return '<div class="help">'. $help .'</div>';
642   }
646  * Return a themed node.
648  * @param $node
649  *   An object providing all relevant information for displaying a node:
650  *   - $node->nid: The ID of the node.
651  *   - $node->type: The content type (story, blog, forum...).
652  *   - $node->title: The title of the node.
653  *   - $node->created: The creation date, as a UNIX timestamp.
654  *   - $node->teaser: A shortened version of the node body.
655  *   - $node->body: The entire node contents.
656  *   - $node->changed: The last modification date, as a UNIX timestamp.
657  *   - $node->uid: The ID of the author.
658  *   - $node->username: The username of the author.
659  * @param $teaser
660  *   Whether to display the teaser only, as on the main page.
661  * @param $page
662  *   Whether to display the node as a standalone page. If TRUE, do not display
663  *   the title because it will be provided by the menu system.
664  * @return
665  *   A string containing the node output.
666  */
667 function theme_node($node, $teaser = FALSE, $page = FALSE) {
668   if (!$node->status) {
669     $output  = '<div class="node-unpublished">';
670   }
672   if (module_exists('taxonomy')) {
673     $terms = taxonomy_link('taxonomy terms', $node);
674   }
676   if ($page == 0) {
677     $output .= t('!title by !name', array('!title' => '<h2 class="title">'. check_plain($node->title) .'</h2>', '!name' => theme('username', $node)));
678   }
679   else {
680     $output .= t('by !name', array('!name' => theme('username', $node)));
681   }
683   if (count($terms)) {
684     $output .= ' <small>('. theme('links', $terms) .')</small><br />';
685   }
687   if ($teaser && $node->teaser) {
688     $output .= $node->teaser;
689   }
690   else {
691     $output .= $node->body;
692   }
694   if ($node->links) {
695     $output .= '<div class="links">'. theme('links', $node->links) .'</div>';
696   }
698   if (!$node->status) {
699     $output .= '</div>';
700   }
702   return $output;
706  * Return a themed submenu, typically displayed under the tabs.
708  * @param $links
709  *   An array of links.
710  */
711 function theme_submenu($links) {
712   return '<div class="submenu">'. implode(' | ', $links) .'</div>';
716  * Return a themed table.
718  * @param $header
719  *   An array containing the table headers. Each element of the array can be
720  *   either a localized string or an associative array with the following keys:
721  *   - "data": The localized title of the table column.
722  *   - "field": The database field represented in the table column (required if
723  *     user is to be able to sort on this column).
724  *   - "sort": A default sort order for this column ("asc" or "desc").
725  *   - Any HTML attributes, such as "colspan", to apply to the column header cell.
726  * @param $rows
727  *   An array of table rows. Every row is an array of cells, or an associative
728  *   array with the following keys:
729  *   - "data": an array of cells
730  *   - Any HTML attributes, such as "class", to apply to the table row.
732  *   Each cell can be either a string or an associative array with the following keys:
733  *   - "data": The string to display in the table cell.
734  *   - "header": Indicates this cell is a header.
735  *   - Any HTML attributes, such as "colspan", to apply to the table cell.
737  *   Here's an example for $rows:
738  *   @verbatim
739  *   $rows = array(
740  *     // Simple row
741  *     array(
742  *       'Cell 1', 'Cell 2', 'Cell 3'
743  *     ),
744  *     // Row with attributes on the row and some of its cells.
745  *     array(
746  *       'data' => array('Cell 1', array('data' => 'Cell 2', 'colspan' => 2)), 'class' => 'funky'
747  *     )
748  *   );
749  *   @endverbatim
751  * @param $attributes
752  *   An array of HTML attributes to apply to the table tag.
753  * @param $caption
754  *   A localized string to use for the <caption> tag.
755  * @return
756  *   An HTML string representing the table.
757  */
758 function theme_table($header, $rows, $attributes = array(), $caption = NULL) {
759   $output = '<table'. drupal_attributes($attributes) .">\n";
761   if (isset($caption)) {
762     $output .= '<caption>'. $caption ."</caption>\n";
763   }
765   // Format the table header:
766   if (count($header)) {
767     $ts = tablesort_init($header);
768     $output .= ' <thead><tr>';
769     foreach ($header as $cell) {
770       $cell = tablesort_header($cell, $header, $ts);
771       $output .= _theme_table_cell($cell, TRUE);
772     }
773     $output .= " </tr></thead>\n";
774   }
776   // Format the table rows:
777   $output .= "<tbody>\n";
778   if (count($rows)) {
779     $flip = array('even' => 'odd', 'odd' => 'even');
780     $class = 'even';
781     foreach ($rows as $number => $row) {
782       $attributes = array();
784       // Check if we're dealing with a simple or complex row
785       if (isset($row['data'])) {
786         foreach ($row as $key => $value) {
787           if ($key == 'data') {
788             $cells = $value;
789           }
790           else {
791             $attributes[$key] = $value;
792           }
793         }
794       }
795       else {
796         $cells = $row;
797       }
799       // Add odd/even class
800       $class = $flip[$class];
801       if (isset($attributes['class'])) {
802         $attributes['class'] .= ' '. $class;
803       }
804       else {
805         $attributes['class'] = $class;
806       }
808       // Build row
809       $output .= ' <tr'. drupal_attributes($attributes) .'>';
810       $i = 0;
811       foreach ($cells as $cell) {
812         $cell = tablesort_cell($cell, $header, $ts, $i++);
813         $output .= _theme_table_cell($cell);
814       }
815       $output .= " </tr>\n";
816     }
817   }
819   $output .= "</tbody></table>\n";
820   return $output;
824  * Returns a header cell for tables that have a select all functionality.
825  */
826 function theme_table_select_header_cell() {
827   drupal_add_js(array('tableSelect' => array('selectAll' => t('Select all rows in this table'), 'selectNone' => t('Deselect all rows in this table'))), 'setting');
828   drupal_add_js('misc/tableselect.js');
830   return array('class' => 'select-all');
834  * Return a themed sort icon.
836  * @param $style
837  *   Set to either asc or desc. This sets which icon to show.
838  * @return
839  *   A themed sort icon.
840  */
841 function theme_tablesort_indicator($style) {
842   if ($style == "asc"){
843     return theme('image', 'misc/arrow-asc.png', t('sort icon'), t('sort ascending'));
844   }
845   else {
846     return theme('image', 'misc/arrow-desc.png', t('sort icon'), t('sort descending'));
847   }
851  * Return a themed box.
853  * @param $title
854  *   The subject of the box.
855  * @param $content
856  *   The content of the box.
857  * @param $region
858  *   The region in which the box is displayed.
859  * @return
860  *   A string containing the box output.
861  */
862 function theme_box($title, $content, $region = 'main') {
863   $output = '<h2 class="title">'. $title .'</h2><div>'. $content .'</div>';
864   return $output;
868  * Return a themed block.
870  * You can style your blocks by defining .block (all blocks),
871  * .block-<i>module</i> (all blocks of module <i>module</i>), and
872  * \#block-<i>module</i>-<i>delta</i> (specific block of module <i>module</i>
873  * with delta <i>delta</i>) in your theme's CSS.
875  * @param $block
876  *   An object populated with fields from the "blocks" database table
877  *   ($block->module, $block->delta ...) and fields returned by
878  *   <i>module</i>_block('view') ($block->subject, $block->content, ...).
879  * @return
880  *   A string containing the block output.
881  */
882 function theme_block($block) {
883   $output  = "<div class=\"block block-$block->module\" id=\"block-$block->module-$block->delta\">\n";
884   $output .= " <h2 class=\"title\">$block->subject</h2>\n";
885   $output .= " <div class=\"content\">$block->content</div>\n";
886   $output .= "</div>\n";
887   return $output;
891  * Return a themed marker, useful for marking new or updated
892  * content.
894  * @param $type
895  *   Number representing the marker type to display
896  * @see MARK_NEW, MARK_UPDATED, MARK_READ
897  * @return
898  *   A string containing the marker.
899  */
900 function theme_mark($type = MARK_NEW) {
901   global $user;
902   if ($user->uid) {
903     if ($type == MARK_NEW) {
904       return ' <span class="marker">'. t('new') .'</span>';
905     }
906     else if ($type == MARK_UPDATED) {
907       return ' <span class="marker">'. t('updated') .'</span>';
908     }
909   }
913  * Return a themed list of items.
915  * @param $items
916  *   An array of items to be displayed in the list. If an item is a string,
917  *   then it is used as is. If an item is an array, then the "data" element of
918  *   the array is used as the contents of the list item. If an item is an array
919  *   with a "children" element, those children are displayed in a nested list.
920  *   All other elements are treated as attributes of the list item element.
921  * @param $title
922  *   The title of the list.
923  * @param $attributes
924  *   The attributes applied to the list element.
925  * @param $type
926  *   The type of list to return (e.g. "ul", "ol")
927  * @return
928  *   A string containing the list output.
929  */
930 function theme_item_list($items = array(), $title = NULL, $type = 'ul', $attributes = NULL) {
931   $output = '<div class="item-list">';
932   if (isset($title)) {
933     $output .= '<h3>'. $title .'</h3>';
934   }
936   if (!empty($items)) {
937     $output .= "<$type" . drupal_attributes($attributes) . '>';
938     foreach ($items as $item) {
939       $attributes = array();
940       $children = array();
941       if (is_array($item)) {
942         foreach ($item as $key => $value) {
943           if ($key == 'data') {
944             $data = $value;
945           }
946           elseif ($key == 'children') {
947             $children = $value;
948           }
949           else {
950             $attributes[$key] = $value;
951           }
952         }
953       }
954       else {
955         $data = $item;
956       }
957       if (count($children) > 0) {
958         $data .= theme_item_list($children, NULL, $type, $attributes); // Render nested list
959       }
960       $output .= '<li' . drupal_attributes($attributes) . '>'. $data .'</li>';
961     }
962     $output .= "</$type>";
963   }
964   $output .= '</div>';
965   return $output;
969  * Returns code that emits the 'more help'-link.
970  */
971 function theme_more_help_link($url) {
972   return '<div class="more-help-link">' . t('[<a href="@link">more help...</a>]', array('@link' => check_url($url))) . '</div>';
976  * Return code that emits an XML icon.
977  */
978 function theme_xml_icon($url) {
979   if ($image = theme('image', 'misc/xml.png', t('XML feed'), t('XML feed'))) {
980     return '<a href="'. check_url($url) .'" class="xml-icon">'. $image. '</a>';
981   }
985  * Return code that emits an feed icon.
986  */
987 function theme_feed_icon($url) {
988   if ($image = theme('image', 'misc/feed.png', t('Syndicate content'), t('Syndicate content'))) {
989     return '<a href="'. check_url($url) .'" class="feed-icon">'. $image. '</a>';
990   }
994  * Execute hook_footer() which is run at the end of the page right before the
995  * close of the body tag.
997  * @param $main (optional)
998  *   Whether the current page is the front page of the site.
999  * @return
1000  *   A string containing the results of the hook_footer() calls.
1001  */
1002 function theme_closure($main = 0) {
1003   $footer = module_invoke_all('footer', $main);
1004   return implode("\n", $footer) . drupal_get_js('footer');
1008  * Return a set of blocks available for the current user.
1010  * @param $region
1011  *   Which set of blocks to retrieve.
1012  * @return
1013  *   A string containing the themed blocks for this region.
1014  */
1015 function theme_blocks($region) {
1016   $output = '';
1018   if ($list = block_list($region)) {
1019     foreach ($list as $key => $block) {
1020       // $key == <i>module</i>_<i>delta</i>
1021       $output .= theme('block', $block);
1022     }
1023   }
1025   // Add any content assigned to this region through drupal_set_content() calls.
1026   $output .= drupal_get_content($region);
1028   return $output;
1032  * Format a username.
1034  * @param $object
1035  *   The user object to format, usually returned from user_load().
1036  * @return
1037  *   A string containing an HTML link to the user's page if the passed object
1038  *   suggests that this is a site user. Otherwise, only the username is returned.
1039  */
1040 function theme_username($object) {
1042   if ($object->uid && $object->name) {
1043     // Shorten the name when it is too long or it will break many tables.
1044     if (drupal_strlen($object->name) > 20) {
1045       $name = drupal_substr($object->name, 0, 15) .'...';
1046     }
1047     else {
1048       $name = $object->name;
1049     }
1051     if (user_access('access user profiles')) {
1052       $output = l($name, 'user/'. $object->uid, array('title' => t('View user profile.')));
1053     }
1054     else {
1055       $output = check_plain($name);
1056     }
1057   }
1058   else if ($object->name) {
1059     // Sometimes modules display content composed by people who are
1060     // not registered members of the site (e.g. mailing list or news
1061     // aggregator modules). This clause enables modules to display
1062     // the true author of the content.
1063     if ($object->homepage) {
1064       $output = l($object->name, $object->homepage);
1065     }
1066     else {
1067       $output = check_plain($object->name);
1068     }
1070     $output .= ' ('. t('not verified') .')';
1071   }
1072   else {
1073     $output = variable_get('anonymous', t('Anonymous'));
1074   }
1076   return $output;
1079 function theme_progress_bar($percent, $message) {
1080   $output = '<div id="progress" class="progress">';
1081   $output .= '<div class="percentage">'. $percent .'%</div>';
1082   $output .= '<div class="status">'. $message .'</div>';
1083   $output .= '<div class="bar"><div class="filled" style="width: '. $percent .'%"></div></div>';
1084   $output .= '</div>';
1086   return $output;
1090  * @} End of "defgroup themeable".
1091  */
1093 function _theme_table_cell($cell, $header = FALSE) {
1094   $attributes = '';
1096   if (is_array($cell)) {
1097     $data = $cell['data'];
1098     $header |= isset($cell['header']);
1099     unset($cell['data']);
1100     unset($cell['header']);
1101     $attributes = drupal_attributes($cell);
1102   }
1103   else {
1104     $data = $cell;
1105   }
1107   if ($header) {
1108     $output = "<th$attributes>$data</th>";
1109   }
1110   else {
1111     $output = "<td$attributes>$data</td>";
1112   }
1114   return $output;