initial import from onelab svn codebase
[plewww.git] / includes / theme.inc
1 <?php
2 // $Id: theme.inc 144 2007-03-28 07:52:20Z thierry $
3
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  */
14
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  */
27
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;
34
35   // If $theme is already set, assume the others are set, too, and do nothing
36   if (isset($theme)) {
37     return;
38   }
39
40   drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
41   $themes = list_themes();
42
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', 'bluemarine');
46
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;
50
51   // Store the identifier for retrieving theme settings with.
52   $theme_key = $theme;
53
54   // If we're using a style, load its appropriate theme,
55   // which is stored in the style's description field.
56   // Also load the stylesheet using theme_add_style().
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     theme_add_style($themes[$theme]->filename);
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       theme_add_style($stylesheet);
69     }
70   }
71
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   }
84 }
85
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;
96
97   if ($refresh) {
98     unset($list);
99   }
100
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   }
110
111   return $list;
112 }
113
114 /**
115  * Provides a list of currently available theme engines
116  *
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;
124
125   if ($refresh) {
126     unset($list);
127   }
128
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   }
138
139   return $list;
140 }
141
142 /**
143  * Generate the themed representation of a Drupal object.
144  *
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.
150  *
151  * For example, to retrieve the HTML that is output by theme_page($output), a
152  * module should call theme('page', $output).
153  *
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   $args = func_get_args();
163   $function = array_shift($args);
164
165   if ($func = theme_get_function($function)) {
166     return call_user_func_array($func, $args);
167   }
168 }
169
170 /**
171  * Determine if a theme function exists, and if so return which one was found.
172  *
173  * @param $function
174  *   The name of the theme function to test.
175  * @return
176  *   The name of the theme function that should be used, or false if no function exists.
177  */
178 function theme_get_function($function) {
179   global $theme, $theme_engine;
180
181   // Because theme() is called a lot, calling init_theme() only to have it
182   // smartly return is a noticeable performance hit.   Don't do it.
183   if (!isset($theme)) {
184     init_theme();
185   }
186
187   if (($theme != '') && function_exists($theme .'_'. $function)) {
188     // call theme function
189     return $theme .'_'. $function;
190   }
191   elseif (($theme != '') && isset($theme_engine) && function_exists($theme_engine .'_'. $function)) {
192     // call engine function
193     return $theme_engine .'_'. $function;
194   }
195   elseif (function_exists('theme_'. $function)){
196     // call Drupal function
197     return 'theme_'. $function;
198   }
199   return false;
200 }
201
202 /**
203  * Return the path to the currently selected theme.
204  */
205 function path_to_theme() {
206   global $theme;
207
208   $themes = list_themes();
209
210   return dirname($themes[$theme]->filename);
211 }
212
213 /**
214  * Retrieve an associative array containing the settings for a theme.
215  *
216  * The final settings are arrived at by merging the default settings,
217  * the site-wide settings, and the settings defined for the specific theme.
218  * If no $key was specified, only the site-wide theme defaults are retrieved.
219  *
220  * The default values for each of settings are also defined in this function.
221  * To add new settings, add their default values here, and then add form elements
222  * to system_theme_settings() in system.module.
223  *
224  * @param $key
225  *  The template/style value for a given theme.
226  *
227  * @return
228  *   An associative array containing theme settings.
229  */
230 function theme_get_settings($key = NULL) {
231   $defaults = array(
232     'mission'                       =>  '',
233     'default_logo'                  =>  1,
234     'logo_path'                     =>  '',
235     'default_favicon'               =>  1,
236     'favicon_path'                  =>  '',
237     'toggle_logo'                   =>  1,
238     'toggle_favicon'                =>  1,
239     'toggle_name'                   =>  1,
240     'toggle_search'                 =>  1,
241     'toggle_slogan'                 =>  0,
242     'toggle_mission'                =>  1,
243     'toggle_node_user_picture'      =>  0,
244     'toggle_comment_user_picture'   =>  0,
245   );
246
247   if (module_exist('node')) {
248     foreach (node_get_types() as $type => $name) {
249       $defaults['toggle_node_info_' . $type] = 1;
250     }
251   }
252   $settings = array_merge($defaults, variable_get('theme_settings', array()));
253
254   if ($key) {
255     $settings = array_merge($settings, variable_get(str_replace('/', '_', 'theme_'. $key .'_settings'), array()));
256   }
257
258   // Only offer search box if search.module is enabled.
259   if (!module_exist('search') || !user_access('search content')) {
260     $settings['toggle_search'] = 0;
261   }
262
263   return $settings;
264 }
265
266 /**
267  * Retrieve a setting for the current theme.
268  * This function is designed for use from within themes & engines
269  * to determine theme settings made in the admin interface.
270  *
271  * Caches values for speed (use $refresh = TRUE to refresh cache)
272  *
273  * @param $setting_name
274  *  The name of the setting to be retrieved.
275  *
276  * @param $refresh
277  *  Whether to reload the cache of settings.
278  *
279  * @return
280  *   The value of the requested setting, NULL if the setting does not exist.
281  */
282 function theme_get_setting($setting_name, $refresh = FALSE) {
283   global $theme_key;
284   static $settings;
285
286   if (empty($settings) || $refresh) {
287     $settings = theme_get_settings($theme_key);
288
289     $themes = list_themes();
290     $theme_object = $themes[$theme_key];
291
292     if ($settings['mission'] == '') {
293       $settings['mission'] = variable_get('site_mission', '');
294     }
295
296     if (!$settings['toggle_mission']) {
297       $settings['mission'] = '';
298     }
299
300     if ($settings['toggle_logo']) {
301       if ($settings['default_logo']) {
302         $settings['logo'] = base_path() . dirname($theme_object->filename) .'/logo.png';
303       }
304       elseif ($settings['logo_path']) {
305         $settings['logo'] = base_path() . $settings['logo_path'];
306       }
307     }
308
309     if ($settings['toggle_favicon']) {
310       if ($settings['default_favicon']) {
311         if (file_exists($favicon = dirname($theme_object->filename) .'/favicon.ico')) {
312           $settings['favicon'] = base_path() . $favicon;
313         }
314         else {
315           $settings['favicon'] = base_path() . 'misc/favicon.ico';
316         }
317       }
318       elseif ($settings['favicon_path']) {
319         $settings['favicon'] = base_path() . $settings['favicon_path'];
320       }
321     }
322   }
323
324   return isset($settings[$setting_name]) ? $settings[$setting_name] : NULL;
325 }
326
327 /**
328  * Add a theme stylesheet to be included later. This is handled separately from
329  * drupal_set_html_head() to enforce the correct CSS cascading order.
330  */
331 function theme_add_style($path = '', $media = 'all') {
332   static $styles = array();
333
334   if ($path && !isset($styles["$media:$path"])) {
335     $style = new stdClass();
336     $style->path = base_path() . $path;
337     $style->media = $media;
338     $styles["$media:$path"] = $style;
339   }
340   return $styles;
341 }
342
343 /**
344  * Return the HTML for a theme's stylesheets.
345  */
346 function theme_get_styles() {
347   $output = '';
348   foreach (theme_add_style() as $style) {
349     $output .= theme('stylesheet_import', $style->path, $style->media);
350   }
351   return $output;
352 }
353
354 /**
355  * @defgroup themeable Themeable functions
356  * @{
357  * Functions that display HTML, and which can be customized by themes.
358  *
359  * All functions that produce HTML for display should be themeable. This means
360  * that they should be named with the theme_ prefix, and invoked using theme()
361  * rather than being called directly. This allows themes to override the display
362  * of any Drupal object.
363  *
364  * The theme system is described and defined in theme.inc.
365  */
366
367 /**
368  * Format a dynamic text string for emphasized display in a placeholder.
369  *
370  * E.g. t('Added term %term', array('%term' => theme('placeholder', $term)))
371  *
372  * @param $text
373  *   The text to format (plain-text).
374  * @return
375  *   The formatted text (html).
376  */
377 function theme_placeholder($text) {
378   return '<em>'. check_plain($text) .'</em>';
379 }
380
381 /**
382  * Return an entire Drupal page displaying the supplied content.
383  *
384  * @param $content
385  *   A string to display in the main content area of the page.
386  * @return
387  *   A string containing the entire HTML page.
388  */
389 function theme_page($content) {
390   $output = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
391   $output .= '<html xmlns="http://www.w3.org/1999/xhtml">';
392   $output .= '<head>';
393   $output .= ' <title>'. (drupal_get_title() ? strip_tags(drupal_get_title()) : variable_get('site_name', 'drupal')) .'</title>';
394   $output .= drupal_get_html_head();
395   $output .= theme_get_styles();
396
397   $output .= ' </head>';
398   $output .= ' <body style="background-color: #fff; color: #000;">';
399   $output .= '<table border="0" cellspacing="4" cellpadding="4"><tr><td style="vertical-align: top; width: 170px;">';
400
401   $output .= theme('blocks', 'all');
402   $output .= '</td><td style="vertical-align: top;">';
403
404   $output .= theme('breadcrumb', drupal_get_breadcrumb());
405   $output .= '<h1>' . drupal_get_title() . '</h1>';
406
407   if ($tabs = theme('menu_local_tasks')) {
408    $output .= $tabs;
409   }
410
411   $output .= theme('help');
412
413   $output .= theme('status_messages');
414
415   $output .= "\n<!-- begin content -->\n";
416   $output .= $content;
417   $output .= "\n<!-- end content -->\n";
418
419   $output .= '</td></tr></table>';
420   $output .= theme_closure();
421   $output .= '</body></html>';
422
423   return $output;
424 }
425
426 function theme_maintenance_page($content, $messages = TRUE, $partial = FALSE) {
427   drupal_set_header('Content-Type: text/html; charset=utf-8');
428   theme('add_style', 'misc/maintenance.css');
429   drupal_set_html_head('<link rel="shortcut icon" href="'. base_path() .'misc/favicon.ico" type="image/x-icon" />');
430
431   $output = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
432   $output .= '<html xmlns="http://www.w3.org/1999/xhtml">';
433   $output .= '<head>';
434   $output .= ' <title>'. drupal_get_title() .'</title>';
435   $output .= drupal_get_html_head();
436   $output .= theme_get_styles();
437   $output .= '</head>';
438   $output .= '<body>';
439   $output .= '<h1>' . drupal_get_title() . '</h1>';
440
441   if ($messages) {
442     $output .= theme('status_messages');
443   }
444
445   $output .= "\n<!-- begin content -->\n";
446   $output .= $content;
447   $output .= "\n<!-- end content -->\n";
448
449   if (!$partial) {
450     $output .= '</body></html>';
451   }
452
453   return $output;
454 }
455
456 /**
457  * Returns themed set of status and/or error messages.  The messages are grouped
458  * by type.
459  *
460  * @return
461  *   A string containing the messages.
462  */
463 function theme_status_messages() {
464   if ($data = drupal_get_messages()) {
465     $output = '';
466     foreach ($data as $type => $messages) {
467       $output .= "<div class=\"messages $type\">\n";
468       if (count($messages) > 1) {
469         $output .= " <ul>\n";
470         foreach($messages as $message) {
471           $output .= '  <li>'. $message ."</li>\n";
472         }
473         $output .= " </ul>\n";
474       }
475       else {
476         $output .= $messages[0];
477       }
478       $output .= "</div>\n";
479     }
480
481     return $output;
482   }
483 }
484
485 /**
486  * Return a themed set of links.
487  *
488  * @param $links
489  *   An array of links to be themed.
490  * @param $delimiter
491  *   A string used to separate the links.
492  * @return
493  *   A string containing the themed links.
494  */
495 function theme_links($links, $delimiter = ' | ') {
496   if (!is_array($links)) {
497     return '';
498   }
499   return implode($delimiter, $links);
500 }
501
502 /**
503  * Return a themed image.
504  *
505  * @param $path
506  *   The path of the image file.
507  * @param $alt
508  *   The alternative text for text-based browsers.
509  * @param $title
510  *   The title text is displayed when the image is hovered in some popular browsers.
511  * @param $attributes
512  *   Associative array of attributes to be placed in the img tag.
513  * @param $getsize
514  *   If set to true, the image's dimension are fetched and added as width/height attributes.
515  * @return
516  *   A string containing the image tag.
517  */
518 function theme_image($path, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE) {
519   if (!$getsize || (is_file($path) && (list($width, $height, $type, $image_attributes) = @getimagesize($path)))) {
520     $attributes = drupal_attributes($attributes);
521     return '<img src="'. check_url(base_path() . $path) .'" alt="'. check_plain($alt) .'" title="'. check_plain($title) .'" '. $image_attributes . $attributes .' />';
522   }
523 }
524
525 /**
526  * Return a themed breadcrumb trail.
527  *
528  * @param $breadcrumb
529  *   An array containing the breadcrumb links.
530  * @return a string containing the breadcrumb output.
531  */
532 function theme_breadcrumb($breadcrumb) {
533   if (!empty($breadcrumb)) {
534     return '<div class="breadcrumb">'. implode(' Â» ', $breadcrumb) .'</div>';
535   }
536 }
537
538 /**
539  * Return a themed help message.
540  *
541  * @return a string containing the helptext for the current page.
542  */
543 function theme_help() {
544   if ($help = menu_get_active_help()) {
545     return '<div class="help">'. $help .'</div>';
546   }
547 }
548
549 /**
550  * Return a themed node.
551  *
552  * @param $node
553  *   An object providing all relevant information for displaying a node:
554  *   - $node->nid: The ID of the node.
555  *   - $node->type: The content type (story, blog, forum...).
556  *   - $node->title: The title of the node.
557  *   - $node->created: The creation date, as a UNIX timestamp.
558  *   - $node->teaser: A shortened version of the node body.
559  *   - $node->body: The entire node contents.
560  *   - $node->changed: The last modification date, as a UNIX timestamp.
561  *   - $node->uid: The ID of the author.
562  *   - $node->username: The username of the author.
563  * @param $teaser
564  *   Whether to display the teaser only, as on the main page.
565  * @param $page
566  *   Whether to display the node as a standalone page. If TRUE, do not display
567  *   the title because it will be provided by the menu system.
568  * @return
569  *   A string containing the node output.
570  */
571 function theme_node($node, $teaser = FALSE, $page = FALSE) {
572   if (!$node->status) {
573     $output  = '<div class="node-unpublished">';
574   }
575
576   if (module_exist('taxonomy')) {
577     $terms = taxonomy_link('taxonomy terms', $node);
578   }
579
580   if ($page == 0) {
581     $output .= t('%title by %name', array('%title' => '<h2 class="title">'. check_plain($node->title) .'</h2>', '%name' => theme('username', $node)));
582   }
583   else {
584     $output .= t('by %name', array('%name' => theme('username', $node)));
585   }
586
587   if (count($terms)) {
588     $output .= ' <small>('. theme('links', $terms) .')</small><br />';
589   }
590
591   if ($teaser && $node->teaser) {
592     $output .= $node->teaser;
593   }
594   else {
595     $output .= $node->body;
596   }
597
598   if ($node->links) {
599     $output .= '<div class="links">'. theme('links', $node->links) .'</div>';
600   }
601
602   if (!$node->status) {
603     $output .= '</div>';
604   }
605
606   return $output;
607 }
608
609 /**
610  * Return a themed form element.
611  *
612  * @param $title the form element's title
613  * @param $value the form element's data
614  * @param $description the form element's description or explanation
615  * @param $id the form element's ID used by the &lt;label&gt; tag
616  * @param $required a boolean to indicate whether this is a required field or not
617  * @param $error a string with an error message filed against this form element
618  *
619  * @return a string representing the form element
620  */
621 function theme_form_element($title, $value, $description = NULL, $id = NULL, $required = FALSE, $error = FALSE) {
622
623   $output  = '<div class="form-item">'."\n";
624   $required = $required ? '<span class="form-required" title="'. t('This field is required.') .'">*</span>' : '';
625
626   if ($title) {
627     if ($id) {
628       $output .= ' <label for="'. form_clean_id($id) .'">'. t('%title: %required', array('%title' => $title, '%required' => $required)) . "</label>\n";
629     }
630     else {
631       $output .= ' <label>'. t('%title: %required', array('%title' => $title, '%required' => $required)) . "</label>\n";
632     }
633   }
634
635   $output .= " $value\n";
636
637   if ($description) {
638     $output .= ' <div class="description">'. $description ."</div>\n";
639   }
640
641   $output .= "</div>\n";
642
643   return $output;
644 }
645
646 /**
647  * Return a themed submenu, typically displayed under the tabs.
648  *
649  * @param $links
650  *   An array of links.
651  */
652 function theme_submenu($links) {
653   return '<div class="submenu">'. implode(' | ', $links) .'</div>';
654 }
655
656 /**
657  * Return a themed table.
658  *
659  * @param $header
660  *   An array containing the table headers. Each element of the array can be
661  *   either a localized string or an associative array with the following keys:
662  *   - "data": The localized title of the table column.
663  *   - "field": The database field represented in the table column (required if
664  *     user is to be able to sort on this column).
665  *   - "sort": A default sort order for this column ("asc" or "desc").
666  *   - Any HTML attributes, such as "colspan", to apply to the column header cell.
667  * @param $rows
668  *   An array of table rows. Every row is an array of cells, or an associative
669  *   array with the following keys:
670  *   - "data": an array of cells
671  *   - Any HTML attributes, such as "class", to apply to the table row.
672  *
673  *   Each cell can be either a string or an associative array with the following keys:
674  *   - "data": The string to display in the table cell.
675  *   - Any HTML attributes, such as "colspan", to apply to the table cell.
676  *
677  *   Here's an example for $rows:
678  *   @verbatim
679  *   $rows = array(
680  *     // Simple row
681  *     array(
682  *       'Cell 1', 'Cell 2', 'Cell 3'
683  *     ),
684  *     // Row with attributes on the row and some of its cells.
685  *     array(
686  *       'data' => array('Cell 1', array('data' => 'Cell 2', 'colspan' => 2)), 'class' => 'funky'
687  *     )
688  *   );
689  *   @endverbatim
690  *
691  * @param $attributes
692  *   An array of HTML attributes to apply to the table tag.
693  * @param $caption
694  *   A localized string to use for the <caption> tag.
695  * @return
696  *   An HTML string representing the table.
697  */
698 function theme_table($header, $rows, $attributes = array(), $caption = NULL) {
699   $output = '<table'. drupal_attributes($attributes) .">\n";
700
701   if (isset($caption)) {
702     $output .= '<caption>'. $caption ."</caption>\n";
703   }
704
705   // Format the table header:
706   if (count($header)) {
707     $ts = tablesort_init($header);
708     $output .= ' <thead><tr>';
709     foreach ($header as $cell) {
710       $cell = tablesort_header($cell, $header, $ts);
711       $output .= _theme_table_cell($cell, 1);
712     }
713     $output .= " </tr></thead>\n";
714   }
715
716   // Format the table rows:
717   $output .= "<tbody>\n";
718   if (count($rows)) {
719     foreach ($rows as $number => $row) {
720       $attributes = array();
721
722       // Check if we're dealing with a simple or complex row
723       if (isset($row['data'])) {
724         foreach ($row as $key => $value) {
725           if ($key == 'data') {
726             $cells = $value;
727           }
728           else {
729             $attributes[$key] = $value;
730           }
731         }
732       }
733       else {
734         $cells = $row;
735       }
736
737       // Add odd/even class
738       $class = ($number % 2 == 1) ? 'even': 'odd';
739       if (isset($attributes['class'])) {
740         $attributes['class'] .= ' '. $class;
741       }
742       else {
743         $attributes['class'] = $class;
744       }
745
746       // Build row
747       $output .= ' <tr'. drupal_attributes($attributes) .'>';
748       $i = 0;
749       foreach ($cells as $cell) {
750         $cell = tablesort_cell($cell, $header, $ts, $i++);
751         $output .= _theme_table_cell($cell, 0);
752       }
753       $output .= " </tr>\n";
754     }
755   }
756
757   $output .= "</tbody></table>\n";
758   return $output;
759 }
760
761 /**
762  * Return a themed sort icon.
763  *
764  * @param $style
765  *   Set to either asc or desc.  This sets which icon to show.
766  * @return
767  *   A themed sort icon.
768  */
769 function theme_tablesort_indicator($style) {
770   if ($style == "asc"){
771     return theme('image', 'misc/arrow-asc.png', t('sort icon'), t('sort ascending'));
772   }
773   else {
774     return theme('image', 'misc/arrow-desc.png', t('sort icon'), t('sort descending'));
775   }
776 }
777
778 /**
779  * Return a themed box.
780  *
781  * @param $title
782  *   The subject of the box.
783  * @param $content
784  *   The content of the box.
785  * @param $region
786  *   The region in which the box is displayed.
787  * @return
788  *   A string containing the box output.
789  */
790 function theme_box($title, $content, $region = 'main') {
791   $output = '<h2 class="title">'. $title .'</h2><div>'. $content .'</div>';
792   return $output;
793 }
794
795 /**
796  * Return a themed block.
797  *
798  * You can style your blocks by defining .block (all blocks),
799  * .block-<i>module</i> (all blocks of module <i>module</i>), and
800  * \#block-<i>module</i>-<i>delta</i> (specific block of module <i>module</i>
801  * with delta <i>delta</i>) in your theme's CSS.
802  *
803  * @param $block
804  *   An object populated with fields from the "blocks" database table
805  *   ($block->module, $block->delta ...) and fields returned by
806  *   <i>module</i>_block('view') ($block->subject, $block->content, ...).
807  * @return
808  *   A string containing the block output.
809  */
810 function theme_block($block) {
811   $output  = "<div class=\"block block-$block->module\" id=\"block-$block->module-$block->delta\">\n";
812   $output .= " <h2 class=\"title\">$block->subject</h2>\n";
813   $output .= " <div class=\"content\">$block->content</div>\n";
814   $output .= "</div>\n";
815   return $output;
816 }
817
818 /**
819  * Return a themed marker, useful for marking new or updated
820  * content.
821  *
822  * @param $type
823  *   Number representing the marker type to display
824  * @see MARK_NEW, MARK_UPDATED, MARK_READ
825  * @return
826  *   A string containing the marker.
827  */
828 function theme_mark($type = MARK_NEW) {
829   global $user;
830   if ($user->uid) {
831     if ($type == MARK_NEW) {
832       return ' <span class="marker">'. t('new') .'</span>';
833     }
834     else if ($type == MARK_UPDATED) {
835       return ' <span class="marker">'. t('updated') .'</span>';
836     }
837   }
838 }
839
840 /**
841  * Import a stylesheet using @import.
842  *
843  * @param $path
844  *  The path to the stylesheet.
845  *
846  * @param $media
847  *  The media type to specify for the stylesheet
848  *
849  * @return
850  *  A string containing the HTML for the stylesheet import.
851  */
852 function theme_stylesheet_import($path, $media = 'all') {
853   return '<style type="text/css" media="'. $media .'">@import "'. $path .'";</style>';
854 }
855
856 /**
857  * Return a themed list of items.
858  *
859  * @param $items
860  *   An array of items to be displayed in the list.
861  * @param $title
862  *   The title of the list.
863  * @param $type
864  *   The type of list to return (e.g. "ul", "ol")
865  * @return
866  *   A string containing the list output.
867  */
868 function theme_item_list($items = array(), $title = NULL, $type = 'ul') {
869   $output = '<div class="item-list">';
870   if (isset($title)) {
871     $output .= '<h3>'. $title .'</h3>';
872   }
873
874   if (!empty($items)) {
875     $output .= "<$type>";
876     foreach ($items as $item) {
877       $output .= '<li>'. $item .'</li>';
878     }
879     $output .= "</$type>";
880   }
881   $output .= '</div>';
882   return $output;
883 }
884
885 /**
886  * Returns code that emits the 'more help'-link.
887  */
888 function theme_more_help_link($url) {
889   return '<div class="more-help-link">' . t('[<a href="%link">more help...</a>]', array('%link' => check_url($url))) . '</div>';
890 }
891
892 /**
893  * Return code that emits an XML icon.
894  */
895 function theme_xml_icon($url) {
896   if ($image = theme('image', 'misc/xml.png', t('XML feed'), t('XML feed'))) {
897     return '<a href="'. check_url($url) .'" class="xml-icon">'. $image. '</a>';
898   }
899 }
900
901 /**
902  * Return code that emits an feed icon.
903  */
904 function theme_feed_icon($url) {
905   if ($image = theme('image', 'misc/feed.png', t('Syndicate content'), t('Syndicate content'))) {
906     return '<a href="'. check_url($url) .'" class="feed-icon">'. $image. '</a>';
907   }
908 }
909
910 /**
911  * Execute hook_footer() which is run at the end of the page right before the
912  * close of the body tag.
913  *
914  * @param $main (optional)
915  *
916  * @return
917  *   A string containing the results of the hook_footer() calls.
918  */
919 function theme_closure($main = 0) {
920   $footer = module_invoke_all('footer', $main);
921   return implode("\n", $footer);
922 }
923
924 /**
925  * Return a set of blocks available for the current user.
926  *
927  * @param $region
928  *   Which set of blocks to retrieve.
929  * @return
930  *   A string containing the themed blocks for this region.
931  */
932 function theme_blocks($region) {
933   $output = '';
934
935   if ($list = block_list($region)) {
936     foreach ($list as $key => $block) {
937       // $key == <i>module</i>_<i>delta</i>
938       $output .= theme('block', $block);
939     }
940   }
941
942   // Add any content assigned to this region through drupal_set_content() calls.
943   $output .= drupal_get_content($region);
944
945   return $output;
946 }
947
948 /**
949  * Format a username.
950  *
951  * @param $object
952  *   The user object to format, usually returned from user_load().
953  * @return
954  *   A string containing an HTML link to the user's page if the passed object
955  *   suggests that this is a site user. Otherwise, only the username is returned.
956  */
957 function theme_username($object) {
958
959   if ($object->uid && $object->name) {
960     // Shorten the name when it is too long or it will break many tables.
961     if (drupal_strlen($object->name) > 20) {
962       $name = drupal_substr($object->name, 0, 15) .'...';
963     }
964     else {
965       $name = $object->name;
966     }
967
968     if (user_access('access user profiles')) {
969       $output = l($name, 'user/'. $object->uid, array('title' => t('View user profile.')));
970     }
971     else {
972       $output = check_plain($name);
973     }
974   }
975   else if ($object->name) {
976     // Sometimes modules display content composed by people who are
977     // not registered members of the site (e.g. mailing list or news
978     // aggregator modules). This clause enables modules to display
979     // the true author of the content.
980     if ($object->homepage) {
981       $output = l($object->name, $object->homepage);
982     }
983     else {
984       $output = check_plain($object->name);
985     }
986
987     $output .= ' ('. t('not verified') .')';
988   }
989   else {
990     $output = variable_get('anonymous', 'Anonymous');
991   }
992
993   return $output;
994 }
995
996 function theme_progress_bar($percent, $message) {
997   $output = '<div id="progress" class="progress">';
998   $output .= '<div class="percentage">'. $percent .'%</div>';
999   $output .= '<div class="status">'. $message .'</div>';
1000   $output .= '<div class="bar"><div class="filled" style="width: '. $percent .'%"></div></div>';
1001   $output .= '</div>';
1002
1003   return $output;
1004 }
1005
1006 /**
1007  * @} End of "defgroup themeable".
1008  */
1009
1010 function _theme_table_cell($cell, $header = 0) {
1011   $attributes = '';
1012
1013   if (is_array($cell)) {
1014     $data = $cell['data'];
1015     foreach ($cell as $key => $value) {
1016       if ($key != 'data')  {
1017         $attributes .= " $key=\"$value\"";
1018       }
1019     }
1020   }
1021   else {
1022     $data = $cell;
1023   }
1024
1025   if ($header) {
1026     $output = "<th$attributes>$data</th>";
1027   }
1028   else {
1029     $output = "<td$attributes>$data</td>";
1030   }
1031
1032   return $output;
1033 }
1034