theme.inc 29.6 KB
Newer Older
Dries's avatar
   
Dries committed
1
<?php
2
// $Id$
Dries's avatar
   
Dries committed
3

4
/**
Dries's avatar
   
Dries committed
5
 * @file
6
 * The theme system, which controls the output of Drupal.
Dries's avatar
   
Dries committed
7
8
9
10
 *
 * The theme system allows for nearly all output of the Drupal system to be
 * customized by user themes.
 *
Dries's avatar
   
Dries committed
11
 * @see <a href="http://drupal.org/node/253">Theme system</a>
Dries's avatar
   
Dries committed
12
13
 * @see themeable
 */
Dries's avatar
   
Dries committed
14

15
16
17
18
19
20
21
22
23
24
25
26
27
 /**
 * @name Content markers
 * @{
 * Markers used by theme_mark() and node_mark() to designate content.
 * @see theme_mark(), node_mark()
 */
define('MARK_READ',    0);
define('MARK_NEW',     1);
define('MARK_UPDATED', 2);
/**
 * @} End of "Content markers".
 */

Dries's avatar
   
Dries committed
28
/**
Dries's avatar
   
Dries committed
29
 * Initialize the theme system by loading the theme.
Dries's avatar
   
Dries committed
30
31
 *
 */
Dries's avatar
   
Dries committed
32
function init_theme() {
33
34
35
36
37
38
  global $theme, $user, $custom_theme, $theme_engine, $theme_key;

  // If $theme is already set, assume the others are set, too, and do nothing
  if (isset($theme)) {
    return;
  }
Dries's avatar
   
Dries committed
39

40
  drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
Dries's avatar
   
Dries committed
41
42
  $themes = list_themes();

43
44
  // Only select the user selected theme if it is available in the
  // list of enabled themes.
Dries's avatar
   
Dries committed
45
  $theme = $user->theme && $themes[$user->theme]->status ? $user->theme : variable_get('theme_default', 'bluemarine');
Dries's avatar
   
Dries committed
46
47

  // Allow modules to override the present theme... only select custom theme
Dries's avatar
   
Dries committed
48
  // if it is available in the list of installed themes.
49
  $theme = $custom_theme && $themes[$custom_theme] ? $custom_theme : $theme;
Dries's avatar
   
Dries committed
50
51
52
53
54
55

  // Store the identifier for retrieving theme settings with.
  $theme_key = $theme;

  // If we're using a style, load its appropriate theme,
  // which is stored in the style's description field.
56
  // Also add the stylesheet using drupal_add_css().
Dries's avatar
   
Dries committed
57
58
  // Otherwise, load the theme.
  if (strpos($themes[$theme]->filename, '.css')) {
59
    // File is a style; loads its CSS.
Dries's avatar
   
Dries committed
60
    // Set theme to its template/theme
61
    drupal_add_css($themes[$theme]->filename, 'theme');
62
    $theme = basename(dirname($themes[$theme]->description));
Dries's avatar
   
Dries committed
63
64
65
  }
  else {
    // File is a template/theme
66
    // Load its CSS, if it exists
Dries's avatar
   
Dries committed
67
    if (file_exists($stylesheet = dirname($themes[$theme]->filename) .'/style.css')) {
68
      drupal_add_css($stylesheet, 'theme');
Dries's avatar
   
Dries committed
69
70
    }
  }
Dries's avatar
   
Dries committed
71

Dries's avatar
   
Dries committed
72
  if (strpos($themes[$theme]->filename, '.theme')) {
73
   // file is a theme; include it
74
   include_once './' . $themes[$theme]->filename;
Dries's avatar
   
Dries committed
75
76
77
  }
  elseif (strpos($themes[$theme]->description, '.engine')) {
    // file is a template; include its engine
78
    include_once './' . $themes[$theme]->description;
Dries's avatar
   
Dries committed
79
80
81
82
83
    $theme_engine = basename($themes[$theme]->description, '.engine');
    if (function_exists($theme_engine .'_init')) {
      call_user_func($theme_engine .'_init', $themes[$theme]);
    }
  }
Dries's avatar
   
Dries committed
84
85
86
}

/**
Dries's avatar
   
Dries committed
87
88
89
 * Provides a list of currently available themes.
 *
 * @param $refresh
90
91
92
 *   Whether to reload the list of themes from the database.
 * @return
 *   An array of the currently available themes.
Dries's avatar
   
Dries committed
93
 */
94
function list_themes($refresh = FALSE) {
Dries's avatar
   
Dries committed
95
96
97
98
99
100
101
102
  static $list;

  if ($refresh) {
    unset($list);
  }

  if (!$list) {
    $list = array();
103
    $result = db_query("SELECT * FROM {system} WHERE type = 'theme'");
Dries's avatar
   
Dries committed
104
105
106
107
108
109
110
111
112
113
    while ($theme = db_fetch_object($result)) {
      if (file_exists($theme->filename)) {
        $list[$theme->name] = $theme;
      }
    }
  }

  return $list;
}

Dries's avatar
   
Dries committed
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
 * Provides a list of currently available theme engines
 *
 * @param $refresh
 *   Whether to reload the list of themes from the database.
 * @return
 *   An array of the currently available theme engines.
 */
function list_theme_engines($refresh = FALSE) {
  static $list;

  if ($refresh) {
    unset($list);
  }

  if (!$list) {
    $list = array();
Dries's avatar
   
Dries committed
131
    $result = db_query("SELECT * FROM {system} WHERE type = 'theme_engine' AND status = '1' ORDER BY name");
Dries's avatar
   
Dries committed
132
133
134
135
136
137
138
139
140
141
    while ($engine = db_fetch_object($result)) {
      if (file_exists($engine->filename)) {
        $list[$engine->name] = $engine;
      }
    }
  }

  return $list;
}

Dries's avatar
   
Dries committed
142
/**
143
 * Generate the themed representation of a Drupal object.
Dries's avatar
   
Dries committed
144
145
146
 *
 * All requests for themed functions must go through this function. It examines
 * the request and routes it to the appropriate theme function. If the current
Dries's avatar
   
Dries committed
147
148
149
 * theme does not implement the requested function, then the current theme
 * engine is checked. If neither the engine nor theme implement the requested
 * function, then the base theme function is called.
150
151
152
 *
 * For example, to retrieve the HTML that is output by theme_page($output), a
 * module should call theme('page', $output).
Dries's avatar
   
Dries committed
153
 *
154
155
156
157
158
159
 * @param $function
 *   The name of the theme function to call.
 * @param ...
 *   Additional arguments to pass along to the theme function.
 * @return
 *   An HTML string that generates the themed output.
Dries's avatar
   
Dries committed
160
 */
Dries's avatar
   
Dries committed
161
function theme() {
162
163
164
165
166
167
168
169
170
171
172
173
174
175
  $args = func_get_args();
  $function = array_shift($args);

  if ($func = theme_get_function($function)) {
    return call_user_func_array($func, $args);
  }
}

/**
 * Determine if a theme function exists, and if so return which one was found.
 *
 * @param $function
 *   The name of the theme function to test.
 * @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) {
Dries's avatar
   
Dries committed
179
180
  global $theme, $theme_engine;

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
184
  if (!isset($theme)) {
    init_theme();
Dries's avatar
   
Dries committed
185
  }
Dries's avatar
   
Dries committed
186

Dries's avatar
   
Dries committed
187
188
  if (($theme != '') && function_exists($theme .'_'. $function)) {
    // call theme function
189
    return $theme .'_'. $function;
Dries's avatar
   
Dries committed
190
  }
Dries's avatar
   
Dries committed
191
192
  elseif (($theme != '') && isset($theme_engine) && function_exists($theme_engine .'_'. $function)) {
    // call engine function
193
    return $theme_engine .'_'. $function;
Dries's avatar
   
Dries committed
194
  }
195
  elseif (function_exists('theme_'. $function)){
Dries's avatar
   
Dries committed
196
    // call Drupal function
197
    return 'theme_'. $function;
Dries's avatar
   
Dries committed
198
  }
199
  return FALSE;
Dries's avatar
   
Dries committed
200
201
202
}

/**
203
 * Return the path to the currently selected theme.
Dries's avatar
   
Dries committed
204
 */
Dries's avatar
   
Dries committed
205
206
function path_to_theme() {
  global $theme;
Dries's avatar
   
Dries committed
207
208
209
210

  $themes = list_themes();

  return dirname($themes[$theme]->filename);
Dries's avatar
   
Dries committed
211
212
}

Dries's avatar
   
Dries committed
213
214
215
/**
 * Retrieve an associative array containing the settings for a theme.
 *
216
 * The final settings are arrived at by merging the default settings,
Dries's avatar
   
Dries committed
217
218
219
220
221
222
223
224
225
226
227
228
229
 * the site-wide settings, and the settings defined for the specific theme.
 * If no $key was specified, only the site-wide theme defaults are retrieved.
 *
 * The default values for each of settings are also defined in this function.
 * To add new settings, add their default values here, and then add form elements
 * to system_theme_settings() in system.module.
 *
 * @param $key
 *  The template/style value for a given theme.
 *
 * @return
 *   An associative array containing theme settings.
 */
230
function theme_get_settings($key = NULL) {
Dries's avatar
   
Dries committed
231
232
233
234
  $defaults = array(
    'mission'                       =>  '',
    'default_logo'                  =>  1,
    'logo_path'                     =>  '',
235
236
    'default_favicon'               =>  1,
    'favicon_path'                  =>  '',
Dries's avatar
   
Dries committed
237
    'toggle_logo'                   =>  1,
238
    'toggle_favicon'                =>  1,
Dries's avatar
   
Dries committed
239
240
241
242
243
244
245
246
    'toggle_name'                   =>  1,
    'toggle_search'                 =>  1,
    'toggle_slogan'                 =>  0,
    'toggle_mission'                =>  1,
    'toggle_node_user_picture'      =>  0,
    'toggle_comment_user_picture'   =>  0,
  );

247
  if (module_exist('node')) {
248
    foreach (node_get_types() as $type => $name) {
249
250
      $defaults['toggle_node_info_' . $type] = 1;
    }
Dries's avatar
   
Dries committed
251
252
253
254
255
256
257
  }
  $settings = array_merge($defaults, variable_get('theme_settings', array()));

  if ($key) {
    $settings = array_merge($settings, variable_get(str_replace('/', '_', 'theme_'. $key .'_settings'), array()));
  }

258
  // Only offer search box if search.module is enabled.
259
  if (!module_exist('search') || !user_access('search content')) {
260
261
262
    $settings['toggle_search'] = 0;
  }

Dries's avatar
   
Dries committed
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
  return $settings;
}

/**
 * Retrieve a setting for the current theme.
 * This function is designed for use from within themes & engines
 * to determine theme settings made in the admin interface.
 *
 * Caches values for speed (use $refresh = TRUE to refresh cache)
 *
 * @param $setting_name
 *  The name of the setting to be retrieved.
 *
 * @param $refresh
 *  Whether to reload the cache of settings.
 *
 * @return
 *   The value of the requested setting, NULL if the setting does not exist.
 */
282
function theme_get_setting($setting_name, $refresh = FALSE) {
283
  global $theme_key;
Dries's avatar
   
Dries committed
284
285
286
  static $settings;

  if (empty($settings) || $refresh) {
287
    $settings = theme_get_settings($theme_key);
Dries's avatar
   
Dries committed
288
289
290
291
292
293
294
295
296
297
298
299
300
301

    $themes = list_themes();
    $theme_object = $themes[$theme_key];

    if ($settings['mission'] == '') {
      $settings['mission'] = variable_get('site_mission', '');
    }

    if (!$settings['toggle_mission']) {
      $settings['mission'] = '';
    }

    if ($settings['toggle_logo']) {
      if ($settings['default_logo']) {
302
        $settings['logo'] = base_path() . dirname($theme_object->filename) .'/logo.png';
Dries's avatar
   
Dries committed
303
304
      }
      elseif ($settings['logo_path']) {
305
        $settings['logo'] = base_path() . $settings['logo_path'];
Dries's avatar
   
Dries committed
306
307
308
      }
    }

309
310
311
    if ($settings['toggle_favicon']) {
      if ($settings['default_favicon']) {
        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
      }
    }
Dries's avatar
   
Dries committed
322
323
324
325
326
  }

  return isset($settings[$setting_name]) ? $settings[$setting_name] : NULL;
}

327
328
329
/**
 * @defgroup themeable Themeable functions
 * @{
Dries's avatar
   
Dries committed
330
 * Functions that display HTML, and which can be customized by themes.
331
 *
332
333
334
335
 * All functions that produce HTML for display should be themeable. This means
 * that they should be named with the theme_ prefix, and invoked using theme()
 * rather than being called directly. This allows themes to override the display
 * of any Drupal object.
336
 *
Dries's avatar
   
Dries committed
337
 * The theme system is described and defined in theme.inc.
338
 */
339

340
/**
341
 * Format a dynamic text string for emphasized display in a placeholder.
342
343
344
345
346
347
348
349
350
351
352
 *
 * E.g. t('Added term %term', array('%term' => theme('placeholder', $term)))
 *
 * @param $text
 *   The text to format (plain-text).
 * @return
 *   The formatted text (html).
 */
function theme_placeholder($text) {
  return '<em>'. check_plain($text) .'</em>';
}
Dries's avatar
   
Dries committed
353

Dries's avatar
   
Dries committed
354
/**
355
356
357
358
359
360
 * Return an entire Drupal page displaying the supplied content.
 *
 * @param $content
 *   A string to display in the main content area of the page.
 * @return
 *   A string containing the entire HTML page.
Dries's avatar
   
Dries committed
361
 */
362
function theme_page($content) {
Dries's avatar
   
Dries committed
363
  $output = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
364
365
  $output .= '<html xmlns="http://www.w3.org/1999/xhtml">';
  $output .= '<head>';
366
  $output .= ' <title>'. (drupal_get_title() ? strip_tags(drupal_get_title()) : variable_get('site_name', 'drupal')) .'</title>';
Dries's avatar
Dries committed
367
  $output .= drupal_get_html_head();
368
  $output .= drupal_get_css();
Dries's avatar
   
Dries committed
369

370
  $output .= ' </head>';
371
  $output .= ' <body style="background-color: #fff; color: #000;">';
372
  $output .= '<table border="0" cellspacing="4" cellpadding="4"><tr><td style="vertical-align: top; width: 170px;">';
Dries's avatar
   
Dries committed
373

374
375
  $output .= theme('blocks', 'all');
  $output .= '</td><td style="vertical-align: top;">';
Dries's avatar
   
Dries committed
376

377
378
  $output .= theme('breadcrumb', drupal_get_breadcrumb());
  $output .= '<h1>' . drupal_get_title() . '</h1>';
Dries's avatar
   
Dries committed
379
380
381
382
383

  if ($tabs = theme('menu_local_tasks')) {
   $output .= $tabs;
  }

384
  $output .= theme('help');
Dries's avatar
   
Dries committed
385

386
  $output .= theme('status_messages');
Dries's avatar
   
Dries committed
387

Dries's avatar
   
Dries committed
388
  $output .= "\n<!-- begin content -->\n";
Dries's avatar
   
Dries committed
389
  $output .= $content;
Dries's avatar
   
Dries committed
390
391
  $output .= "\n<!-- end content -->\n";

392
  $output .= '</td></tr></table>';
Dries's avatar
   
Dries committed
393
  $output .= theme_closure();
394
  $output .= '</body></html>';
Dries's avatar
   
Dries committed
395
396
397
398

  return $output;
}

399
function theme_maintenance_page($content, $messages = TRUE, $partial = FALSE) {
400
401
  drupal_set_header('Content-Type: text/html; charset=utf-8');
  theme('add_style', 'misc/maintenance.css');
402
  drupal_set_html_head('<link rel="shortcut icon" href="'. base_path() .'misc/favicon.ico" type="image/x-icon" />');
403

404
405
406
  $output = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
  $output .= '<html xmlns="http://www.w3.org/1999/xhtml">';
  $output .= '<head>';
407
  $output .= ' <title>'. strip_tags(drupal_get_title()) .'</title>';
408
  $output .= drupal_get_html_head();
409
  $output .= drupal_get_css();
410
411
412
413
  $output .= '</head>';
  $output .= '<body>';
  $output .= '<h1>' . drupal_get_title() . '</h1>';

414
  if ($messages) {
Steven Wittens's avatar
Steven Wittens committed
415
    $output .= theme('status_messages');
416
  }
417
418
419
420
421

  $output .= "\n<!-- begin content -->\n";
  $output .= $content;
  $output .= "\n<!-- end content -->\n";

422
  if (!$partial) {
Steven Wittens's avatar
Steven Wittens committed
423
    $output .= '</body></html>';
424
  }
425
426
427
428

  return $output;
}

429
430
431
432
433
434
435
436
437
function theme_install_page($content) {
  drupal_set_header('Content-Type: text/html; charset=utf-8');
  theme('add_style', 'misc/maintenance.css');
  drupal_set_html_head('<link rel="shortcut icon" href="'. base_path() .'misc/favicon.ico" type="image/x-icon" />');
  $output = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
  $output .= '<html xmlns="http://www.w3.org/1999/xhtml">';
  $output .= '<head>';
  $output .= ' <title>'. strip_tags(drupal_get_title()) .'</title>';
  $output .= drupal_get_html_head();
438
  $output .= drupal_get_css();
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
  $output .= '</head>';
  $output .= '<body>';
  $output .= '<h1>' . drupal_get_title() . '</h1>';

  $messages = drupal_set_message();
  if (isset($messages['error'])) {
    $errors = count($messages['error']) > 1 ? 'errors' : 'error';
    $output .= "<h3>The following $errors must be resolved before you can continue the installation process:</h3>";
    $output .= theme('status_messages', 'error');
  }

  $output .= "\n<!-- begin content -->\n";
  $output .= $content;
  $output .= "\n<!-- end content -->\n";

  if (isset($messages['status'])) {
    $warnings = count($messages['status']) > 1 ? 'warnings' : 'warning';
    $output .= "<h4>The following installation $warnings should be carefully reviewed, but in most cases may be safely ignored:</h4>";
    $output .= theme('status_messages', 'status');
  }

  $output .= '</body></html>';

  return $output;
}

Dries's avatar
   
Dries committed
465
/**
466
 * Return a themed set of status and/or error messages. The messages are grouped
Dries's avatar
   
Dries committed
467
468
 * by type.
 *
469
470
471
 * @param $display
 *   (optional) Set to 'status' or 'error' to display only messages of that type.
 *
472
473
 * @return
 *   A string containing the messages.
Dries's avatar
   
Dries committed
474
 */
475
476
477
478
479
480
481
482
function theme_status_messages($display = NULL) {
  $output = '';
  foreach (drupal_get_messages($display) as $type => $messages) {
    $output .= "<div class=\"messages $type\">\n";
    if (count($messages) > 1) {
      $output .= " <ul>\n";
      foreach($messages as $message) {
        $output .= '  <li>'. $message ."</li>\n";
Dries's avatar
   
Dries committed
483
      }
484
      $output .= " </ul>\n";
Dries's avatar
   
Dries committed
485
    }
486
487
488
489
    else {
      $output .= $messages[0];
    }
    $output .= "</div>\n";
Dries's avatar
   
Dries committed
490
  }
491
  return $output;
Dries's avatar
   
Dries committed
492
493
}

Dries's avatar
   
Dries committed
494
/**
495
496
497
 * Return a themed set of links.
 *
 * @param $links
498
 *   A keyed array of links to be themed.
499
500
501
502
 * @param $delimiter
 *   A string used to separate the links.
 * @return
 *   A string containing the themed links.
Dries's avatar
   
Dries committed
503
 */
504
function theme_links($links, $delimiter = ' | ') {
505
506
507
508
  $output = array();

  if (is_array($links)) {
    foreach ($links as $key => $link) {
509
510
511
      // Automatically add a class to each link and convert all _ to - for XHTML compliance
      if (isset($link['attributes']) && isset($link['attributes']['class'])) {
        $link['attributes']['class'] .= ' '. str_replace('_', '-', $key);
512
513
      }
      else {
514
        $link['attributes']['class'] = str_replace('_', '-', $key);
515
516
      }

517
518
      if ($link['href']) {
        $output[] = l($link['title'], $link['href'], $link['attributes'], $link['query'], $link['fragment']);
519
      }
520
      else if ($link['title']) {
521
        //Some links are actually not links, but we wrap these in <span> for adding title and class attributes
522
        $output[] = '<span'. drupal_attributes($link['attributes']) .'>'. check_plain($link['title']) .'</span>';
523
524
      }
    }
525
  }
526
527

  return implode($delimiter, $output);
Dries's avatar
   
Dries committed
528
}
Dries's avatar
   
Dries committed
529

Dries's avatar
   
Dries committed
530
/**
531
 * Return a themed image.
Dries's avatar
   
Dries committed
532
 *
Dries's avatar
   
Dries committed
533
534
535
536
537
538
 * @param $path
 *   The path of the image file.
 * @param $alt
 *   The alternative text for text-based browsers.
 * @param $title
 *   The title text is displayed when the image is hovered in some popular browsers.
539
540
 * @param $attributes
 *   Associative array of attributes to be placed in the img tag.
Steven Wittens's avatar
Steven Wittens committed
541
 * @param $getsize
542
 *   If set to TRUE, the image's dimension are fetched and added as width/height attributes.
543
 * @return
Dries's avatar
   
Dries committed
544
 *   A string containing the image tag.
Dries's avatar
   
Dries committed
545
 */
546
function theme_image($path, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE) {
547
  if (!$getsize || (is_file($path) && (list($width, $height, $type, $image_attributes) = @getimagesize($path)))) {
548
    $attributes = drupal_attributes($attributes);
549
    return '<img src="'. check_url(base_path() . $path) .'" alt="'. check_plain($alt) .'" title="'. check_plain($title) .'" '. $image_attributes . $attributes .' />';
Dries's avatar
   
Dries committed
550
  }
Dries's avatar
   
Dries committed
551
}
Dries's avatar
   
Dries committed
552

Dries's avatar
   
Dries committed
553
/**
554
 * Return a themed breadcrumb trail.
Dries's avatar
   
Dries committed
555
 *
556
557
558
 * @param $breadcrumb
 *   An array containing the breadcrumb links.
 * @return a string containing the breadcrumb output.
Dries's avatar
   
Dries committed
559
 */
Dries's avatar
   
Dries committed
560
function theme_breadcrumb($breadcrumb) {
561
562
563
  if (!empty($breadcrumb)) {
    return '<div class="breadcrumb">'. implode(' » ', $breadcrumb) .'</div>';
  }
Dries's avatar
   
Dries committed
564
}
Dries's avatar
   
Dries committed
565

566
567
568
569
570
571
572
573
574
575
576
/**
 * Return a themed help message.
 *
 * @return a string containing the helptext for the current page.
 */
function theme_help() {
  if ($help = menu_get_active_help()) {
    return '<div class="help">'. $help .'</div>';
  }
}

Dries's avatar
   
Dries committed
577
/**
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
 * Return a themed node.
 *
 * @param $node
 *   An object providing all relevant information for displaying a node:
 *   - $node->nid: The ID of the node.
 *   - $node->type: The content type (story, blog, forum...).
 *   - $node->title: The title of the node.
 *   - $node->created: The creation date, as a UNIX timestamp.
 *   - $node->teaser: A shortened version of the node body.
 *   - $node->body: The entire node contents.
 *   - $node->changed: The last modification date, as a UNIX timestamp.
 *   - $node->uid: The ID of the author.
 *   - $node->username: The username of the author.
 * @param $teaser
 *   Whether to display the teaser only, as on the main page.
 * @param $page
 *   Whether to display the node as a standalone page. If TRUE, do not display
 *   the title because it will be provided by the menu system.
 * @return
 *   A string containing the node output.
Dries's avatar
   
Dries committed
598
 */
599
function theme_node($node, $teaser = FALSE, $page = FALSE) {
600
  if (!$node->status) {
Dries's avatar
Dries committed
601
    $output  = '<div class="node-unpublished">';
602
603
  }

604
605
  if (module_exist('taxonomy')) {
    $terms = taxonomy_link('taxonomy terms', $node);
Dries's avatar
   
Dries committed
606
607
  }

Dries's avatar
   
Dries committed
608
  if ($page == 0) {
609
    $output .= t('%title by %name', array('%title' => '<h2 class="title">'. check_plain($node->title) .'</h2>', '%name' => theme('username', $node)));
Dries's avatar
   
Dries committed
610
611
  }
  else {
612
    $output .= t('by %name', array('%name' => theme('username', $node)));
Dries's avatar
   
Dries committed
613
  }
Dries's avatar
   
Dries committed
614
615

  if (count($terms)) {
616
    $output .= ' <small>('. theme('links', $terms) .')</small><br />';
Dries's avatar
   
Dries committed
617
618
  }

619
  if ($teaser && $node->teaser) {
Dries's avatar
   
Dries committed
620
621
622
623
    $output .= $node->teaser;
  }
  else {
    $output .= $node->body;
Dries's avatar
   
Dries committed
624
625
  }

626
627
  if ($node->links) {
    $output .= '<div class="links">'. theme('links', $node->links) .'</div>';
Dries's avatar
   
Dries committed
628
  }
Dries's avatar
   
Dries committed
629

630
631
632
633
  if (!$node->status) {
    $output .= '</div>';
  }

Dries's avatar
   
Dries committed
634
  return $output;
Dries's avatar
   
Dries committed
635
636
}

Dries's avatar
   
Dries committed
637
/**
638
 * Return a themed submenu, typically displayed under the tabs.
Dries's avatar
   
Dries committed
639
 *
640
641
 * @param $links
 *   An array of links.
Dries's avatar
   
Dries committed
642
643
 */
function theme_submenu($links) {
644
  return '<div class="submenu">'. implode(' | ', $links) .'</div>';
Dries's avatar
   
Dries committed
645
646
}

Dries's avatar
   
Dries committed
647
/**
648
649
650
651
652
653
654
655
656
657
658
 * Return a themed table.
 *
 * @param $header
 *   An array containing the table headers. Each element of the array can be
 *   either a localized string or an associative array with the following keys:
 *   - "data": The localized title of the table column.
 *   - "field": The database field represented in the table column (required if
 *     user is to be able to sort on this column).
 *   - "sort": A default sort order for this column ("asc" or "desc").
 *   - Any HTML attributes, such as "colspan", to apply to the column header cell.
 * @param $rows
659
660
661
662
663
664
 *   An array of table rows. Every row is an array of cells, or an associative
 *   array with the following keys:
 *   - "data": an array of cells
 *   - Any HTML attributes, such as "class", to apply to the table row.
 *
 *   Each cell can be either a string or an associative array with the following keys:
665
 *   - "data": The string to display in the table cell.
666
 *   - "header": Indicates this cell is a header.
667
 *   - Any HTML attributes, such as "colspan", to apply to the table cell.
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
 *
 *   Here's an example for $rows:
 *   @verbatim
 *   $rows = array(
 *     // Simple row
 *     array(
 *       'Cell 1', 'Cell 2', 'Cell 3'
 *     ),
 *     // Row with attributes on the row and some of its cells.
 *     array(
 *       'data' => array('Cell 1', array('data' => 'Cell 2', 'colspan' => 2)), 'class' => 'funky'
 *     )
 *   );
 *   @endverbatim
 *
683
684
 * @param $attributes
 *   An array of HTML attributes to apply to the table tag.
685
686
 * @param $caption
 *   A localized string to use for the <caption> tag.
687
688
 * @return
 *   An HTML string representing the table.
Dries's avatar
   
Dries committed
689
 */
690
function theme_table($header, $rows, $attributes = array(), $caption = NULL) {
Dries's avatar
   
Dries committed
691
  $output = '<table'. drupal_attributes($attributes) .">\n";
Dries's avatar
   
Dries committed
692

693
694
695
696
  if (isset($caption)) {
    $output .= '<caption>'. $caption ."</caption>\n";
  }

697
  // Format the table header:
698
  if (count($header)) {
Dries's avatar
   
Dries committed
699
    $ts = tablesort_init($header);
700
    $output .= ' <thead><tr>';
Dries's avatar
   
Dries committed
701
    foreach ($header as $cell) {
702
      $cell = tablesort_header($cell, $header, $ts);
703
      $output .= _theme_table_cell($cell, TRUE);
Dries's avatar
   
Dries committed
704
    }
705
    $output .= " </tr></thead>\n";
Dries's avatar
   
Dries committed
706
707
  }

708
  // Format the table rows:
709
  $output .= "<tbody>\n";
710
  if (count($rows)) {
Dries's avatar
   
Dries committed
711
    foreach ($rows as $number => $row) {
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
      $attributes = array();

      // Check if we're dealing with a simple or complex row
      if (isset($row['data'])) {
        foreach ($row as $key => $value) {
          if ($key == 'data') {
            $cells = $value;
          }
          else {
            $attributes[$key] = $value;
          }
        }
      }
      else {
        $cells = $row;
      }

729
730
      // Add odd/even class
      $class = ($number % 2 == 1) ? 'even': 'odd';
731
732
      if (isset($attributes['class'])) {
        $attributes['class'] .= ' '. $class;
Dries's avatar
   
Dries committed
733
734
      }
      else {
735
        $attributes['class'] = $class;
Dries's avatar
   
Dries committed
736
737
      }

738
739
      // Build row
      $output .= ' <tr'. drupal_attributes($attributes) .'>';
740
      $i = 0;
741
742
      foreach ($cells as $cell) {
        $cell = tablesort_cell($cell, $header, $ts, $i++);
743
        $output .= _theme_table_cell($cell);
Dries's avatar
   
Dries committed
744
745
746
747
748
      }
      $output .= " </tr>\n";
    }
  }

749
  $output .= "</tbody></table>\n";
Dries's avatar
   
Dries committed
750
751
752
  return $output;
}

753
754
755
756
/**
 * Return a themed sort icon.
 *
 * @param $style
757
 *   Set to either asc or desc. This sets which icon to show.
758
759
760
761
762
763
764
765
766
767
768
769
 * @return
 *   A themed sort icon.
 */
function theme_tablesort_indicator($style) {
  if ($style == "asc"){
    return theme('image', 'misc/arrow-asc.png', t('sort icon'), t('sort ascending'));
  }
  else {
    return theme('image', 'misc/arrow-desc.png', t('sort icon'), t('sort descending'));
  }
}

Dries's avatar
   
Dries committed
770
/**
771
772
773
774
775
776
777
778
779
780
 * Return a themed box.
 *
 * @param $title
 *   The subject of the box.
 * @param $content
 *   The content of the box.
 * @param $region
 *   The region in which the box is displayed.
 * @return
 *   A string containing the box output.
Dries's avatar
   
Dries committed
781
 */
Dries's avatar
   
Dries committed
782
function theme_box($title, $content, $region = 'main') {
783
  $output = '<h2 class="title">'. $title .'</h2><div>'. $content .'</div>';
Dries's avatar
   
Dries committed
784
  return $output;
Dries's avatar
   
Dries committed
785
786
787
}

/**
788
 * Return a themed block.
Dries's avatar
   
Dries committed
789
790
791
792
793
794
 *
 * You can style your blocks by defining .block (all blocks),
 * .block-<i>module</i> (all blocks of module <i>module</i>), and
 * \#block-<i>module</i>-<i>delta</i> (specific block of module <i>module</i>
 * with delta <i>delta</i>) in your theme's CSS.
 *
795
796
 * @param $block
 *   An object populated with fields from the "blocks" database table
797
 *   ($block->module, $block->delta ...) and fields returned by
798
799
800
 *   <i>module</i>_block('view') ($block->subject, $block->content, ...).
 * @return
 *   A string containing the block output.
Dries's avatar
   
Dries committed
801
 */
Dries's avatar
   
Dries committed
802
function theme_block($block) {
Dries's avatar
   
Dries committed
803
804
805
806
  $output  = "<div class=\"block block-$block->module\" id=\"block-$block->module-$block->delta\">\n";
  $output .= " <h2 class=\"title\">$block->subject</h2>\n";
  $output .= " <div class=\"content\">$block->content</div>\n";
  $output .= "</div>\n";
Dries's avatar
   
Dries committed
807
  return $output;
Dries's avatar
   
Dries committed
808
}
Dries's avatar
   
Dries committed
809

Dries's avatar
   
Dries committed
810
/**
811
812
 * Return a themed marker, useful for marking new or updated
 * content.
Dries's avatar
   
Dries committed
813
 *
814
 * @param $type
815
816
 *   Number representing the marker type to display
 * @see MARK_NEW, MARK_UPDATED, MARK_READ
817
818
 * @return
 *   A string containing the marker.
Dries's avatar
   
Dries committed
819
 */
820
821
function theme_mark($type = MARK_NEW) {
  global $user;
822
823
  if ($user->uid) {
    if ($type == MARK_NEW) {
Dries's avatar
Dries committed
824
      return ' <span class="marker">'. t('new') .'</span>';
825
826
    }
    else if ($type == MARK_UPDATED) {
Dries's avatar
Dries committed
827
      return ' <span class="marker">'. t('updated') .'</span>';
828
    }
829
  }
Dries's avatar
   
Dries committed
830
831
}

Dries's avatar
   
Dries committed
832
/**
833
834
835
 * Return a themed list of items.
 *
 * @param $items
836
837
838
839
 *   An array of items to be displayed in the list. If an item is a string,
 *   then it is used as is. If an item is an array, then the "data" element of
 *   the array is used as the contents of the list item and all other elements
 *   are treated as attributes of the list item element.
840
841
 * @param $title
 *   The title of the list.
842
843
 * @param $attributes
 *   The attributes applied to the list element.
844
845
 * @param $type
 *   The type of list to return (e.g. "ul", "ol")
846
847
 * @return
 *   A string containing the list output.
Dries's avatar
   
Dries committed
848
 */
849
function theme_item_list($items = array(), $title = NULL, $type = 'ul', $attributes = NULL) {
850
  $output = '<div class="item-list">';
Dries's avatar
   
Dries committed
851
  if (isset($title)) {
852
    $output .= '<h3>'. $title .'</h3>';
Dries's avatar
   
Dries committed
853
854
  }

855
  if (!empty($items)) {
856
    $output .= "<$type" . drupal_attributes($attributes) . '>';
Dries's avatar
   
Dries committed
857
    foreach ($items as $item) {
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
      $attributes = array();
      if (is_array($item)) {
        foreach ($item as $key => $value) {
          if ($key == 'data') {
            $data = $value;
          }
          else {
            $attributes[$key] = $value;
          }
        }
      }
      else {
        $data = $item;
      }
      $output .= '<li' . drupal_attributes($attributes) . '>'. $data .'</li>';
Dries's avatar
   
Dries committed
873
    }
874
    $output .= "</$type>";
Dries's avatar
   
Dries committed
875
  }
876
  $output .= '</div>';
Dries's avatar
   
Dries committed
877
878
879
  return $output;
}

Dries's avatar
   
Dries committed
880
/**
881
 * Returns code that emits the 'more help'-link.
Dries's avatar
   
Dries committed
882
 */
Dries's avatar
   
Dries committed
883
function theme_more_help_link($url) {
884
  return '<div class="more-help-link">' . t('[<a href="%link">more help...</a>]', array('%link' => check_url($url))) . '</div>';
Dries's avatar
   
Dries committed
885
886
}

Dries's avatar
   
Dries committed
887
/**
888
 * Return code that emits an XML icon.
Dries's avatar
   
Dries committed
889
890
 */
function theme_xml_icon($url) {
Dries's avatar
   
Dries committed
891
  if ($image = theme('image', 'misc/xml.png', t('XML feed'), t('XML feed'))) {
892
    return '<a href="'. check_url($url) .'" class="xml-icon">'. $image. '</a>';
Dries's avatar
   
Dries committed
893
  }
Dries's avatar
   
Dries committed
894
895
}

896
897
898
899
900
/**
 * Return code that emits an feed icon.
 */
function theme_feed_icon($url) {
  if ($image = theme('image', 'misc/feed.png', t('Syndicate content'), t('Syndicate content'))) {
901
    return '<a href="'. check_url($url) .'" class="feed-icon">'. $image. '</a>';
902
903
904
  }
}

Dries's avatar
   
Dries committed
905
/**
906
907
 * Execute hook_footer() which is run at the end of the page right before the
 * close of the body tag.
Dries's avatar
   
Dries committed
908
909
910
 *
 * @param $main (optional)
 *
911
912
 * @return
 *   A string containing the results of the hook_footer() calls.
Dries's avatar
   
Dries committed
913
 */
Dries's avatar
   
Dries committed
914
function theme_closure($main = 0) {
Dries's avatar
   
Dries committed
915
  $footer = module_invoke_all('footer', $main);
916
  return implode("\n", $footer);
Dries's avatar
   
Dries committed
917
918
}

Dries's avatar
   
Dries committed
919
/**
920
 * Return a set of blocks available for the current user.
921
 *
922
923
924
925
 * @param $region
 *   Which set of blocks to retrieve.
 * @return
 *   A string containing the themed blocks for this region.
926
927
 */
function theme_blocks($region) {
Dries's avatar
   
Dries committed
928
  $output = '';
929

930
  if ($list = block_list($region)) {
931
932
933
    foreach ($list as $key => $block) {
      // $key == <i>module</i>_<i>delta</i>
      $output .= theme('block', $block);
Dries's avatar
   
Dries committed
934
935
    }
  }
936
937
938
939

  // Add any content assigned to this region through drupal_set_content() calls.
  $output .= drupal_get_content($region);

Dries's avatar
   
Dries committed
940
  return $output;
Dries's avatar
   
Dries committed
941
}
942

943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
/**
 * Format a username.
 *
 * @param $object
 *   The user object to format, usually returned from user_load().
 * @return
 *   A string containing an HTML link to the user's page if the passed object
 *   suggests that this is a site user. Otherwise, only the username is returned.
 */
function theme_username($object) {

  if ($object->uid && $object->name) {
    // Shorten the name when it is too long or it will break many tables.
    if (drupal_strlen($object->name) > 20) {
      $name = drupal_substr($object->name, 0, 15) .'...';
    }
    else {
      $name = $object->name;
    }

    if (user_access('access user profiles')) {
      $output = l($name, 'user/'. $object->uid, array('title' => t('View user profile.')));
    }
    else {
967
      $output = check_plain($name);
968
969
970
971
972
973
974
975
    }
  }
  else if ($object->name) {
    // Sometimes modules display content composed by people who are
    // not registered members of the site (e.g. mailing list or news
    // aggregator modules). This clause enables modules to display
    // the true author of the content.
    if ($object->homepage) {
976
      $output = l($object->name, $object->homepage);
977
978
    }
    else {
979
      $output = check_plain($object->name);
980
981
982
983
984
985
986
987
988
989
    }

    $output .= ' ('. t('not verified') .')';
  }
  else {
    $output = variable_get('anonymous', 'Anonymous');
  }

  return $output;
}
990

991
992
993
994
995
996
997
998
999
1000
function theme_progress_bar($percent, $message) {
  $output = '<div id="progress" class="progress">';
  $output .= '<div class="percentage">'. $percent .'%</div>';
  $output .= '<div class="status">'. $message .'</div>';
  $output .= '<div class="bar"><div class="filled" style="width: '. $percent .'%"></div></div>';
  $output .= '</div>';

  return $output;
}

1001
/**
Dries's avatar
   
Dries committed
1002
 * @} End of "defgroup themeable".
1003
 */
1004

1005
function _theme_table_cell($cell, $header = FALSE) {
Dries's avatar
   
Dries committed
1006
1007
  $attributes = '';

Dries's avatar
   
Dries committed
1008
  if (is_array($cell)) {
Dries's avatar
   
Dries committed
1009
    $data = $cell['data'];
1010
1011
1012
1013
    $header |= isset($cell['header']);
    unset($cell['data']);
    unset($cell['header']);
    $attributes = drupal_attributes($cell);
Dries's avatar
   
Dries committed
1014
  }
Dries's avatar
   
Dries committed
1015
1016
  else {
    $data = $cell;
Dries's avatar
   
Dries committed
1017
  }
Dries's avatar
   
Dries committed
1018

Dries's avatar
   
Dries committed
1019
1020
  if ($header) {
    $output = "<th$attributes>$data</th>";
Dries's avatar
   
Dries committed
1021
  }
Dries's avatar
   
Dries committed
1022
1023
  else {
    $output = "<td$attributes>$data</td>";
Dries's avatar
   
Dries committed
1024
  }
Dries's avatar
   
Dries committed
1025

Dries's avatar
   
Dries committed
1026
1027
  return $output;
}
1028