| [ Index ] |
PHP Cross Reference of Drupal 6 (gatewave) |
[Summary view] [Print] [Text view]
1 <?php 2 // $Id: theme.inc,v 1.415.2.28 2010/11/03 19:03:19 goba Exp $ 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 * @ingroup themeable 12 */ 13 14 /** 15 * @name Content markers 16 * @{ 17 * Markers used by theme_mark() and node_mark() to designate content. 18 * @see theme_mark(), node_mark() 19 */ 20 define('MARK_READ', 0); 21 define('MARK_NEW', 1); 22 define('MARK_UPDATED', 2); 23 /** 24 * @} End of "Content markers". 25 */ 26 27 /** 28 * Initialize the theme system by loading the theme. 29 */ 30 function init_theme() { 31 global $theme, $user, $custom_theme, $theme_key; 32 33 // If $theme is already set, assume the others are set, too, and do nothing 34 if (isset($theme)) { 35 return; 36 } 37 38 drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE); 39 $themes = list_themes(); 40 41 // Only select the user selected theme if it is available in the 42 // list of enabled themes. 43 $theme = !empty($user->theme) && !empty($themes[$user->theme]->status) ? $user->theme : variable_get('theme_default', 'garland'); 44 45 // Allow modules to override the present theme... only select custom theme 46 // if it is available in the list of installed themes. 47 $theme = $custom_theme && $themes[$custom_theme] ? $custom_theme : $theme; 48 49 // Store the identifier for retrieving theme settings with. 50 $theme_key = $theme; 51 52 // Find all our ancestor themes and put them in an array. 53 $base_theme = array(); 54 $ancestor = $theme; 55 while ($ancestor && isset($themes[$ancestor]->base_theme)) { 56 $base_theme[] = $new_base_theme = $themes[$themes[$ancestor]->base_theme]; 57 $ancestor = $themes[$ancestor]->base_theme; 58 } 59 _init_theme($themes[$theme], array_reverse($base_theme)); 60 } 61 62 /** 63 * Initialize the theme system given already loaded information. This 64 * function is useful to initialize a theme when no database is present. 65 * 66 * @param $theme 67 * An object with the following information: 68 * filename 69 * The .info file for this theme. The 'path' to 70 * the theme will be in this file's directory. (Required) 71 * owner 72 * The path to the .theme file or the .engine file to load for 73 * the theme. (Required) 74 * stylesheet 75 * The primary stylesheet for the theme. (Optional) 76 * engine 77 * The name of theme engine to use. (Optional) 78 * @param $base_theme 79 * An optional array of objects that represent the 'base theme' if the 80 * theme is meant to be derivative of another theme. It requires 81 * the same information as the $theme object. It should be in 82 * 'oldest first' order, meaning the top level of the chain will 83 * be first. 84 * @param $registry_callback 85 * The callback to invoke to set the theme registry. 86 */ 87 function _init_theme($theme, $base_theme = array(), $registry_callback = '_theme_load_registry') { 88 global $theme_info, $base_theme_info, $theme_engine, $theme_path; 89 $theme_info = $theme; 90 $base_theme_info = $base_theme; 91 92 $theme_path = dirname($theme->filename); 93 94 // Prepare stylesheets from this theme as well as all ancestor themes. 95 // We work it this way so that we can have child themes override parent 96 // theme stylesheets easily. 97 $final_stylesheets = array(); 98 99 // Grab stylesheets from base theme 100 foreach ($base_theme as $base) { 101 if (!empty($base->stylesheets)) { 102 foreach ($base->stylesheets as $media => $stylesheets) { 103 foreach ($stylesheets as $name => $stylesheet) { 104 $final_stylesheets[$media][$name] = $stylesheet; 105 } 106 } 107 } 108 } 109 110 // Add stylesheets used by this theme. 111 if (!empty($theme->stylesheets)) { 112 foreach ($theme->stylesheets as $media => $stylesheets) { 113 foreach ($stylesheets as $name => $stylesheet) { 114 $final_stylesheets[$media][$name] = $stylesheet; 115 } 116 } 117 } 118 119 // And now add the stylesheets properly 120 foreach ($final_stylesheets as $media => $stylesheets) { 121 foreach ($stylesheets as $stylesheet) { 122 drupal_add_css($stylesheet, 'theme', $media); 123 } 124 } 125 126 // Do basically the same as the above for scripts 127 $final_scripts = array(); 128 129 // Grab scripts from base theme 130 foreach ($base_theme as $base) { 131 if (!empty($base->scripts)) { 132 foreach ($base->scripts as $name => $script) { 133 $final_scripts[$name] = $script; 134 } 135 } 136 } 137 138 // Add scripts used by this theme. 139 if (!empty($theme->scripts)) { 140 foreach ($theme->scripts as $name => $script) { 141 $final_scripts[$name] = $script; 142 } 143 } 144 145 // Add scripts used by this theme. 146 foreach ($final_scripts as $script) { 147 drupal_add_js($script, 'theme'); 148 } 149 150 $theme_engine = NULL; 151 152 // Initialize the theme. 153 if (isset($theme->engine)) { 154 // Include the engine. 155 include_once './'. $theme->owner; 156 157 $theme_engine = $theme->engine; 158 if (function_exists($theme_engine .'_init')) { 159 foreach ($base_theme as $base) { 160 call_user_func($theme_engine .'_init', $base); 161 } 162 call_user_func($theme_engine .'_init', $theme); 163 } 164 } 165 else { 166 // include non-engine theme files 167 foreach ($base_theme as $base) { 168 // Include the theme file or the engine. 169 if (!empty($base->owner)) { 170 include_once './'. $base->owner; 171 } 172 } 173 // and our theme gets one too. 174 if (!empty($theme->owner)) { 175 include_once './'. $theme->owner; 176 } 177 } 178 179 $registry_callback($theme, $base_theme, $theme_engine); 180 } 181 182 /** 183 * Retrieve the stored theme registry. If the theme registry is already 184 * in memory it will be returned; otherwise it will attempt to load the 185 * registry from cache. If this fails, it will construct the registry and 186 * cache it. 187 */ 188 function theme_get_registry($registry = NULL) { 189 static $theme_registry = NULL; 190 if (isset($registry)) { 191 $theme_registry = $registry; 192 } 193 194 return $theme_registry; 195 } 196 197 /** 198 * Store the theme registry in memory. 199 */ 200 function _theme_set_registry($registry) { 201 // Pass through for setting of static variable. 202 return theme_get_registry($registry); 203 } 204 205 /** 206 * Get the theme_registry cache from the database; if it doesn't exist, build 207 * it. 208 * 209 * @param $theme 210 * The loaded $theme object. 211 * @param $base_theme 212 * An array of loaded $theme objects representing the ancestor themes in 213 * oldest first order. 214 * @param theme_engine 215 * The name of the theme engine. 216 */ 217 function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL) { 218 // Check the theme registry cache; if it exists, use it. 219 $cache = cache_get("theme_registry:$theme->name", 'cache'); 220 if (isset($cache->data)) { 221 $registry = $cache->data; 222 } 223 else { 224 // If not, build one and cache it. 225 $registry = _theme_build_registry($theme, $base_theme, $theme_engine); 226 _theme_save_registry($theme, $registry); 227 } 228 _theme_set_registry($registry); 229 } 230 231 /** 232 * Write the theme_registry cache into the database. 233 */ 234 function _theme_save_registry($theme, $registry) { 235 cache_set("theme_registry:$theme->name", $registry); 236 } 237 238 /** 239 * Force the system to rebuild the theme registry; this should be called 240 * when modules are added to the system, or when a dynamic system needs 241 * to add more theme hooks. 242 */ 243 function drupal_rebuild_theme_registry() { 244 cache_clear_all('theme_registry', 'cache', TRUE); 245 } 246 247 /** 248 * Process a single invocation of the theme hook. $type will be one 249 * of 'module', 'theme_engine', 'base_theme_engine', 'theme', or 'base_theme' 250 * and it tells us some important information. 251 * 252 * Because $cache is a reference, the cache will be continually 253 * expanded upon; new entries will replace old entries in the 254 * array_merge, but we are careful to ensure some data is carried 255 * forward, such as the arguments a theme hook needs. 256 * 257 * An override flag can be set for preprocess functions. When detected the 258 * cached preprocessors for the hook will not be merged with the newly set. 259 * This can be useful to themes and theme engines by giving them more control 260 * over how and when the preprocess functions are run. 261 */ 262 function _theme_process_registry(&$cache, $name, $type, $theme, $path) { 263 $result = array(); 264 $function = $name .'_theme'; 265 if (function_exists($function)) { 266 $result = $function($cache, $type, $theme, $path); 267 268 foreach ($result as $hook => $info) { 269 $result[$hook]['type'] = $type; 270 $result[$hook]['theme path'] = $path; 271 // if function and file are left out, default to standard naming 272 // conventions. 273 if (!isset($info['template']) && !isset($info['function'])) { 274 $result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name .'_') . $hook; 275 } 276 277 // Make sure include files is set so we don't generate notices later. 278 if (!isset($info['include files'])) { 279 $result[$hook]['include files'] = array(); 280 } 281 282 // If a path is set in the info, use what was set. Otherwise use the 283 // default path. This is mostly so system.module can declare theme 284 // functions on behalf of core .include files. 285 // All files are included to be safe. Conditionally included 286 // files can prevent them from getting registered. 287 if (isset($info['file']) && !isset($info['path'])) { 288 // First, check to see if the fully qualified file exists. 289 $filename = './'. $path .'/'. $info['file']; 290 if (file_exists($filename)) { 291 require_once $filename; 292 $result[$hook]['include files'][] = $filename; 293 } 294 else { 295 $filename = './'. $info['file']; 296 if (file_exists($filename)) { 297 require_once $filename; 298 $result[$hook]['include files'][] = $filename; 299 } 300 } 301 } 302 elseif (isset($info['file']) && isset($info['path'])) { 303 $filename = './'. $info['path'] .'/'. $info['file']; 304 if (file_exists($filename)) { 305 require_once $filename; 306 $result[$hook]['include files'][] = $filename; 307 } 308 } 309 310 311 if (isset($info['template']) && !isset($info['path'])) { 312 $result[$hook]['template'] = $path .'/'. $info['template']; 313 } 314 // If 'arguments' have been defined previously, carry them forward. 315 // This should happen if a theme overrides a Drupal defined theme 316 // function, for example. 317 if (!isset($info['arguments']) && isset($cache[$hook])) { 318 $result[$hook]['arguments'] = $cache[$hook]['arguments']; 319 } 320 // Likewise with theme paths. These are used for template naming suggestions. 321 // Theme implementations can occur in multiple paths. Suggestions should follow. 322 if (!isset($info['theme paths']) && isset($cache[$hook])) { 323 $result[$hook]['theme paths'] = $cache[$hook]['theme paths']; 324 } 325 // Check for sub-directories. 326 $result[$hook]['theme paths'][] = isset($info['path']) ? $info['path'] : $path; 327 328 // Check for default _preprocess_ functions. Ensure arrayness. 329 if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) { 330 $info['preprocess functions'] = array(); 331 $prefixes = array(); 332 if ($type == 'module') { 333 // Default preprocessor prefix. 334 $prefixes[] = 'template'; 335 // Add all modules so they can intervene with their own preprocessors. This allows them 336 // to provide preprocess functions even if they are not the owner of the current hook. 337 $prefixes += module_list(); 338 } 339 elseif ($type == 'theme_engine' || $type == 'base_theme_engine') { 340 // Theme engines get an extra set that come before the normally named preprocessors. 341 $prefixes[] = $name .'_engine'; 342 // The theme engine also registers on behalf of the theme. The theme or engine name can be used. 343 $prefixes[] = $name; 344 $prefixes[] = $theme; 345 } 346 else { 347 // This applies when the theme manually registers their own preprocessors. 348 $prefixes[] = $name; 349 } 350 351 foreach ($prefixes as $prefix) { 352 if (function_exists($prefix .'_preprocess')) { 353 $info['preprocess functions'][] = $prefix .'_preprocess'; 354 } 355 356 if (function_exists($prefix .'_preprocess_'. $hook)) { 357 $info['preprocess functions'][] = $prefix .'_preprocess_'. $hook; 358 } 359 360 if (!empty($info['original hook']) && function_exists($prefix .'_preprocess_'. $info['original hook'])) { 361 $info['preprocess functions'][] = $prefix .'_preprocess_'. $info['original hook']; 362 } 363 } 364 } 365 // Check for the override flag and prevent the cached preprocess functions from being used. 366 // This allows themes or theme engines to remove preprocessors set earlier in the registry build. 367 if (!empty($info['override preprocess functions'])) { 368 // Flag not needed inside the registry. 369 unset($result[$hook]['override preprocess functions']); 370 } 371 elseif (isset($cache[$hook]['preprocess functions']) && is_array($cache[$hook]['preprocess functions'])) { 372 $info['preprocess functions'] = array_merge($cache[$hook]['preprocess functions'], $info['preprocess functions']); 373 } 374 elseif (isset($info['original hook']) && isset($cache[$info['original hook']]['preprocess functions']) && is_array($cache[$info['original hook']]['preprocess functions'])) { 375 $info['preprocess functions'] = array_merge($cache[$info['original hook']]['preprocess functions'], $info['preprocess functions']); 376 } 377 $result[$hook]['preprocess functions'] = $info['preprocess functions']; 378 } 379 380 // Merge the newly created theme hooks into the existing cache. 381 $cache = array_merge($cache, $result); 382 } 383 384 // Let themes have preprocess functions even if they didn't register a template. 385 if ($type == 'theme' || $type == 'base_theme') { 386 foreach ($cache as $hook => $info) { 387 // Check only if it's a template and not registered by the theme or engine. 388 if (!empty($info['template']) && empty($result[$hook])) { 389 if (!isset($info['preprocess functions'])) { 390 $cache[$hook]['preprocess functions'] = array(); 391 } 392 if (function_exists($name .'_preprocess')) { 393 $cache[$hook]['preprocess functions'][] = $name .'_preprocess'; 394 } 395 if (function_exists($name .'_preprocess_'. $hook)) { 396 $cache[$hook]['preprocess functions'][] = $name .'_preprocess_'. $hook; 397 } 398 // Ensure uniqueness. 399 $cache[$hook]['preprocess functions'] = array_unique($cache[$hook]['preprocess functions']); 400 } 401 } 402 } 403 } 404 405 /** 406 * Rebuild the hook theme_registry cache. 407 * 408 * @param $theme 409 * The loaded $theme object. 410 * @param $base_theme 411 * An array of loaded $theme objects representing the ancestor themes in 412 * oldest first order. 413 * @param theme_engine 414 * The name of the theme engine. 415 */ 416 function _theme_build_registry($theme, $base_theme, $theme_engine) { 417 $cache = array(); 418 // First, process the theme hooks advertised by modules. This will 419 // serve as the basic registry. 420 foreach (module_implements('theme') as $module) { 421 _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module)); 422 } 423 424 // Process each base theme. 425 foreach ($base_theme as $base) { 426 // If the base theme uses a theme engine, process its hooks. 427 $base_path = dirname($base->filename); 428 if ($theme_engine) { 429 _theme_process_registry($cache, $theme_engine, 'base_theme_engine', $base->name, $base_path); 430 } 431 _theme_process_registry($cache, $base->name, 'base_theme', $base->name, $base_path); 432 } 433 434 // And then the same thing, but for the theme. 435 if ($theme_engine) { 436 _theme_process_registry($cache, $theme_engine, 'theme_engine', $theme->name, dirname($theme->filename)); 437 } 438 439 // Finally, hooks provided by the theme itself. 440 _theme_process_registry($cache, $theme->name, 'theme', $theme->name, dirname($theme->filename)); 441 442 // Let modules alter the registry 443 drupal_alter('theme_registry', $cache); 444 return $cache; 445 } 446 447 /** 448 * Provides a list of currently available themes. 449 * 450 * If the database is active then it will be retrieved from the database. 451 * Otherwise it will retrieve a new list. 452 * 453 * @param $refresh 454 * Whether to reload the list of themes from the database. 455 * @return 456 * An array of the currently available themes. 457 */ 458 function list_themes($refresh = FALSE) { 459 static $list = array(); 460 461 if ($refresh) { 462 $list = array(); 463 } 464 465 if (empty($list)) { 466 $list = array(); 467 $themes = array(); 468 // Extract from the database only when it is available. 469 // Also check that the site is not in the middle of an install or update. 470 if (db_is_active() && !defined('MAINTENANCE_MODE')) { 471 $result = db_query("SELECT * FROM {system} WHERE type = '%s'", 'theme'); 472 while ($theme = db_fetch_object($result)) { 473 if (file_exists($theme->filename)) { 474 $theme->info = unserialize($theme->info); 475 $themes[] = $theme; 476 } 477 } 478 } 479 else { 480 // Scan the installation when the database should not be read. 481 $themes = _system_theme_data(); 482 } 483 484 foreach ($themes as $theme) { 485 foreach ($theme->info['stylesheets'] as $media => $stylesheets) { 486 foreach ($stylesheets as $stylesheet => $path) { 487 $theme->stylesheets[$media][$stylesheet] = $path; 488 } 489 } 490 foreach ($theme->info['scripts'] as $script => $path) { 491 if (file_exists($path)) { 492 $theme->scripts[$script] = $path; 493 } 494 } 495 if (isset($theme->info['engine'])) { 496 $theme->engine = $theme->info['engine']; 497 } 498 if (isset($theme->info['base theme'])) { 499 $theme->base_theme = $theme->info['base theme']; 500 } 501 // Status is normally retrieved from the database. Add zero values when 502 // read from the installation directory to prevent notices. 503 if (!isset($theme->status)) { 504 $theme->status = 0; 505 } 506 $list[$theme->name] = $theme; 507 } 508 } 509 510 return $list; 511 } 512 513 /** 514 * Generate the themed output. 515 * 516 * All requests for theme hooks must go through this function. It examines 517 * the request and routes it to the appropriate theme function. The theme 518 * registry is checked to determine which implementation to use, which may 519 * be a function or a template. 520 * 521 * If the implementation is a function, it is executed and its return value 522 * passed along. 523 * 524 * If the implementation is a template, the arguments are converted to a 525 * $variables array. This array is then modified by the module implementing 526 * the hook, theme engine (if applicable) and the theme. The following 527 * functions may be used to modify the $variables array. They are processed in 528 * this order when available: 529 * 530 * - template_preprocess(&$variables) 531 * This sets a default set of variables for all template implementations. 532 * 533 * - template_preprocess_HOOK(&$variables) 534 * This is the first preprocessor called specific to the hook; it should be 535 * implemented by the module that registers it. 536 * 537 * - MODULE_preprocess(&$variables) 538 * This will be called for all templates; it should only be used if there 539 * is a real need. It's purpose is similar to template_preprocess(). 540 * 541 * - MODULE_preprocess_HOOK(&$variables) 542 * This is for modules that want to alter or provide extra variables for 543 * theming hooks not registered to itself. For example, if a module named 544 * "foo" wanted to alter the $submitted variable for the hook "node" a 545 * preprocess function of foo_preprocess_node() can be created to intercept 546 * and alter the variable. 547 * 548 * - ENGINE_engine_preprocess(&$variables) 549 * This function should only be implemented by theme engines and exists 550 * so that it can set necessary variables for all hooks. 551 * 552 * - ENGINE_engine_preprocess_HOOK(&$variables) 553 * This is the same as the previous function, but it is called for a single 554 * theming hook. 555 * 556 * - ENGINE_preprocess(&$variables) 557 * This is meant to be used by themes that utilize a theme engine. It is 558 * provided so that the preprocessor is not locked into a specific theme. 559 * This makes it easy to share and transport code but theme authors must be 560 * careful to prevent fatal re-declaration errors when using sub-themes that 561 * have their own preprocessor named exactly the same as its base theme. In 562 * the default theme engine (PHPTemplate), sub-themes will load their own 563 * template.php file in addition to the one used for its parent theme. This 564 * increases the risk for these errors. A good practice is to use the engine 565 * name for the base theme and the theme name for the sub-themes to minimize 566 * this possibility. 567 * 568 * - ENGINE_preprocess_HOOK(&$variables) 569 * The same applies from the previous function, but it is called for a 570 * specific hook. 571 * 572 * - THEME_preprocess(&$variables) 573 * These functions are based upon the raw theme; they should primarily be 574 * used by themes that do not use an engine or by sub-themes. It serves the 575 * same purpose as ENGINE_preprocess(). 576 * 577 * - THEME_preprocess_HOOK(&$variables) 578 * The same applies from the previous function, but it is called for a 579 * specific hook. 580 * 581 * There are two special variables that these hooks can set: 582 * 'template_file' and 'template_files'. These will be merged together 583 * to form a list of 'suggested' alternate template files to use, in 584 * reverse order of priority. template_file will always be a higher 585 * priority than items in template_files. theme() will then look for these 586 * files, one at a time, and use the first one 587 * that exists. 588 * @param $hook 589 * The name of the theme function to call. May be an array, in which 590 * case the first hook that actually has an implementation registered 591 * will be used. This can be used to choose 'fallback' theme implementations, 592 * so that if the specific theme hook isn't implemented anywhere, a more 593 * generic one will be used. This can allow themes to create specific theme 594 * implementations for named objects. 595 * @param ... 596 * Additional arguments to pass along to the theme function. 597 * @return 598 * An HTML string that generates the themed output. 599 */ 600 function theme() { 601 $args = func_get_args(); 602 $hook = array_shift($args); 603 604 static $hooks = NULL; 605 if (!isset($hooks)) { 606 init_theme(); 607 $hooks = theme_get_registry(); 608 } 609 610 if (is_array($hook)) { 611 foreach ($hook as $candidate) { 612 if (isset($hooks[$candidate])) { 613 break; 614 } 615 } 616 $hook = $candidate; 617 } 618 619 if (!isset($hooks[$hook])) { 620 return; 621 } 622 623 $info = $hooks[$hook]; 624 global $theme_path; 625 $temp = $theme_path; 626 // point path_to_theme() to the currently used theme path: 627 $theme_path = $hooks[$hook]['theme path']; 628 629 // Include a file if the theme function or preprocess function is held elsewhere. 630 if (!empty($info['include files'])) { 631 foreach ($info['include files'] as $include_file) { 632 include_once($include_file); 633 } 634 } 635 636 // Handle compatibility with theme_registry_alters to prevent failures. 637 if (!empty($info['file'])) { 638 static $included_files = array(); 639 $include_file = $info['file']; 640 if (!empty($info['path'])) { 641 $include_file = $info['path'] .'/'. $include_file; 642 } 643 644 if (empty($included_files[$include_file])) { 645 // Statically cache files we've already tried to include so we don't 646 // run unnecessary file_exists calls. 647 $included_files[$include_file] = TRUE; 648 if (file_exists('./'. $include_file)) { 649 include_once('./'. $include_file); 650 } 651 } 652 } 653 654 if (isset($info['function'])) { 655 // The theme call is a function. 656 $output = call_user_func_array($info['function'], $args); 657 } 658 else { 659 // The theme call is a template. 660 $variables = array( 661 'template_files' => array() 662 ); 663 if (!empty($info['arguments'])) { 664 $count = 0; 665 foreach ($info['arguments'] as $name => $default) { 666 $variables[$name] = isset($args[$count]) ? $args[$count] : $default; 667 $count++; 668 } 669 } 670 671 // default render function and extension. 672 $render_function = 'theme_render_template'; 673 $extension = '.tpl.php'; 674 675 // Run through the theme engine variables, if necessary 676 global $theme_engine; 677 if (isset($theme_engine)) { 678 // If theme or theme engine is implementing this, it may have 679 // a different extension and a different renderer. 680 if ($hooks[$hook]['type'] != 'module') { 681 if (function_exists($theme_engine .'_render_template')) { 682 $render_function = $theme_engine .'_render_template'; 683 } 684 $extension_function = $theme_engine .'_extension'; 685 if (function_exists($extension_function)) { 686 $extension = $extension_function(); 687 } 688 } 689 } 690 691 if (isset($info['preprocess functions']) && is_array($info['preprocess functions'])) { 692 // This construct ensures that we can keep a reference through 693 // call_user_func_array. 694 $args = array(&$variables, $hook); 695 foreach ($info['preprocess functions'] as $preprocess_function) { 696 if (function_exists($preprocess_function)) { 697 call_user_func_array($preprocess_function, $args); 698 } 699 } 700 } 701 702 // Get suggestions for alternate templates out of the variables 703 // that were set. This lets us dynamically choose a template 704 // from a list. The order is FILO, so this array is ordered from 705 // least appropriate first to most appropriate last. 706 $suggestions = array(); 707 708 if (isset($variables['template_files'])) { 709 $suggestions = $variables['template_files']; 710 } 711 if (isset($variables['template_file'])) { 712 $suggestions[] = $variables['template_file']; 713 } 714 715 if ($suggestions) { 716 $template_file = drupal_discover_template($info['theme paths'], $suggestions, $extension); 717 } 718 719 if (empty($template_file)) { 720 $template_file = $hooks[$hook]['template'] . $extension; 721 if (isset($hooks[$hook]['path'])) { 722 $template_file = $hooks[$hook]['path'] .'/'. $template_file; 723 } 724 } 725 $output = $render_function($template_file, $variables); 726 } 727 // restore path_to_theme() 728 $theme_path = $temp; 729 // Add final markup to the full page. 730 if ($hook == 'page' || $hook == 'book_export_html') { 731 $output = drupal_final_markup($output); 732 } 733 return $output; 734 } 735 736 /** 737 * Choose which template file to actually render. These are all suggested 738 * templates from themes and modules. Theming implementations can occur on 739 * multiple levels. All paths are checked to account for this. 740 */ 741 function drupal_discover_template($paths, $suggestions, $extension = '.tpl.php') { 742 global $theme_engine; 743 744 // Remove slashes or null to prevent files from being included from 745 // an unexpected location (especially on Windows servers). 746 $extension = str_replace(array("/", "\\", "\0"), '', $extension); 747 748 // Loop through all paths and suggestions in FIFO order. 749 $suggestions = array_reverse($suggestions); 750 $paths = array_reverse($paths); 751 foreach ($suggestions as $suggestion) { 752 if (!empty($suggestion)) { 753 $suggestion = str_replace(array("/", "\\", "\0"), '', $suggestion); 754 foreach ($paths as $path) { 755 if (file_exists($file = $path .'/'. $suggestion . $extension)) { 756 return $file; 757 } 758 } 759 } 760 } 761 } 762 763 /** 764 * Return the path to the current themed element. 765 * 766 * It can point to the active theme or the module handling a themed implementation. 767 * For example, when invoked within the scope of a theming call it will depend 768 * on where the theming function is handled. If implemented from a module, it 769 * will point to the module. If implemented from the active theme, it will point 770 * to the active theme. When called outside the scope of a theming call, it will 771 * always point to the active theme. 772 */ 773 function path_to_theme() { 774 global $theme_path; 775 776 if (!isset($theme_path)) { 777 init_theme(); 778 } 779 780 return $theme_path; 781 } 782 783 /** 784 * Find overridden theme functions. Called by themes and/or theme engines to 785 * easily discover theme functions. 786 * 787 * @param $cache 788 * The existing cache of theme hooks to test against. 789 * @param $prefixes 790 * An array of prefixes to test, in reverse order of importance. 791 * 792 * @return $templates 793 * The functions found, suitable for returning from hook_theme; 794 */ 795 function drupal_find_theme_functions($cache, $prefixes) { 796 $templates = array(); 797 $functions = get_defined_functions(); 798 799 foreach ($cache as $hook => $info) { 800 foreach ($prefixes as $prefix) { 801 if (!empty($info['pattern'])) { 802 $matches = preg_grep('/^'. $prefix .'_'. $info['pattern'] .'/', $functions['user']); 803 if ($matches) { 804 foreach ($matches as $match) { 805 $new_hook = str_replace($prefix .'_', '', $match); 806 $templates[$new_hook] = array( 807 'function' => $match, 808 'arguments' => $info['arguments'], 809 'original hook' => $hook, 810 'include files' => $info['include files'], 811 ); 812 } 813 } 814 } 815 816 if (function_exists($prefix .'_'. $hook)) { 817 $templates[$hook] = array( 818 'function' => $prefix .'_'. $hook, 819 'include files' => $info['include files'], 820 ); 821 // Ensure that the pattern is maintained from base themes to its sub-themes. 822 // Each sub-theme will have their functions scanned so the pattern must be 823 // held for subsequent runs. 824 if (isset($info['pattern'])) { 825 $templates[$hook]['pattern'] = $info['pattern']; 826 } 827 // Also ensure that the 'file' property is maintained, because it probably 828 // contains the preprocess. 829 } 830 } 831 } 832 833 return $templates; 834 } 835 836 /** 837 * Find overridden theme templates. Called by themes and/or theme engines to 838 * easily discover templates. 839 * 840 * @param $cache 841 * The existing cache of theme hooks to test against. 842 * @param $extension 843 * The extension that these templates will have. 844 * @param $path 845 * The path to search. 846 */ 847 function drupal_find_theme_templates($cache, $extension, $path) { 848 $templates = array(); 849 850 // Collect paths to all sub-themes grouped by base themes. These will be 851 // used for filtering. This allows base themes to have sub-themes in its 852 // folder hierarchy without affecting the base themes template discovery. 853 $theme_paths = array(); 854 foreach (list_themes() as $theme_info) { 855 if (!empty($theme_info->base_theme)) { 856 $theme_paths[$theme_info->base_theme][$theme_info->name] = dirname($theme_info->filename); 857 } 858 } 859 foreach ($theme_paths as $basetheme => $subthemes) { 860 foreach ($subthemes as $subtheme => $subtheme_path) { 861 if (isset($theme_paths[$subtheme])) { 862 $theme_paths[$basetheme] = array_merge($theme_paths[$basetheme], $theme_paths[$subtheme]); 863 } 864 } 865 } 866 global $theme; 867 $subtheme_paths = isset($theme_paths[$theme]) ? $theme_paths[$theme] : array(); 868 869 // Escape the periods in the extension. 870 $regex = str_replace('.', '\.', $extension) .'$'; 871 // Because drupal_system_listing works the way it does, we check for real 872 // templates separately from checking for patterns. 873 $files = drupal_system_listing($regex, $path, 'name', 0); 874 foreach ($files as $template => $file) { 875 // Ignore sub-theme templates for the current theme. 876 if (strpos($file->filename, str_replace($subtheme_paths, '', $file->filename)) !== 0) { 877 continue; 878 } 879 // Chop off the remaining extensions if there are any. $template already 880 // has the rightmost extension removed, but there might still be more, 881 // such as with .tpl.php, which still has .tpl in $template at this point. 882 if (($pos = strpos($template, '.')) !== FALSE) { 883 $template = substr($template, 0, $pos); 884 } 885 // Transform - in filenames to _ to match function naming scheme 886 // for the purposes of searching. 887 $hook = strtr($template, '-', '_'); 888 if (isset($cache[$hook])) { 889 $templates[$hook] = array( 890 'template' => $template, 891 'path' => dirname($file->filename), 892 'include files' => $cache[$hook]['include files'], 893 ); 894 } 895 // Ensure that the pattern is maintained from base themes to its sub-themes. 896 // Each sub-theme will have their templates scanned so the pattern must be 897 // held for subsequent runs. 898 if (isset($cache[$hook]['pattern'])) { 899 $templates[$hook]['pattern'] = $cache[$hook]['pattern']; 900 } 901 } 902 903 $patterns = array_keys($files); 904 905 foreach ($cache as $hook => $info) { 906 if (!empty($info['pattern'])) { 907 // Transform _ in pattern to - to match file naming scheme 908 // for the purposes of searching. 909 $pattern = strtr($info['pattern'], '_', '-'); 910 911 $matches = preg_grep('/^'. $pattern .'/', $patterns); 912 if ($matches) { 913 foreach ($matches as $match) { 914 $file = substr($match, 0, strpos($match, '.')); 915 // Put the underscores back in for the hook name and register this pattern. 916 $templates[strtr($file, '-', '_')] = array( 917 'template' => $file, 918 'path' => dirname($files[$match]->filename), 919 'arguments' => $info['arguments'], 920 'original hook' => $hook, 921 'include files' => $info['include files'], 922 ); 923 } 924 } 925 } 926 } 927 return $templates; 928 } 929 930 /** 931 * Retrieve an associative array containing the settings for a theme. 932 * 933 * The final settings are arrived at by merging the default settings, 934 * the site-wide settings, and the settings defined for the specific theme. 935 * If no $key was specified, only the site-wide theme defaults are retrieved. 936 * 937 * The default values for each of settings are also defined in this function. 938 * To add new settings, add their default values here, and then add form elements 939 * to system_theme_settings() in system.module. 940 * 941 * @param $key 942 * The template/style value for a given theme. 943 * 944 * @return 945 * An associative array containing theme settings. 946 */ 947 function theme_get_settings($key = NULL) { 948 $defaults = array( 949 'mission' => '', 950 'default_logo' => 1, 951 'logo_path' => '', 952 'default_favicon' => 1, 953 'favicon_path' => '', 954 'primary_links' => 1, 955 'secondary_links' => 1, 956 'toggle_logo' => 1, 957 'toggle_favicon' => 1, 958 'toggle_name' => 1, 959 'toggle_search' => 1, 960 'toggle_slogan' => 0, 961 'toggle_mission' => 1, 962 'toggle_node_user_picture' => 0, 963 'toggle_comment_user_picture' => 0, 964 'toggle_primary_links' => 1, 965 'toggle_secondary_links' => 1, 966 ); 967 968 if (module_exists('node')) { 969 foreach (node_get_types() as $type => $name) { 970 $defaults['toggle_node_info_'. $type] = 1; 971 } 972 } 973 $settings = array_merge($defaults, variable_get('theme_settings', array())); 974 975 if ($key) { 976 $settings = array_merge($settings, variable_get(str_replace('/', '_', 'theme_'. $key .'_settings'), array())); 977 } 978 979 // Only offer search box if search.module is enabled. 980 if (!module_exists('search') || !user_access('search content')) { 981 $settings['toggle_search'] = 0; 982 } 983 984 return $settings; 985 } 986 987 /** 988 * Retrieve a setting for the current theme. 989 * This function is designed for use from within themes & engines 990 * to determine theme settings made in the admin interface. 991 * 992 * Caches values for speed (use $refresh = TRUE to refresh cache) 993 * 994 * @param $setting_name 995 * The name of the setting to be retrieved. 996 * 997 * @param $refresh 998 * Whether to reload the cache of settings. 999 * 1000 * @return 1001 * The value of the requested setting, NULL if the setting does not exist. 1002 */ 1003 function theme_get_setting($setting_name, $refresh = FALSE) { 1004 global $theme_key; 1005 static $settings; 1006 1007 if (empty($settings) || $refresh) { 1008 $settings = theme_get_settings($theme_key); 1009 1010 $themes = list_themes(); 1011 $theme_object = $themes[$theme_key]; 1012 1013 if ($settings['mission'] == '') { 1014 $settings['mission'] = variable_get('site_mission', ''); 1015 } 1016 1017 if (!$settings['toggle_mission']) { 1018 $settings['mission'] = ''; 1019 } 1020 1021 if ($settings['toggle_logo']) { 1022 if ($settings['default_logo']) { 1023 $settings['logo'] = base_path() . dirname($theme_object->filename) .'/logo.png'; 1024 } 1025 elseif ($settings['logo_path']) { 1026 $settings['logo'] = base_path() . $settings['logo_path']; 1027 } 1028 } 1029 1030 if ($settings['toggle_favicon']) { 1031 if ($settings['default_favicon']) { 1032 if (file_exists($favicon = dirname($theme_object->filename) .'/favicon.ico')) { 1033 $settings['favicon'] = base_path() . $favicon; 1034 } 1035 else { 1036 $settings['favicon'] = base_path() .'misc/favicon.ico'; 1037 } 1038 } 1039 elseif ($settings['favicon_path']) { 1040 $settings['favicon'] = base_path() . $settings['favicon_path']; 1041 } 1042 else { 1043 $settings['toggle_favicon'] = FALSE; 1044 } 1045 } 1046 } 1047 1048 return isset($settings[$setting_name]) ? $settings[$setting_name] : NULL; 1049 } 1050 1051 /** 1052 * Render a system default template, which is essentially a PHP template. 1053 * 1054 * @param $template_file 1055 * The filename of the template to render. Note that this will overwrite 1056 * anything stored in $variables['template_file'] if using a preprocess hook. 1057 * @param $variables 1058 * A keyed array of variables that will appear in the output. 1059 * 1060 * @return 1061 * The output generated by the template. 1062 */ 1063 function theme_render_template($template_file, $variables) { 1064 extract($variables, EXTR_SKIP); // Extract the variables to a local namespace 1065 ob_start(); // Start output buffering 1066 include "./$template_file"; // Include the template file 1067 $contents = ob_get_contents(); // Get the contents of the buffer 1068 ob_end_clean(); // End buffering and discard 1069 return $contents; // Return the contents 1070 } 1071 1072 /** 1073 * @defgroup themeable Default theme implementations 1074 * @{ 1075 * Functions and templates that present output to the user, and can be 1076 * implemented by themes. 1077 * 1078 * Drupal's presentation layer is a pluggable system known as the theme 1079 * layer. Each theme can take control over most of Drupal's output, and 1080 * has complete control over the CSS. 1081 * 1082 * Inside Drupal, the theme layer is utilized by the use of the theme() 1083 * function, which is passed the name of a component (the theme hook) 1084 * and several arguments. For example, theme('table', $header, $rows); 1085 * Additionally, the theme() function can take an array of theme 1086 * hooks, which can be used to provide 'fallback' implementations to 1087 * allow for more specific control of output. For example, the function: 1088 * theme(array('table__foo', 'table'), $header, $rows) would look to see if 1089 * 'table__foo' is registered anywhere; if it is not, it would 'fall back' 1090 * to the generic 'table' implementation. This can be used to attach specific 1091 * theme functions to named objects, allowing the themer more control over 1092 * specific types of output. 1093 * 1094 * As of Drupal 6, every theme hook is required to be registered by the 1095 * module that owns it, so that Drupal can tell what to do with it and 1096 * to make it simple for themes to identify and override the behavior 1097 * for these calls. 1098 * 1099 * The theme hooks are registered via hook_theme(), which returns an 1100 * array of arrays with information about the hook. It describes the 1101 * arguments the function or template will need, and provides 1102 * defaults for the template in case they are not filled in. If the default 1103 * implementation is a function, by convention it is named theme_HOOK(). 1104 * 1105 * Each module should provide a default implementation for theme_hooks that 1106 * it registers. This implementation may be either a function or a template; 1107 * if it is a function it must be specified via hook_theme(). By convention, 1108 * default implementations of theme hooks are named theme_HOOK. Default 1109 * template implementations are stored in the module directory. 1110 * 1111 * Drupal's default template renderer is a simple PHP parsing engine that 1112 * includes the template and stores the output. Drupal's theme engines 1113 * can provide alternate template engines, such as XTemplate, Smarty and 1114 * PHPTal. The most common template engine is PHPTemplate (included with 1115 * Drupal and implemented in phptemplate.engine, which uses Drupal's default 1116 * template renderer. 1117 * 1118 * In order to create theme-specific implementations of these hooks, 1119 * themes can implement their own version of theme hooks, either as functions 1120 * or templates. These implementations will be used instead of the default 1121 * implementation. If using a pure .theme without an engine, the .theme is 1122 * required to implement its own version of hook_theme() to tell Drupal what 1123 * it is implementing; themes utilizing an engine will have their well-named 1124 * theming functions automatically registered for them. While this can vary 1125 * based upon the theme engine, the standard set by phptemplate is that theme 1126 * functions should be named either phptemplate_HOOK or THEMENAME_HOOK. For 1127 * example, for Drupal's default theme (Garland) to implement the 'table' hook, 1128 * the phptemplate.engine would find phptemplate_table() or garland_table(). 1129 * The ENGINE_HOOK() syntax is preferred, as this can be used by sub-themes 1130 * (which are themes that share code but use different stylesheets). 1131 * 1132 * The theme system is described and defined in theme.inc. 1133 * 1134 * @see theme() 1135 * @see hook_theme() 1136 */ 1137 1138 /** 1139 * Formats text for emphasized display in a placeholder inside a sentence. 1140 * Used automatically by t(). 1141 * 1142 * @param $text 1143 * The text to format (plain-text). 1144 * @return 1145 * The formatted text (html). 1146 */ 1147 function theme_placeholder($text) { 1148 return '<em>'. check_plain($text) .'</em>'; 1149 } 1150 1151 /** 1152 * Return a themed set of status and/or error messages. The messages are grouped 1153 * by type. 1154 * 1155 * @param $display 1156 * (optional) Set to 'status' or 'error' to display only messages of that type. 1157 * 1158 * @return 1159 * A string containing the messages. 1160 */ 1161 function theme_status_messages($display = NULL) { 1162 $output = ''; 1163 foreach (drupal_get_messages($display) as $type => $messages) { 1164 $output .= "<div class=\"messages $type\">\n"; 1165 if (count($messages) > 1) { 1166 $output .= " <ul>\n"; 1167 foreach ($messages as $message) { 1168 $output .= ' <li>'. $message ."</li>\n"; 1169 } 1170 $output .= " </ul>\n"; 1171 } 1172 else { 1173 $output .= $messages[0]; 1174 } 1175 $output .= "</div>\n"; 1176 } 1177 return $output; 1178 } 1179 1180 /** 1181 * Return a themed set of links. 1182 * 1183 * @param $links 1184 * A keyed array of links to be themed. 1185 * @param $attributes 1186 * A keyed array of attributes 1187 * @return 1188 * A string containing an unordered list of links. 1189 */ 1190 function theme_links($links, $attributes = array('class' => 'links')) { 1191 global $language; 1192 $output = ''; 1193 1194 if (count($links) > 0) { 1195 $output = '<ul'. drupal_attributes($attributes) .'>'; 1196 1197 $num_links = count($links); 1198 $i = 1; 1199 1200 foreach ($links as $key => $link) { 1201 $class = $key; 1202 1203 // Add first, last and active classes to the list of links to help out themers. 1204 if ($i == 1) { 1205 $class .= ' first'; 1206 } 1207 if ($i == $num_links) { 1208 $class .= ' last'; 1209 } 1210 if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page())) 1211 && (empty($link['language']) || $link['language']->language == $language->language)) { 1212 $class .= ' active'; 1213 } 1214 $output .= '<li'. drupal_attributes(array('class' => $class)) .'>'; 1215 1216 if (isset($link['href'])) { 1217 // Pass in $link as $options, they share the same keys. 1218 $output .= l($link['title'], $link['href'], $link); 1219 } 1220 else if (!empty($link['title'])) { 1221 // Some links are actually not links, but we wrap these in <span> for adding title and class attributes 1222 if (empty($link['html'])) { 1223 $link['title'] = check_plain($link['title']); 1224 } 1225 $span_attributes = ''; 1226 if (isset($link['attributes'])) { 1227 $span_attributes = drupal_attributes($link['attributes']); 1228 } 1229 $output .= '<span'. $span_attributes .'>'. $link['title'] .'</span>'; 1230 } 1231 1232 $i++; 1233 $output .= "</li>\n"; 1234 } 1235 1236 $output .= '</ul>'; 1237 } 1238 1239 return $output; 1240 } 1241 1242 /** 1243 * Return a themed image. 1244 * 1245 * @param $path 1246 * Either the path of the image file (relative to base_path()) or a full URL. 1247 * If this is a full URL, $getsize must be set to FALSE or nothing will be returned. 1248 * @param $alt 1249 * The alternative text for text-based browsers. 1250 * @param $title 1251 * The title text is displayed when the image is hovered in some popular browsers. 1252 * @param $attributes 1253 * Associative array of attributes to be placed in the img tag. 1254 * @param $getsize 1255 * If set to TRUE, the image's dimension are fetched and added as width/height attributes. 1256 * Defaults to TRUE. Must be set to FALSE if $path is a full URL. 1257 * 1258 * @return 1259 * A string containing the image tag. 1260 */ 1261 function theme_image($path, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE) { 1262 if (!$getsize || (is_file($path) && (list($width, $height, $type, $image_attributes) = @getimagesize($path)))) { 1263 $attributes = drupal_attributes($attributes); 1264 $url = (url($path) == $path) ? $path : (base_path() . $path); 1265 return '<img src="'. check_url($url) .'" alt="'. check_plain($alt) .'" title="'. check_plain($title) .'" '. (isset($image_attributes) ? $image_attributes : '') . $attributes .' />'; 1266 } 1267 } 1268 1269 /** 1270 * Return a themed breadcrumb trail. 1271 * 1272 * @param $breadcrumb 1273 * An array containing the breadcrumb links. 1274 * @return a string containing the breadcrumb output. 1275 */ 1276 function theme_breadcrumb($breadcrumb) { 1277 if (!empty($breadcrumb)) { 1278 return '<div class="breadcrumb">'. implode(' » ', $breadcrumb) .'</div>'; 1279 } 1280 } 1281 1282 /** 1283 * Return a themed help message. 1284 * 1285 * @return a string containing the helptext for the current page. 1286 */ 1287 function theme_help() { 1288 if ($help = menu_get_active_help()) { 1289 return '<div class="help">'. $help .'</div>'; 1290 } 1291 } 1292 1293 /** 1294 * Return a themed submenu, typically displayed under the tabs. 1295 * 1296 * @param $links 1297 * An array of links. 1298 */ 1299 function theme_submenu($links) { 1300 return '<div class="submenu">'. implode(' | ', $links) .'</div>'; 1301 } 1302 1303 /** 1304 * Return a themed table. 1305 * 1306 * @param $header 1307 * An array containing the table headers. Each element of the array can be 1308 * either a localized string or an associative array with the following keys: 1309 * - "data": The localized title of the table column. 1310 * - "field": The database field represented in the table column (required if 1311 * user is to be able to sort on this column). 1312 * - "sort": A default sort order for this column ("asc" or "desc"). 1313 * - Any HTML attributes, such as "colspan", to apply to the column header cell. 1314 * @param $rows 1315 * An array of table rows. Every row is an array of cells, or an associative 1316 * array with the following keys: 1317 * - "data": an array of cells 1318 * - Any HTML attributes, such as "class", to apply to the table row. 1319 * 1320 * Each cell can be either a string or an associative array with the following keys: 1321 * - "data": The string to display in the table cell. 1322 * - "header": Indicates this cell is a header. 1323 * - Any HTML attributes, such as "colspan", to apply to the table cell. 1324 * 1325 * Here's an example for $rows: 1326 * @code 1327 * $rows = array( 1328 * // Simple row 1329 * array( 1330 * 'Cell 1', 'Cell 2', 'Cell 3' 1331 * ), 1332 * // Row with attributes on the row and some of its cells. 1333 * array( 1334 * 'data' => array('Cell 1', array('data' => 'Cell 2', 'colspan' => 2)), 'class' => 'funky' 1335 * ) 1336 * ); 1337 * @endcode 1338 * 1339 * @param $attributes 1340 * An array of HTML attributes to apply to the table tag. 1341 * @param $caption 1342 * A localized string to use for the <caption> tag. 1343 * @return 1344 * An HTML string representing the table. 1345 */ 1346 function theme_table($header, $rows, $attributes = array(), $caption = NULL) { 1347 1348 // Add sticky headers, if applicable. 1349 if (count($header)) { 1350 drupal_add_js('misc/tableheader.js'); 1351 // Add 'sticky-enabled' class to the table to identify it for JS. 1352 // This is needed to target tables constructed by this function. 1353 $attributes['class'] = empty($attributes['class']) ? 'sticky-enabled' : ($attributes['class'] .' sticky-enabled'); 1354 } 1355 1356 $output = '<table'. drupal_attributes($attributes) .">\n"; 1357 1358 if (isset($caption)) { 1359 $output .= '<caption>'. $caption ."</caption>\n"; 1360 } 1361 1362 // Format the table header: 1363 if (count($header)) { 1364 $ts = tablesort_init($header); 1365 // HTML requires that the thead tag has tr tags in it followed by tbody 1366 // tags. Using ternary operator to check and see if we have any rows. 1367 $output .= (count($rows) ? ' <thead><tr>' : ' <tr>'); 1368 foreach ($header as $cell) { 1369 $cell = tablesort_header($cell, $header, $ts); 1370 $output .= _theme_table_cell($cell, TRUE); 1371 } 1372 // Using ternary operator to close the tags based on whether or not there are rows 1373 $output .= (count($rows) ? " </tr></thead>\n" : "</tr>\n"); 1374 } 1375 else { 1376 $ts = array(); 1377 } 1378 1379 // Format the table rows: 1380 if (count($rows)) { 1381 $output .= "<tbody>\n"; 1382 $flip = array('even' => 'odd', 'odd' => 'even'); 1383 $class = 'even'; 1384 foreach ($rows as $number => $row) { 1385 $attributes = array(); 1386 1387 // Check if we're dealing with a simple or complex row 1388 if (isset($row['data'])) { 1389 foreach ($row as $key => $value) { 1390 if ($key == 'data') { 1391 $cells = $value; 1392 } 1393 else { 1394 $attributes[$key] = $value; 1395 } 1396 } 1397 } 1398 else { 1399 $cells = $row; 1400 } 1401 if (count($cells)) { 1402 // Add odd/even class 1403 $class = $flip[$class]; 1404 if (isset($attributes['class'])) { 1405 $attributes['class'] .= ' '. $class; 1406 } 1407 else { 1408 $attributes['class'] = $class; 1409 } 1410 1411 // Build row 1412 $output .= ' <tr'. drupal_attributes($attributes) .'>'; 1413 $i = 0; 1414 foreach ($cells as $cell) { 1415 $cell = tablesort_cell($cell, $header, $ts, $i++); 1416 $output .= _theme_table_cell($cell); 1417 } 1418 $output .= " </tr>\n"; 1419 } 1420 } 1421 $output .= "</tbody>\n"; 1422 } 1423 1424 $output .= "</table>\n"; 1425 return $output; 1426 } 1427 1428 /** 1429 * Returns a header cell for tables that have a select all functionality. 1430 */ 1431 function theme_table_select_header_cell() { 1432 drupal_add_js('misc/tableselect.js'); 1433 1434 return array('class' => 'select-all'); 1435 } 1436 1437 /** 1438 * Return a themed sort icon. 1439 * 1440 * @param $style 1441 * Set to either asc or desc. This sets which icon to show. 1442 * @return 1443 * A themed sort icon. 1444 */ 1445 function theme_tablesort_indicator($style) { 1446 if ($style == "asc") { 1447 return theme('image', 'misc/arrow-asc.png', t('sort icon'), t('sort ascending')); 1448 } 1449 else { 1450 return theme('image', 'misc/arrow-desc.png', t('sort icon'), t('sort descending')); 1451 } 1452 } 1453 1454 /** 1455 * Return a themed box. 1456 * 1457 * @param $title 1458 * The subject of the box. 1459 * @param $content 1460 * The content of the box. 1461 * @param $region 1462 * The region in which the box is displayed. 1463 * @return 1464 * A string containing the box output. 1465 */ 1466 function theme_box($title, $content, $region = 'main') { 1467 $output = '<h2 class="title">'. $title .'</h2><div>'. $content .'</div>'; 1468 return $output; 1469 } 1470 1471 /** 1472 * Return a themed marker, useful for marking new or updated 1473 * content. 1474 * 1475 * @param $type 1476 * Number representing the marker type to display 1477 * @see MARK_NEW, MARK_UPDATED, MARK_READ 1478 * @return 1479 * A string containing the marker. 1480 */ 1481 function theme_mark($type = MARK_NEW) { 1482 global $user; 1483 if ($user->uid) { 1484 if ($type == MARK_NEW) { 1485 return ' <span class="marker">'. t('new') .'</span>'; 1486 } 1487 else if ($type == MARK_UPDATED) { 1488 return ' <span class="marker">'. t('updated') .'</span>'; 1489 } 1490 } 1491 } 1492 1493 /** 1494 * Return a themed list of items. 1495 * 1496 * @param $items 1497 * An array of items to be displayed in the list. If an item is a string, 1498 * then it is used as is. If an item is an array, then the "data" element of 1499 * the array is used as the contents of the list item. If an item is an array 1500 * with a "children" element, those children are displayed in a nested list. 1501 * All other elements are treated as attributes of the list item element. 1502 * @param $title 1503 * The title of the list. 1504 * @param $type 1505 * The type of list to return (e.g. "ul", "ol") 1506 * @param $attributes 1507 * The attributes applied to the list element. 1508 * @return 1509 * A string containing the list output. 1510 */ 1511 function theme_item_list($items = array(), $title = NULL, $type = 'ul', $attributes = NULL) { 1512 $output = '<div class="item-list">'; 1513 if (isset($title)) { 1514 $output .= '<h3>'. $title .'</h3>'; 1515 } 1516 1517 if (!empty($items)) { 1518 $output .= "<$type". drupal_attributes($attributes) .'>'; 1519 $num_items = count($items); 1520 foreach ($items as $i => $item) { 1521 $attributes = array(); 1522 $children = array(); 1523 if (is_array($item)) { 1524 foreach ($item as $key => $value) { 1525 if ($key == 'data') { 1526 $data = $value; 1527 } 1528 elseif ($key == 'children') { 1529 $children = $value; 1530 } 1531 else { 1532 $attributes[$key] = $value; 1533 } 1534 } 1535 } 1536 else { 1537 $data = $item; 1538 } 1539 if (count($children) > 0) { 1540 $data .= theme_item_list($children, NULL, $type, $attributes); // Render nested list 1541 } 1542 if ($i == 0) { 1543 $attributes['class'] = empty($attributes['class']) ? 'first' : ($attributes['class'] .' first'); 1544 } 1545 if ($i == $num_items - 1) { 1546 $attributes['class'] = empty($attributes['class']) ? 'last' : ($attributes['class'] .' last'); 1547 } 1548 $output .= '<li'. drupal_attributes($attributes) .'>'. $data ."</li>\n"; 1549 } 1550 $output .= "</$type>"; 1551 } 1552 $output .= '</div>'; 1553 return $output; 1554 } 1555 1556 /** 1557 * Returns code that emits the 'more help'-link. 1558 */ 1559 function theme_more_help_link($url) { 1560 return '<div class="more-help-link">'. t('[<a href="@link">more help...</a>]', array('@link' => check_url($url))) .'</div>'; 1561 } 1562 1563 /** 1564 * Return code that emits an XML icon. 1565 * 1566 * For most use cases, this function has been superseded by theme_feed_icon(). 1567 * 1568 * @see theme_feed_icon() 1569 * @param $url 1570 * The url of the feed. 1571 */ 1572 function theme_xml_icon($url) { 1573 if ($image = theme('image', 'misc/xml.png', t('XML feed'), t('XML feed'))) { 1574 return '<a href="'. check_url($url) .'" class="xml-icon">'. $image .'</a>'; 1575 } 1576 } 1577 1578 /** 1579 * Return code that emits an feed icon. 1580 * 1581 * @param $url 1582 * The url of the feed. 1583 * @param $title 1584 * A descriptive title of the feed. 1585 */ 1586 function theme_feed_icon($url, $title) { 1587 if ($image = theme('image', 'misc/feed.png', t('Syndicate content'), $title)) { 1588 return '<a href="'. check_url($url) .'" class="feed-icon">'. $image .'</a>'; 1589 } 1590 } 1591 1592 /** 1593 * Returns code that emits the 'more' link used on blocks. 1594 * 1595 * @param $url 1596 * The url of the main page 1597 * @param $title 1598 * A descriptive verb for the link, like 'Read more' 1599 */ 1600 function theme_more_link($url, $title) { 1601 return '<div class="more-link">'. t('<a href="@link" title="@title">more</a>', array('@link' => check_url($url), '@title' => $title)) .'</div>'; 1602 } 1603 1604 /** 1605 * Execute hook_footer() which is run at the end of the page right before the 1606 * close of the body tag. 1607 * 1608 * @param $main (optional) 1609 * Whether the current page is the front page of the site. 1610 * @return 1611 * A string containing the results of the hook_footer() calls. 1612 */ 1613 function theme_closure($main = 0) { 1614 $footer = module_invoke_all('footer', $main); 1615 return implode("\n", $footer) . drupal_get_js('footer'); 1616 } 1617 1618 /** 1619 * Return a set of blocks available for the current user. 1620 * 1621 * @param $region 1622 * Which set of blocks to retrieve. 1623 * @return 1624 * A string containing the themed blocks for this region. 1625 */ 1626 function theme_blocks($region) { 1627 $output = ''; 1628 1629 if ($list = block_list($region)) { 1630 foreach ($list as $key => $block) { 1631 // $key == <i>module</i>_<i>delta</i> 1632 $output .= theme('block', $block); 1633 } 1634 } 1635 1636 // Add any content assigned to this region through drupal_set_content() calls. 1637 $output .= drupal_get_content($region); 1638 1639 return $output; 1640 } 1641 1642 /** 1643 * Format a username. 1644 * 1645 * @param $object 1646 * The user object to format, usually returned from user_load(). 1647 * @return 1648 * A string containing an HTML link to the user's page if the passed object 1649 * suggests that this is a site user. Otherwise, only the username is returned. 1650 */ 1651 function theme_username($object) { 1652 1653 if ($object->uid && $object->name) { 1654 // Shorten the name when it is too long or it will break many tables. 1655 if (drupal_strlen($object->name) > 20) { 1656 $name = drupal_substr($object->name, 0, 15) .'...'; 1657 } 1658 else { 1659 $name = $object->name; 1660 } 1661 1662 if (user_access('access user profiles')) { 1663 $output = l($name, 'user/'. $object->uid, array('attributes' => array('title' => t('View user profile.')))); 1664 } 1665 else { 1666 $output = check_plain($name); 1667 } 1668 } 1669 else if ($object->name) { 1670 // Sometimes modules display content composed by people who are 1671 // not registered members of the site (e.g. mailing list or news 1672 // aggregator modules). This clause enables modules to display 1673 // the true author of the content. 1674 if (!empty($object->homepage)) { 1675 $output = l($object->name, $object->homepage, array('attributes' => array('rel' => 'nofollow'))); 1676 } 1677 else { 1678 $output = check_plain($object->name); 1679 } 1680 1681 $output .= ' ('. t('not verified') .')'; 1682 } 1683 else { 1684 $output = check_plain(variable_get('anonymous', t('Anonymous'))); 1685 } 1686 1687 return $output; 1688 } 1689 1690 /** 1691 * Return a themed progress bar. 1692 * 1693 * @param $percent 1694 * The percentage of the progress. 1695 * @param $message 1696 * A string containing information to be displayed. 1697 * @return 1698 * A themed HTML string representing the progress bar. 1699 */ 1700 function theme_progress_bar($percent, $message) { 1701 $output = '<div id="progress" class="progress">'; 1702 $output .= '<div class="bar"><div class="filled" style="width: '. $percent .'%"></div></div>'; 1703 $output .= '<div class="percentage">'. $percent .'%</div>'; 1704 $output .= '<div class="message">'. $message .'</div>'; 1705 $output .= '</div>'; 1706 1707 return $output; 1708 } 1709 1710 /** 1711 * Create a standard indentation div. Used for drag and drop tables. 1712 * 1713 * @param $size 1714 * Optional. The number of indentations to create. 1715 * @return 1716 * A string containing indentations. 1717 */ 1718 function theme_indentation($size = 1) { 1719 $output = ''; 1720 for ($n = 0; $n < $size; $n++) { 1721 $output .= '<div class="indentation"> </div>'; 1722 } 1723 return $output; 1724 } 1725 1726 /** 1727 * @} End of "defgroup themeable". 1728 */ 1729 1730 function _theme_table_cell($cell, $header = FALSE) { 1731 $attributes = ''; 1732 1733 if (is_array($cell)) { 1734 $data = isset($cell['data']) ? $cell['data'] : ''; 1735 $header |= isset($cell['header']); 1736 unset($cell['data']); 1737 unset($cell['header']); 1738 $attributes = drupal_attributes($cell); 1739 } 1740 else { 1741 $data = $cell; 1742 } 1743 1744 if ($header) { 1745 $output = "<th$attributes>$data</th>"; 1746 } 1747 else { 1748 $output = "<td$attributes>$data</td>"; 1749 } 1750 1751 return $output; 1752 } 1753 1754 /** 1755 * Adds a default set of helper variables for preprocess functions and 1756 * templates. This comes in before any other preprocess function which makes 1757 * it possible to be used in default theme implementations (non-overriden 1758 * theme functions). 1759 */ 1760 function template_preprocess(&$variables, $hook) { 1761 global $user; 1762 static $count = array(); 1763 1764 // Track run count for each hook to provide zebra striping. 1765 // See "template_preprocess_block()" which provides the same feature specific to blocks. 1766 $count[$hook] = isset($count[$hook]) && is_int($count[$hook]) ? $count[$hook] : 1; 1767 $variables['zebra'] = ($count[$hook] % 2) ? 'odd' : 'even'; 1768 $variables['id'] = $count[$hook]++; 1769 1770 // Tell all templates where they are located. 1771 $variables['directory'] = path_to_theme(); 1772 1773 // Set default variables that depend on the database. 1774 $variables['is_admin'] = FALSE; 1775 $variables['is_front'] = FALSE; 1776 $variables['logged_in'] = FALSE; 1777 if ($variables['db_is_active'] = db_is_active() && !defined('MAINTENANCE_MODE')) { 1778 // Check for administrators. 1779 if (user_access('access administration pages')) { 1780 $variables['is_admin'] = TRUE; 1781 } 1782 // Flag front page status. 1783 $variables['is_front'] = drupal_is_front_page(); 1784 // Tell all templates by which kind of user they're viewed. 1785 $variables['logged_in'] = ($user->uid > 0); 1786 // Provide user object to all templates 1787 $variables['user'] = $user; 1788 } 1789 } 1790 1791 /** 1792 * Process variables for page.tpl.php 1793 * 1794 * Most themes utilize their own copy of page.tpl.php. The default is located 1795 * inside "modules/system/page.tpl.php". Look in there for the full list of 1796 * variables. 1797 * 1798 * Uses the arg() function to generate a series of page template suggestions 1799 * based on the current path. 1800 * 1801 * Any changes to variables in this preprocessor should also be changed inside 1802 * template_preprocess_maintenance_page() to keep all them consistent. 1803 * 1804 * The $variables array contains the following arguments: 1805 * - $content 1806 * - $show_blocks 1807 * 1808 * @see page.tpl.php 1809 */ 1810 function template_preprocess_page(&$variables) { 1811 // Add favicon 1812 if (theme_get_setting('toggle_favicon')) { 1813 drupal_set_html_head('<link rel="shortcut icon" href="'. check_url(theme_get_setting('favicon')) .'" type="image/x-icon" />'); 1814 } 1815 1816 global $theme; 1817 // Populate all block regions. 1818 $regions = system_region_list($theme); 1819 // Load all region content assigned via blocks. 1820 foreach (array_keys($regions) as $region) { 1821 // Prevent left and right regions from rendering blocks when 'show_blocks' == FALSE. 1822 if (!(!$variables['show_blocks'] && ($region == 'left' || $region == 'right'))) { 1823 $blocks = theme('blocks', $region); 1824 } 1825 else { 1826 $blocks = ''; 1827 } 1828 // Assign region to a region variable. 1829 isset($variables[$region]) ? $variables[$region] .= $blocks : $variables[$region] = $blocks; 1830 } 1831 1832 // Set up layout variable. 1833 $variables['layout'] = 'none'; 1834 if (!empty($variables['left'])) { 1835 $variables['layout'] = 'left'; 1836 } 1837 if (!empty($variables['right'])) { 1838 $variables['layout'] = ($variables['layout'] == 'left') ? 'both' : 'right'; 1839 } 1840 1841 // Set mission when viewing the frontpage. 1842 if (drupal_is_front_page()) { 1843 $mission = filter_xss_admin(theme_get_setting('mission')); 1844 } 1845 1846 // Construct page title 1847 if (drupal_get_title()) { 1848 $head_title = array(strip_tags(drupal_get_title()), variable_get('site_name', 'Drupal')); 1849 } 1850 else { 1851 $head_title = array(variable_get('site_name', 'Drupal')); 1852 if (variable_get('site_slogan', '')) { 1853 $head_title[] = variable_get('site_slogan', ''); 1854 } 1855 } 1856 $variables['head_title'] = implode(' | ', $head_title); 1857 $variables['base_path'] = base_path(); 1858 $variables['front_page'] = url(); 1859 $variables['breadcrumb'] = theme('breadcrumb', drupal_get_breadcrumb()); 1860 $variables['feed_icons'] = drupal_get_feeds(); 1861 $variables['footer_message'] = filter_xss_admin(variable_get('site_footer', FALSE)); 1862 $variables['head'] = drupal_get_html_head(); 1863 $variables['help'] = theme('help'); 1864 $variables['language'] = $GLOBALS['language']; 1865 $variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr'; 1866 $variables['logo'] = theme_get_setting('logo'); 1867 $variables['messages'] = $variables['show_messages'] ? theme('status_messages') : ''; 1868 $variables['mission'] = isset($mission) ? $mission : ''; 1869 $variables['primary_links'] = theme_get_setting('toggle_primary_links') ? menu_primary_links() : array(); 1870 $variables['secondary_links'] = theme_get_setting('toggle_secondary_links') ? menu_secondary_links() : array(); 1871 $variables['search_box'] = (theme_get_setting('toggle_search') ? drupal_get_form('search_theme_form') : ''); 1872 $variables['site_name'] = (theme_get_setting('toggle_name') ? filter_xss_admin(variable_get('site_name', 'Drupal')) : ''); 1873 $variables['site_slogan'] = (theme_get_setting('toggle_slogan') ? filter_xss_admin(variable_get('site_slogan', '')) : ''); 1874 $variables['css'] = drupal_add_css(); 1875 $variables['styles'] = drupal_get_css(); 1876 $variables['scripts'] = drupal_get_js(); 1877 $variables['tabs'] = theme('menu_local_tasks'); 1878 $variables['title'] = drupal_get_title(); 1879 // Closure should be filled last. 1880 $variables['closure'] = theme('closure'); 1881 1882 if ($node = menu_get_object()) { 1883 $variables['node'] = $node; 1884 } 1885 1886 // Compile a list of classes that are going to be applied to the body element. 1887 // This allows advanced theming based on context (home page, node of certain type, etc.). 1888 $body_classes = array(); 1889 // Add a class that tells us whether we're on the front page or not. 1890 $body_classes[] = $variables['is_front'] ? 'front' : 'not-front'; 1891 // Add a class that tells us whether the page is viewed by an authenticated user or not. 1892 $body_classes[] = $variables['logged_in'] ? 'logged-in' : 'not-logged-in'; 1893 // Add arg(0) to make it possible to theme the page depending on the current page 1894 // type (e.g. node, admin, user, etc.). To avoid illegal characters in the class, 1895 // we're removing everything disallowed. We are not using 'a-z' as that might leave 1896 // in certain international characters (e.g. German umlauts). 1897 $body_classes[] = preg_replace('![^abcdefghijklmnopqrstuvwxyz0-9-_]+!s', '', 'page-'. form_clean_id(drupal_strtolower(arg(0)))); 1898 // If on an individual node page, add the node type. 1899 if (isset($variables['node']) && $variables['node']->type) { 1900 $body_classes[] = 'node-type-'. form_clean_id($variables['node']->type); 1901 } 1902 // Add information about the number of sidebars. 1903 if ($variables['layout'] == 'both') { 1904 $body_classes[] = 'two-sidebars'; 1905 } 1906 elseif ($variables['layout'] == 'none') { 1907 $body_classes[] = 'no-sidebars'; 1908 } 1909 else { 1910 $body_classes[] = 'one-sidebar sidebar-'. $variables['layout']; 1911 } 1912 // Implode with spaces. 1913 $variables['body_classes'] = implode(' ', $body_classes); 1914 1915 // Build a list of suggested template files in order of specificity. One 1916 // suggestion is made for every element of the current path, though 1917 // numeric elements are not carried to subsequent suggestions. For example, 1918 // http://www.example.com/node/1/edit would result in the following 1919 // suggestions: 1920 // 1921 // page-node-edit.tpl.php 1922 // page-node-1.tpl.php 1923 // page-node.tpl.php 1924 // page.tpl.php 1925 $i = 0; 1926 $suggestion = 'page'; 1927 $suggestions = array(); 1928 while ($arg = arg($i++)) { 1929 $arg = str_replace(array("/", "\\", "\0"), '', $arg); 1930 $suggestions[] = $suggestion .'-'. $arg; 1931 if (!is_numeric($arg)) { 1932 $suggestion .= '-'. $arg; 1933 } 1934 } 1935 if (drupal_is_front_page()) { 1936 $suggestions[] = 'page-front'; 1937 } 1938 1939 if ($suggestions) { 1940 $variables['template_files'] = $suggestions; 1941 } 1942 } 1943 1944 /** 1945 * Process variables for node.tpl.php 1946 * 1947 * Most themes utilize their own copy of node.tpl.php. The default is located 1948 * inside "modules/node/node.tpl.php". Look in there for the full list of 1949 * variables. 1950 * 1951 * The $variables array contains the following arguments: 1952 * - $node 1953 * - $teaser 1954 * - $page 1955 * 1956 * @see node.tpl.php 1957 */ 1958 function template_preprocess_node(&$variables) { 1959 $node = $variables['node']; 1960 if (module_exists('taxonomy')) { 1961 $variables['taxonomy'] = taxonomy_link('taxonomy terms', $node); 1962 } 1963 else { 1964 $variables['taxonomy'] = array(); 1965 } 1966 1967 if ($variables['teaser'] && $node->teaser) { 1968 $variables['content'] = $node->teaser; 1969 } 1970 elseif (isset($node->body)) { 1971 $variables['content'] = $node->body; 1972 } 1973 else { 1974 $variables['content'] = ''; 1975 } 1976 1977 $variables['date'] = format_date($node->created); 1978 $variables['links'] = !empty($node->links) ? theme('links', $node->links, array('class' => 'links inline')) : ''; 1979 $variables['name'] = theme('username', $node); 1980 $variables['node_url'] = url('node/'. $node->nid); 1981 $variables['terms'] = theme('links', $variables['taxonomy'], array('class' => 'links inline')); 1982 $variables['title'] = check_plain($node->title); 1983 1984 // Flatten the node object's member fields. 1985 $variables = array_merge((array)$node, $variables); 1986 1987 // Display info only on certain node types. 1988 if (theme_get_setting('toggle_node_info_'. $node->type)) { 1989 $variables['submitted'] = theme('node_submitted', $node); 1990 $variables['picture'] = theme_get_setting('toggle_node_user_picture') ? theme('user_picture', $node) : ''; 1991 } 1992 else { 1993 $variables['submitted'] = ''; 1994 $variables['picture'] = ''; 1995 } 1996 // Clean up name so there are no underscores. 1997 $variables['template_files'][] = 'node-'. $node->type; 1998 } 1999 2000 /** 2001 * Process variables for block.tpl.php 2002 * 2003 * Prepare the values passed to the theme_block function to be passed 2004 * into a pluggable template engine. Uses block properties to generate a 2005 * series of template file suggestions. If none are found, the default 2006 * block.tpl.php is used. 2007 * 2008 * Most themes utilize their own copy of block.tpl.php. The default is located 2009 * inside "modules/system/block.tpl.php". Look in there for the full list of 2010 * variables. 2011 * 2012 * The $variables array contains the following arguments: 2013 * - $block 2014 * 2015 * @see block.tpl.php 2016 */ 2017 function template_preprocess_block(&$variables) { 2018 static $block_counter = array(); 2019 // All blocks get an independent counter for each region. 2020 if (!isset($block_counter[$variables['block']->region])) { 2021 $block_counter[$variables['block']->region] = 1; 2022 } 2023 // Same with zebra striping. 2024 $variables['block_zebra'] = ($block_counter[$variables['block']->region] % 2) ? 'odd' : 'even'; 2025 $variables['block_id'] = $block_counter[$variables['block']->region]++; 2026 2027 $variables['template_files'][] = 'block-'. $variables['block']->region; 2028 $variables['template_files'][] = 'block-'. $variables['block']->module; 2029 $variables['template_files'][] = 'block-'. $variables['block']->module .'-'. $variables['block']->delta; 2030 } 2031
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Mar 24 11:18:33 2011 | Cross-referenced by PHPXref 0.7 |