theme.inc 62.1 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
 */
Dries's avatar
 
Dries committed
31
function init_theme() {
32
  global $theme, $user, $custom_theme, $theme_key;
33 34 35 36 37

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

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

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

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

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

53 54 55 56
  // Find all our ancestor themes and put them in an array.
  $base_theme = array();
  $ancestor = $theme;
  while ($ancestor && isset($themes[$ancestor]->base_theme)) {
57 58
    $base_theme[] = $new_base_theme = $themes[$themes[$ancestor]->base_theme];
    $ancestor = $themes[$ancestor]->base_theme;
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
  }
  _init_theme($themes[$theme], array_reverse($base_theme));
}

/**
 * Initialize the theme system given already loaded information. This
 * function is useful to initialize a theme when no database is present.
 *
 * @param $theme
 *   An object with the following information:
 *     filename
 *       The .info file for this theme. The 'path' to
 *       the theme will be in this file's directory. (Required)
 *     owner
 *       The path to the .theme file or the .engine file to load for
 *       the theme. (Required)
 *     stylesheet
 *       The primary stylesheet for the theme. (Optional)
 *     engine
 *       The name of theme engine to use. (Optional)
 * @param $base_theme
 *    An optional array of objects that represent the 'base theme' if the
 *    theme is meant to be derivative of another theme. It requires
 *    the same information as the $theme object. It should be in
 *    'oldest first' order, meaning the top level of the chain will
 *    be first.
85 86
 * @param $registry_callback
 *   The callback to invoke to set the theme registry.
87
 */
88
function _init_theme($theme, $base_theme = array(), $registry_callback = '_theme_load_registry') {
89 90 91 92 93 94
  global $theme_info, $base_theme_info, $theme_engine, $theme_path;
  $theme_info = $theme;
  $base_theme_info = $base_theme;

  $theme_path = dirname($theme->filename);

95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
  // Prepare stylesheets from this theme as well as all ancestor themes.
  // We work it this way so that we can have child themes override parent
  // theme stylesheets easily.
  $final_stylesheets = array();

  // Grab stylesheets from base theme
  foreach ($base_theme as $base) {
    if (!empty($base->stylesheets)) {
      foreach ($base->stylesheets as $media => $stylesheets) {
        foreach ($stylesheets as $name => $stylesheet) {
          $final_stylesheets[$media][$name] = $stylesheet;
        }
      }
    }
  }

111 112 113
  // Add stylesheets used by this theme.
  if (!empty($theme->stylesheets)) {
    foreach ($theme->stylesheets as $media => $stylesheets) {
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
      foreach ($stylesheets as $name => $stylesheet) {
        $final_stylesheets[$media][$name] = $stylesheet;
      }
    }
  }

  // And now add the stylesheets properly
  foreach ($final_stylesheets as $media => $stylesheets) {
    foreach ($stylesheets as $stylesheet) {
      drupal_add_css($stylesheet, 'theme', $media);
    }
  }

  // Do basically the same as the above for scripts
  $final_scripts = array();

  // Grab scripts from base theme
  foreach ($base_theme as $base) {
    if (!empty($base->scripts)) {
      foreach ($base->scripts as $name => $script) {
        $final_scripts[$name] = $script;
135
      }
Dries's avatar
 
Dries committed
136 137
    }
  }
138

139 140
  // Add scripts used by this theme.
  if (!empty($theme->scripts)) {
141 142
    foreach ($theme->scripts as $name => $script) {
      $final_scripts[$name] = $script;
143 144 145
    }
  }

146 147 148 149 150
  // Add scripts used by this theme.
  foreach ($final_scripts as $script) {
    drupal_add_js($script, 'theme');
  }

151 152 153 154 155 156 157 158
  $theme_engine = NULL;

  // Initialize the theme.
  if (isset($theme->engine)) {
    // Include the engine.
    include_once './'. $theme->owner;

    $theme_engine = $theme->engine;
Dries's avatar
 
Dries committed
159
    if (function_exists($theme_engine .'_init')) {
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
      foreach ($base_theme as $base) {
        call_user_func($theme_engine .'_init', $base);
      }
      call_user_func($theme_engine .'_init', $theme);
    }
  }
  else {
    // include non-engine theme files
    foreach ($base_theme as $base) {
      // Include the theme file or the engine.
      if (!empty($base->owner)) {
        include_once './'. $base->owner;
      }
    }
    // and our theme gets one too.
    if (!empty($theme->owner)) {
      include_once './'. $theme->owner;
Dries's avatar
 
Dries committed
177 178
    }
  }
179 180

  $registry_callback($theme, $base_theme, $theme_engine);
Dries's avatar
 
Dries committed
181 182
}

183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
/**
 * Retrieve the stored theme registry. If the theme registry is already
 * in memory it will be returned; otherwise it will attempt to load the
 * registry from cache. If this fails, it will construct the registry and
 * cache it.
 */
function theme_get_registry($registry = NULL) {
  static $theme_registry = NULL;
  if (isset($registry)) {
    $theme_registry = $registry;
  }

  return $theme_registry;
}

/**
 * Store the theme registry in memory.
 */
function _theme_set_registry($registry) {
  // Pass through for setting of static variable.
  return theme_get_registry($registry);
}

/**
 * Get the theme_registry cache from the database; if it doesn't exist, build
 * it.
209 210 211 212 213 214 215 216
 *
 * @param $theme
 *   The loaded $theme object.
 * @param $base_theme
 *   An array of loaded $theme objects representing the ancestor themes in
 *   oldest first order.
 * @param theme_engine
 *   The name of the theme engine.
217
 */
218 219 220
function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL) {
  // Check the theme registry cache; if it exists, use it.
  $cache = cache_get("theme_registry:$theme->name", 'cache');
221
  if (isset($cache->data)) {
222
    $registry = $cache->data;
223 224
  }
  else {
225 226
    // If not, build one and cache it.
    $registry = _theme_build_registry($theme, $base_theme, $theme_engine);
227 228 229 230 231 232 233 234 235
    _theme_save_registry($theme, $registry);
  }
  _theme_set_registry($registry);
}

/**
 * Write the theme_registry cache into the database.
 */
function _theme_save_registry($theme, $registry) {
236
  cache_set("theme_registry:$theme->name", $registry);
237 238 239 240 241 242 243 244 245 246 247 248
}

/**
 * Force the system to rebuild the theme registry; this should be called
 * when modules are added to the system, or when a dynamic system needs
 * to add more theme hooks.
 */
function drupal_rebuild_theme_registry() {
  cache_clear_all('theme_registry', 'cache', TRUE);
}

/**
249 250 251 252 253 254 255 256
 * Process a single invocation of the theme hook. $type will be one
 * of 'module', 'theme_engine' or 'theme' and it tells us some
 * important information.
 *
 * Because $cache is a reference, the cache will be continually
 * expanded upon; new entries will replace old entries in the
 * array_merge, but we are careful to ensure some data is carried
 * forward, such as the arguments a theme hook needs.
257 258 259 260 261
 *
 * An override flag can be set for preprocess functions. When detected the
 * cached preprocessors for the hook will not be merged with the newly set.
 * This can be useful to themes and theme engines by giving them more control
 * over how and when the preprocess functions are run.
262
 */
263
function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
264 265
  $function = $name .'_theme';
  if (function_exists($function)) {
266
    $result = $function($cache, $type, $theme, $path);
267 268 269

    foreach ($result as $hook => $info) {
      $result[$hook]['type'] = $type;
270
      $result[$hook]['theme path'] = $path;
271 272
      // if function and file are left out, default to standard naming
      // conventions.
273
      if (!isset($info['template']) && !isset($info['function'])) {
274 275
        $result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name .'_') . $hook;
      }
276 277 278
      // If a path is set in the info, use what was set. Otherwise use the
      // default path. This is mostly so system.module can declare theme
      // functions on behalf of core .include files.
279 280
      // All files are included to be safe. Conditionally included
      // files can prevent them from getting registered.
281 282
      if (isset($info['file']) && !isset($info['path'])) {
        $result[$hook]['file'] = $path .'/'. $info['file'];
283 284 285 286 287 288 289 290
        include_once($result[$hook]['file']);
      }
      elseif (isset($info['file']) && isset($info['path'])) {
        include_once($info['path'] .'/'. $info['file']);
      }

      if (isset($info['template']) && !isset($info['path'])) {
        $result[$hook]['template'] = $path .'/'. $info['template'];
291 292 293 294 295 296 297
      }
      // If 'arguments' have been defined previously, carry them forward.
      // This should happen if a theme overrides a Drupal defined theme
      // function, for example.
      if (!isset($info['arguments']) && isset($cache[$hook])) {
        $result[$hook]['arguments'] = $cache[$hook]['arguments'];
      }
298 299 300 301 302 303 304 305
      // Likewise with theme paths. These are used for template naming suggestions.
      // Theme implementations can occur in multiple paths. Suggestions should follow.
      if (!isset($info['theme paths']) && isset($cache[$hook])) {
        $result[$hook]['theme paths'] = $cache[$hook]['theme paths'];
      }
      // Check for sub-directories.
      $result[$hook]['theme paths'][] = isset($info['path']) ? $info['path'] : $path;

306 307 308
      // Check for default _preprocess_ functions. Ensure arrayness.
      if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) {
        $info['preprocess functions'] = array();
309 310 311 312 313 314 315
        $prefixes = array();
        if ($type == 'module') {
          // Default preprocessor prefix.
          $prefixes[] = 'template';
          // Add all modules so they can intervene with their own preprocessors. This allows them
          // to provide preprocess functions even if they are not the owner of the current hook.
          $prefixes += module_list();
316
        }
317 318 319 320 321
        elseif ($type == 'theme_engine') {
          // Theme engines get an extra set that come before the normally named preprocessors.
          $prefixes[] = $name .'_engine';
          // The theme engine also registers on behalf of the theme. The theme or engine name can be used.
          $prefixes[] = $name;
322
          $prefixes[] = $theme;
323
        }
324 325 326 327 328
        else {
          // This applies when the theme manually registers their own preprocessors.
          $prefixes[] = $name;
        }

329 330 331 332 333 334 335
        foreach ($prefixes as $prefix) {
          if (function_exists($prefix .'_preprocess')) {
            $info['preprocess functions'][] = $prefix .'_preprocess';
          }
          if (function_exists($prefix .'_preprocess_'. $hook)) {
            $info['preprocess functions'][] = $prefix .'_preprocess_'. $hook;
          }
336
        }
337
      }
338 339 340 341 342 343 344
      // Check for the override flag and prevent the cached preprocess functions from being used.
      // This allows themes or theme engines to remove preprocessors set earlier in the registry build.
      if (!empty($info['override preprocess functions'])) {
        // Flag not needed inside the registry.
        unset($result[$hook]['override preprocess functions']);
      }
      elseif (isset($cache[$hook]['preprocess functions']) && is_array($cache[$hook]['preprocess functions'])) {
345 346 347
        $info['preprocess functions'] = array_merge($cache[$hook]['preprocess functions'], $info['preprocess functions']);
      }
      $result[$hook]['preprocess functions'] = $info['preprocess functions'];
348 349
    }

350
    // Merge the newly created theme hooks into the existing cache.
351 352 353 354 355 356
    $cache = array_merge($cache, $result);
  }
}

/**
 * Rebuild the hook theme_registry cache.
357 358 359 360 361 362 363 364
 *
 * @param $theme
 *   The loaded $theme object.
 * @param $base_theme
 *   An array of loaded $theme objects representing the ancestor themes in
 *   oldest first order.
 * @param theme_engine
 *   The name of the theme engine.
365
 */
366
function _theme_build_registry($theme, $base_theme, $theme_engine) {
367
  $cache = array();
368 369
  // First, process the theme hooks advertised by modules. This will
  // serve as the basic registry.
370
  foreach (module_implements('theme') as $module) {
371 372 373 374 375 376 377 378 379 380 381
    _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module));
  }

  // Process each base theme.
  foreach ($base_theme as $base) {
    // If the theme uses a theme engine, process its hooks.
    $base_path = dirname($base->filename);
    if ($theme_engine) {
      _theme_process_registry($cache, $theme_engine, 'base_theme_engine', $base->name, $base_path);
    }
    _theme_process_registry($cache, $base->name, 'base_theme', $base->name, $base_path);
382 383
  }

384
  // And then the same thing, but for the theme.
385
  if ($theme_engine) {
386
    _theme_process_registry($cache, $theme_engine, 'theme_engine', $theme->name, dirname($theme->filename));
387 388
  }

389 390
  // Finally, hooks provided by the theme itself.
  _theme_process_registry($cache, $theme->name, 'theme', $theme->name, dirname($theme->filename));
391

392 393
  // Let modules alter the registry
  drupal_alter('theme_registry', $cache);
394 395 396
  return $cache;
}

Dries's avatar
 
Dries committed
397
/**
Dries's avatar
 
Dries committed
398 399
 * Provides a list of currently available themes.
 *
400 401 402
 * If the database is active then it will be retrieved from the database.
 * Otherwise it will retrieve a new list.
 *
Dries's avatar
 
Dries committed
403
 * @param $refresh
404 405 406
 *   Whether to reload the list of themes from the database.
 * @return
 *   An array of the currently available themes.
Dries's avatar
 
Dries committed
407
 */
408
function list_themes($refresh = FALSE) {
409
  static $list = array();
Dries's avatar
 
Dries committed
410 411

  if ($refresh) {
412
    $list = array();
Dries's avatar
 
Dries committed
413 414
  }

415
  if (empty($list)) {
Dries's avatar
 
Dries committed
416
    $list = array();
417 418
    $themes = array();
    // Extract from the database only when it is available.
419 420
    // Also check that the site is not in the middle of an install or update.
    if (db_is_active() && !defined('MAINTENANCE_MODE')) {
421 422 423 424 425
      $result = db_query("SELECT * FROM {system} WHERE type = '%s'", 'theme');
      while ($theme = db_fetch_object($result)) {
        if (file_exists($theme->filename)) {
          $theme->info = unserialize($theme->info);
          $themes[] = $theme;
426
        }
427 428 429
      }
    }
    else {
430
      // Scan the installation when the database should not be read.
431 432 433 434 435 436
      $themes = _system_theme_data();
    }

    foreach ($themes as $theme) {
      foreach ($theme->info['stylesheets'] as $media => $stylesheets) {
        foreach ($stylesheets as $stylesheet => $path) {
437
          if (file_exists($path)) {
438
            $theme->stylesheets[$media][$stylesheet] = $path;
439
          }
440
        }
441 442 443 444
      }
      foreach ($theme->info['scripts'] as $script => $path) {
        if (file_exists($path)) {
          $theme->scripts[$script] = $path;
445
        }
Dries's avatar
 
Dries committed
446
      }
447 448
      if (isset($theme->info['engine'])) {
        $theme->engine = $theme->info['engine'];
Dries's avatar
 
Dries committed
449
      }
450 451 452
      if (isset($theme->info['base theme'])) {
        $theme->base_theme = $theme->info['base theme'];
      }
453 454 455 456 457
      // Status is normally retrieved from the database. Add zero values when
      // read from the installation directory to prevent notices.
      if (!isset($theme->status)) {
        $theme->status = 0;
      }
458
      $list[$theme->name] = $theme;
Dries's avatar
 
Dries committed
459 460 461 462 463 464
    }
  }

  return $list;
}

Dries's avatar
 
Dries committed
465
/**
466 467 468 469 470 471 472 473 474
 * Generate the themed output.
 *
 * All requests for theme hooks must go through this function. It examines
 * the request and routes it to the appropriate theme function. The theme
 * registry is checked to determine which implementation to use, which may
 * be a function or a template.
 *
 * If the implementation is a function, it is executed and its return value
 * passed along.
Dries's avatar
 
Dries committed
475
 *
476
 * If the implementation is a template, the arguments are converted to a
477 478 479 480
 * $variables array. This array is then modified by the module implementing
 * the hook, theme engine (if applicable) and the theme. The following
 * functions may be used to modify the $variables array. They are processed in
 * this order when available:
481
 *
482 483
 * - template_preprocess(&$variables)
 *   This sets a default set of variables for all template implementations.
484
 *
485 486 487
 * - template_preprocess_HOOK(&$variables)
 *   This is the first preprocessor called specific to the hook; it should be
 *   implemented by the module that registers it.
488
 *
489 490 491
 * - MODULE_preprocess(&$variables)
 *   This will be called for all templates; it should only be used if there
 *   is a real need. It's purpose is similar to template_preprocess().
492
 *
493 494 495 496 497 498
 * - MODULE_preprocess_HOOK(&$variables)
 *   This is for modules that want to alter or provide extra variables for
 *   theming hooks not registered to itself. For example, if a module named
 *   "foo" wanted to alter the $submitted variable for the hook "node" a
 *   preprocess function of foo_preprocess_node() can be created to intercept
 *   and alter the variable.
499
 *
500 501 502
 * - ENGINE_engine_preprocess(&$variables)
 *   This function should only be implemented by theme engines and exists
 *   so that it can set necessary variables for all hooks.
503
 *
504 505 506 507 508 509 510 511 512
 * - ENGINE_engine_preprocess_HOOK(&$variables)
 *   This is the same as the previous function, but it is called for a single
 *   theming hook.
 *
 * - ENGINE_preprocess(&$variables)
 *   This is meant to be used by themes that utilize a theme engine. It is
 *   provided so that the preprocessor is not locked into a specific theme.
 *   This makes it easy to share and transport code but theme authors must be
 *   careful to prevent fatal re-declaration errors when using sub-themes that
513
 *   have their own preprocessor named exactly the same as its base theme. In
514
 *   the default theme engine (PHPTemplate), sub-themes will load their own
515
 *   template.php file in addition to the one used for its parent theme. This
516 517 518
 *   increases the risk for these errors. A good practice is to use the engine
 *   name for the base theme and the theme name for the sub-themes to minimize
 *   this possibility.
519
 *
520 521 522 523 524 525 526 527
 * - ENGINE_preprocess_HOOK(&$variables)
 *   The same applies from the previous function, but it is called for a
 *   specific hook.
 *
 * - THEME_preprocess(&$variables)
 *   These functions are based upon the raw theme; they should primarily be
 *   used by themes that do not use an engine or by sub-themes. It serves the
 *   same purpose as ENGINE_preprocess().
528
 *
529 530 531
 * - THEME_preprocess_HOOK(&$variables)
 *   The same applies from the previous function, but it is called for a
 *   specific hook.
Dries's avatar
 
Dries committed
532
 *
533 534 535 536 537 538 539 540
 * There are two special variables that these hooks can set:
 *   'template_file' and 'template_files'. These will be merged together
 *   to form a list of 'suggested' alternate template files to use, in
 *   reverse order of priority. template_file will always be a higher
 *   priority than items in template_files. theme() will then look for these
 *   files, one at a time, and use the first one
 *   that exists.
 * @param $hook
541 542 543 544 545 546
 *   The name of the theme function to call. May be an array, in which
 *   case the first hook that actually has an implementation registered
 *   will be used. This can be used to choose 'fallback' theme implementations,
 *   so that if the specific theme hook isn't implemented anywhere, a more
 *   generic one will be used. This can allow themes to create specific theme
 *   implementations for named objects.
547 548 549 550
 * @param ...
 *   Additional arguments to pass along to the theme function.
 * @return
 *   An HTML string that generates the themed output.
Dries's avatar
 
Dries committed
551
 */
Dries's avatar
 
Dries committed
552
function theme() {
553
  $args = func_get_args();
554
  $hook = array_shift($args);
555

556 557 558 559
  static $hooks = NULL;
  if (!isset($hooks)) {
    init_theme();
    $hooks = theme_get_registry();
560
  }
561

562 563 564 565 566 567 568 569 570
  if (is_array($hook)) {
    foreach ($hook as $candidate) {
      if (isset($hooks[$candidate])) {
        break;
      }
    }
    $hook = $candidate;
  }

571 572
  if (!isset($hooks[$hook])) {
    return;
573 574
  }

575
  $info = $hooks[$hook];
576 577 578 579
  global $theme_path;
  $temp = $theme_path;
  // point path_to_theme() to the currently used theme path:
  $theme_path = $hooks[$hook]['theme path'];
Dries's avatar
 
Dries committed
580

581 582 583 584 585 586 587 588
  // Include a file if the theme function or preprocess function is held elsewhere.
  if (!empty($info['file'])) {
    $include_file = $info['file'];
    if (isset($info['path'])) {
      $include_file = $info['path'] .'/'. $include_file;
    }
    include_once($include_file);
  }
589 590
  if (isset($info['function'])) {
    // The theme call is a function.
591
    $output = call_user_func_array($info['function'], $args);
Dries's avatar
 
Dries committed
592
  }
593 594 595 596 597 598 599 600 601 602 603 604
  else {
    // The theme call is a template.
    $variables = array(
      'template_files' => array()
    );
    if (!empty($info['arguments'])) {
      $count = 0;
      foreach ($info['arguments'] as $name => $default) {
        $variables[$name] = isset($args[$count]) ? $args[$count] : $default;
        $count++;
      }
    }
Dries's avatar
 
Dries committed
605

606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
    // default render function and extension.
    $render_function = 'theme_render_template';
    $extension = '.tpl.php';

    // Run through the theme engine variables, if necessary
    global $theme_engine;
    if (isset($theme_engine)) {
      // If theme or theme engine is implementing this, it may have
      // a different extension and a different renderer.
      if ($hooks[$hook]['type'] != 'module') {
        if (function_exists($theme_engine .'_render_template')) {
          $render_function = $theme_engine .'_render_template';
        }
        $extension_function = $theme_engine .'_extension';
        if (function_exists($extension_function)) {
          $extension = $extension_function();
        }
      }
    }

626 627 628 629 630 631 632 633
    if (isset($info['preprocess functions']) && is_array($info['preprocess functions'])) {
      // This construct ensures that we can keep a reference through
      // call_user_func_array.
      $args = array(&$variables, $hook);
      foreach ($info['preprocess functions'] as $preprocess_function) {
        if (function_exists($preprocess_function)) {
          call_user_func_array($preprocess_function, $args);
        }
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
      }
    }

    // Get suggestions for alternate templates out of the variables
    // that were set. This lets us dynamically choose a template
    // from a list. The order is FILO, so this array is ordered from
    // least appropriate first to most appropriate last.
    $suggestions = array();

    if (isset($variables['template_files'])) {
      $suggestions = $variables['template_files'];
    }
    if (isset($variables['template_file'])) {
      $suggestions[] = $variables['template_file'];
    }

    if ($suggestions) {
651
      $template_file = drupal_discover_template($info['theme paths'], $suggestions, $extension);
652 653 654
    }

    if (empty($template_file)) {
655
      $template_file = $hooks[$hook]['template'] . $extension;
656 657 658 659
      if (isset($hooks[$hook]['path'])) {
        $template_file = $hooks[$hook]['path'] .'/'. $template_file;
      }
    }
660
    $output = $render_function($template_file, $variables);
Dries's avatar
 
Dries committed
661
  }
662 663 664
  // restore path_to_theme()
  $theme_path = $temp;
  return $output;
665 666 667
}

/**
668 669 670
 * Choose which template file to actually render. These are all suggested
 * templates from themes and modules. Theming implementations can occur on
 * multiple levels. All paths are checked to account for this.
671
 */
672
function drupal_discover_template($paths, $suggestions, $extension = '.tpl.php') {
673 674
  global $theme_engine;

675
  // Loop through all paths and suggestions in FIFO order.
676
  $suggestions = array_reverse($suggestions);
677
  $paths = array_reverse($paths);
678
  foreach ($suggestions as $suggestion) {
679 680 681 682 683 684
    if (!empty($suggestion)) {
      foreach ($paths as $path) {
        if (file_exists($file = $path .'/'. $suggestion . $extension)) {
          return $file;
        }
      }
685
    }
Dries's avatar
 
Dries committed
686 687 688 689
  }
}

/**
690
 * Return the path to the currently selected theme.
Dries's avatar
 
Dries committed
691
 */
Dries's avatar
 
Dries committed
692
function path_to_theme() {
693
  global $theme_path;
Dries's avatar
 
Dries committed
694

695
  if (!isset($theme_path)) {
696 697 698
    init_theme();
  }

699
  return $theme_path;
700 701
}

702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
/**
 * Find overridden theme functions. Called by themes and/or theme engines to
 * easily discover theme functions.
 *
 * @param $cache
 *   The existing cache of theme hooks to test against.
 * @param $prefixes
 *   An array of prefixes to test, in reverse order of importance.
 *
 * @return $templates
 *   The functions found, suitable for returning from hook_theme;
 */
function drupal_find_theme_functions($cache, $prefixes) {
  $templates = array();
  $functions = get_defined_functions();

  foreach ($cache as $hook => $info) {
    foreach ($prefixes as $prefix) {
      if (!empty($info['pattern'])) {
        $matches = preg_grep('/^'. $prefix .'_'. $info['pattern'] .'/', $functions['user']);
        if ($matches) {
          foreach ($matches as $match) {
724
            $new_hook = str_replace($prefix .'_', '', $match);
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
            $templates[$new_hook] = array(
              'function' => $match,
              'arguments' => $info['arguments'],
            );
          }
        }
      }
      if (function_exists($prefix .'_'. $hook)) {
        $templates[$hook] = array(
          'function' => $prefix .'_'. $hook,
        );
      }
    }
  }

  return $templates;
}

/**
 * Find overridden theme templates. Called by themes and/or theme engines to
 * easily discover templates.
 *
 * @param $cache
 *   The existing cache of theme hooks to test against.
 * @param $extension
 *   The extension that these templates will have.
 * @param $path
 *   The path to search.
 */
function drupal_find_theme_templates($cache, $extension, $path) {
  $templates = array();

  // Escape the dots in the extension.
758
  $regex = str_replace('.', '\.', $extension) .'$';
759 760 761 762 763

  // Because drupal_system_listing works the way it does, we check for real
  // templates separately from checking for patterns.
  $files = drupal_system_listing($regex, $path, 'name', 0);
  foreach ($files as $template => $file) {
764 765 766 767 768 769
    // Chop off the remaining extensions if there are any. $template already
    // has the rightmost extension removed, but there might still be more,
    // such as with .tpl.php, which still has .tpl in $template at this point.
    if (($pos = strpos($template, '.')) !== FALSE) {
      $template = substr($template, 0, $pos);
    }
770 771 772 773 774
    // Transform - in filenames to _ to match function naming scheme
    // for the purposes of searching.
    $hook = strtr($template, '-', '_');
    if (isset($cache[$hook])) {
      $templates[$hook] = array(
775
        'template' => $template,
776 777 778 779 780 781 782 783 784
        'path' => dirname($file->filename),
      );
    }
  }

  $patterns = array_keys($files);

  foreach ($cache as $hook => $info) {
    if (!empty($info['pattern'])) {
785 786 787 788 789
      // Transform _ in pattern to - to match file naming scheme
      // for the purposes of searching.
      $pattern = strtr($info['pattern'], '_', '-');

      $matches = preg_grep('/^'. $pattern .'/', $patterns);
790 791 792
      if ($matches) {
        foreach ($matches as $match) {
          $file = substr($match, 0, strpos($match, '.'));
793 794
          // Put the underscores back in for the hook name and register this pattern.
          $templates[strtr($file, '-', '_')] = array(
795
            'template' => $file,
796 797 798 799 800 801 802 803 804 805
            'path' => dirname($files[$match]->filename),
            'arguments' => $info['arguments'],
          );
        }
      }
    }
  }
  return $templates;
}

Dries's avatar
 
Dries committed
806 807 808
/**
 * Retrieve an associative array containing the settings for a theme.
 *
809
 * The final settings are arrived at by merging the default settings,
Dries's avatar
 
Dries committed
810 811 812 813 814 815 816 817 818 819 820 821 822
 * 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.
 */
823
function theme_get_settings($key = NULL) {
Dries's avatar
 
Dries committed
824 825 826 827
  $defaults = array(
    'mission'                       =>  '',
    'default_logo'                  =>  1,
    'logo_path'                     =>  '',
828 829
    'default_favicon'               =>  1,
    'favicon_path'                  =>  '',
830 831
    'primary_links'                 =>  1,
    'secondary_links'               =>  1,
Dries's avatar
 
Dries committed
832
    'toggle_logo'                   =>  1,
833
    'toggle_favicon'                =>  1,
Dries's avatar
 
Dries committed
834 835 836 837 838 839
    'toggle_name'                   =>  1,
    'toggle_search'                 =>  1,
    'toggle_slogan'                 =>  0,
    'toggle_mission'                =>  1,
    'toggle_node_user_picture'      =>  0,
    'toggle_comment_user_picture'   =>  0,
840 841
    'toggle_primary_links'          =>  1,
    'toggle_secondary_links'        =>  1,
Dries's avatar
 
Dries committed
842 843
  );

844
  if (module_exists('node')) {
845
    foreach (node_get_types() as $type => $name) {
846
      $defaults['toggle_node_info_'. $type] = 1;
847
    }
Dries's avatar
 
Dries committed
848 849 850 851 852 853 854
  }
  $settings = array_merge($defaults, variable_get('theme_settings', array()));

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

855
  // Only offer search box if search.module is enabled.
856
  if (!module_exists('search') || !user_access('search content')) {
857 858 859
    $settings['toggle_search'] = 0;
  }

Dries's avatar
 
Dries committed
860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878
  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.
 */
879
function theme_get_setting($setting_name, $refresh = FALSE) {
880
  global $theme_key;
Dries's avatar
 
Dries committed
881 882 883
  static $settings;

  if (empty($settings) || $refresh) {
884
    $settings = theme_get_settings($theme_key);
Dries's avatar
 
Dries committed
885 886 887 888 889 890 891 892 893 894 895 896 897 898

    $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']) {
899
        $settings['logo'] = base_path() . dirname($theme_object->filename) .'/logo.png';
Dries's avatar
 
Dries committed
900 901
      }
      elseif ($settings['logo_path']) {
902
        $settings['logo'] = base_path() . $settings['logo_path'];
Dries's avatar
 
Dries committed
903 904 905
      }
    }

906 907 908
    if ($settings['toggle_favicon']) {
      if ($settings['default_favicon']) {
        if (file_exists($favicon = dirname($theme_object->filename) .'/favicon.ico')) {
909
          $settings['favicon'] = base_path() . $favicon;
910 911
        }
        else {
912
          $settings['favicon'] = base_path() .'misc/favicon.ico';
913 914 915
        }
      }
      elseif ($settings['favicon_path']) {
916
        $settings['favicon'] = base_path() . $settings['favicon_path'];
917
      }
918 919 920
      else {
        $settings['toggle_favicon'] = FALSE;
      }
921
    }
Dries's avatar
 
Dries committed
922 923 924 925 926
  }

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

927
/**
928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
 * Render a system default template, which is essentially a PHP template.
 *
 * @param $file
 *   The filename of the template to render.
 * @param $variables
 *   A keyed array of variables that will appear in the output.
 *
 * @return
 *   The output generated by the template.
 */
function theme_render_template($file, $variables) {
  extract($variables, EXTR_SKIP);  // Extract the variables to a local namespace
  ob_start();                      // Start output buffering
  include "./$file";               // Include the file
  $contents = ob_get_contents();   // Get the contents of the buffer
  ob_end_clean();                  // End buffering and discard
  return $contents;                // Return the contents
}

/**
 * @defgroup themeable Default theme implementations
949
 * @{
950 951 952 953 954 955
 * Functions and templates that present output to the user, and can be
 * implemented by themes.
 *
 * Drupal's presentation layer is a pluggable system known as the theme
 * layer. Each theme can take control over most of Drupal's output, and
 * has complete control over the CSS.
956
 *
957 958 959
 * Inside Drupal, the theme layer is utilized by the use of the theme()
 * function, which is passed the name of a component (the theme hook)
 * and several arguments. For example, theme('table', $header, $rows);
960 961 962
 * Additionally, the theme() function can take an array of theme
 * hooks, which can be used to provide 'fallback' implementations to
 * allow for more specific control of output. For example, the function:
963
 * theme(array('table__foo', 'table'), $header, $rows) would look to see if
964 965 966 967
 * 'table__foo' is registered anywhere; if it is not, it would 'fall back'
 * to the generic 'table' implementation. This can be used to attach specific
 * theme functions to named objects, allowing the themer more control over
 * specific types of output.
968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005
 *
 * As of Drupal 6, every theme hook is required to be registered by the
 * module that owns it, so that Drupal can tell what to do with it and
 * to make it simple for themes to identify and override the behavior
 * for these calls.
 *
 * The theme hooks are registered via hook_theme(), which returns an
 * array of arrays with information about the hook. It describes the
 * arguments the function or template will need, and provides
 * defaults for the template in case they are not filled in. If the default
 * implementation is a function, by convention it is named theme_HOOK().
 *
 * Each module should provide a default implementation for themes that
 * it registers. This implementation may be either a function or a template;
 * if it is a function it must be specified via hook_theme(). By convention,
 * default implementations of theme hooks are named theme_HOOK. Default
 * template implementations are stored in the module directory.
 *
 * Drupal's default template renderer is a simple PHP parsing engine that
 * includes the template and stores the output. Drupal's theme engines
 * can provide alternate template engines, such as XTemplate, Smarty and
 * PHPTal. The most common template engine is PHPTemplate (included with
 * Drupal and implemented in phptemplate.engine, which uses Drupal's default
 * template renderer.
 *
 * In order to create theme-specific implementations of these hooks,
 * themes can implement their own version of theme hooks, either as functions
 * or templates. These implementations will be used instead of the default
 * implementation. If using a pure .theme without an engine, the .theme is
 * required to implement its own version of hook_theme() to tell Drupal what
 * it is implementing; themes utilizing an engine will have their well-named
 * theming functions automatically registered for them. While this can vary
 * based upon the theme engine, the standard set by phptemplate is that theme
 * functions should be named either phptemplate_HOOK or THEMENAME_HOOK. For
 * example, for Drupal's default theme (Garland) to implement the 'table' hook,
 * the phptemplate.engine would find phptemplate_table() or garland_table().
 * The ENGINE_HOOK() syntax is preferred, as this can be used by sub-themes
 * (which are themes that share code but use different stylesheets).
1006
 *
Dries's avatar
 
Dries committed
1007
 * The theme system is described and defined in theme.inc.
1008 1009 1010
 *
 * @see theme()
 * @see hook_theme()
1011
 */
1012

1013
/**
1014 1015
 * Formats text for emphasized display in a placeholder inside a sentence.
 * Used automatically by t().
1016 1017 1018 1019 1020 1021 1022 1023 1024
 *
 * @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
1025

Dries's avatar
 
Dries committed
1026
/**
1027
 * Return a themed set of status and/or error messages. The messages are grouped
Dries's avatar
 
Dries committed
1028 1029
 * by type.
 *
1030 1031 1032
 * @param $display
 *   (optional) Set to 'status' or 'error' to display only messages of that type.
 *
1033 1034
 * @return
 *   A string containing the messages.
Dries's avatar
 
Dries committed
1035
 */
1036 1037 1038 1039 1040 1041
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";
1042
      foreach ($messages as $message) {
1043
        $output .= '  <li>'. $message ."</li>\n";
Dries's avatar
 
Dries committed
1044
      }
1045
      $output .= " </ul>\n";
Dries's avatar
 
Dries committed
1046
    }
1047 1048 1049 1050
    else {
      $output .= $messages[0];
    }
    $output .= "</div>\n";
Dries's avatar
 
Dries committed
1051
  }
1052
  return $output;
Dries's avatar
 
Dries committed
1053 1054
}

Dries's avatar
 
Dries committed
1055
/**
1056 1057 1058
 * Return a themed set of links.
 *
 * @param $links
1059
 *   A keyed array of links to be themed.
1060 1061
 * @param $attributes
 *   A keyed array of attributes
1062
 * @return
1063
 *   A string containing an unordered list of links.
Dries's avatar
 
Dries committed
1064
 */
1065 1066
function theme_links($links, $attributes = array('class' => 'links')) {
  $output = '';
1067

1068
  if (count($links) > 0) {
1069 1070 1071 1072 1073
    $output = '<ul'. drupal_attributes($attributes) .'>';

    $num_links = count($links);
    $i = 1;

1074
    foreach ($links as $key => $link) {
1075
      $class = $key;
1076

1077
      // Add first, last and active classes to the list of links to help out themers.
1078
      if ($i == 1) {
1079
        $class .= ' first';
1080 1081
      }
      if ($i == $num_links) {
1082 1083
        $class .= ' last';
      }
1084
      if (isset($link['href']) && $link['href'] == $_GET['q']) {
1085
        $class .= ' active';
1086
      }
1087
      $output .= '<li class="'. $class .'">';
1088

1089
      if (isset($link['href'])) {
1090 1091
        // Pass in $link as $options, they share the same keys.
        $output .= l($link['title'], $link['href'], $link);
1092
      }
1093
      else if (!empty($link['title'])) {
1094 1095
        // Some links are actually not links, but we wrap these in <span> for adding title and class attributes
        if (empty($link['html'])) {
1096 1097
          $link['title'] = check_plain($link['title']);
        }
1098 1099 1100 1101 1102
        $span_attributes = '';
        if (isset($link['attributes'])) {
          $span_attributes = drupal_attributes($link['attributes']);
        }
        $output .= '<span'. $span_attributes .'>'. $link['title'] .'</span>';
1103
      }
1104 1105

      $i++;
1106
      $output .= "</li>\n";
1107
    }
1108 1109

    $output .= '</ul>';
1110
  }
1111

1112
  return $output;
Dries's avatar
 
Dries committed
1113
}
Dries's avatar
 
Dries committed
1114

Dries's avatar
 
Dries committed
1115
/**
1116
 * Return a themed image.
Dries's avatar
 
Dries committed
1117
 *
Dries's avatar
 
Dries committed
1118
 * @param $path
1119
 *   Either the path of the image file (relative to base_path()) or a full URL.
Dries's avatar
 
Dries committed
1120 1121 1122 1123
 * @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.
Dries's avatar