| Drupal | PHP Cross Reference | Content Management Systems |
1 <?php 2 3 /** 4 * @file 5 * The theme system, which controls the output of Drupal. 6 * 7 * The theme system allows for nearly all output of the Drupal system to be 8 * customized by user themes. 9 */ 10 11 /** 12 * @defgroup content_flags Content markers 13 * @{ 14 * Markers used by theme_mark() and node_mark() to designate content. 15 * @see theme_mark(), node_mark() 16 */ 17 18 /** 19 * Mark content as read. 20 */ 21 define('MARK_READ', 0); 22 23 /** 24 * Mark content as being new. 25 */ 26 define('MARK_NEW', 1); 27 28 /** 29 * Mark content as being updated. 30 */ 31 define('MARK_UPDATED', 2); 32 33 /** 34 * @} End of "Content markers". 35 */ 36 37 /** 38 * Determines if a theme is available to use. 39 * 40 * @param $theme 41 * Either the name of a theme or a full theme object. 42 * 43 * @return 44 * Boolean TRUE if the theme is enabled or is the site administration theme; 45 * FALSE otherwise. 46 */ 47 function drupal_theme_access($theme) { 48 if (is_object($theme)) { 49 return _drupal_theme_access($theme); 50 } 51 else { 52 $themes = list_themes(); 53 return isset($themes[$theme]) && _drupal_theme_access($themes[$theme]); 54 } 55 } 56 57 /** 58 * Helper function for determining access to a theme. 59 * 60 * @see drupal_theme_access() 61 */ 62 function _drupal_theme_access($theme) { 63 $admin_theme = variable_get('admin_theme'); 64 return !empty($theme->status) || ($admin_theme && $theme->name == $admin_theme); 65 } 66 67 /** 68 * Initialize the theme system by loading the theme. 69 */ 70 function drupal_theme_initialize() { 71 global $theme, $user, $theme_key; 72 73 // If $theme is already set, assume the others are set, too, and do nothing 74 if (isset($theme)) { 75 return; 76 } 77 78 drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE); 79 $themes = list_themes(); 80 81 // Only select the user selected theme if it is available in the 82 // list of themes that can be accessed. 83 $theme = !empty($user->theme) && drupal_theme_access($user->theme) ? $user->theme : variable_get('theme_default', 'bartik'); 84 85 // Allow modules to override the theme. Validation has already been performed 86 // inside menu_get_custom_theme(), so we do not need to check it again here. 87 $custom_theme = menu_get_custom_theme(); 88 $theme = !empty($custom_theme) ? $custom_theme : $theme; 89 90 // Store the identifier for retrieving theme settings with. 91 $theme_key = $theme; 92 93 // Find all our ancestor themes and put them in an array. 94 $base_theme = array(); 95 $ancestor = $theme; 96 while ($ancestor && isset($themes[$ancestor]->base_theme)) { 97 $ancestor = $themes[$ancestor]->base_theme; 98 $base_theme[] = $themes[$ancestor]; 99 } 100 _drupal_theme_initialize($themes[$theme], array_reverse($base_theme)); 101 102 // Themes can have alter functions, so reset the drupal_alter() cache. 103 drupal_static_reset('drupal_alter'); 104 105 // Provide the page with information about the theme that's used, so that a 106 // later Ajax request can be rendered using the same theme. 107 // @see ajax_base_page_theme() 108 $setting['ajaxPageState'] = array( 109 'theme' => $theme_key, 110 'theme_token' => drupal_get_token($theme_key), 111 ); 112 drupal_add_js($setting, 'setting'); 113 } 114 115 /** 116 * Initialize the theme system given already loaded information. This 117 * function is useful to initialize a theme when no database is present. 118 * 119 * @param $theme 120 * An object with the following information: 121 * filename 122 * The .info file for this theme. The 'path' to 123 * the theme will be in this file's directory. (Required) 124 * owner 125 * The path to the .theme file or the .engine file to load for 126 * the theme. (Required) 127 * stylesheet 128 * The primary stylesheet for the theme. (Optional) 129 * engine 130 * The name of theme engine to use. (Optional) 131 * @param $base_theme 132 * An optional array of objects that represent the 'base theme' if the 133 * theme is meant to be derivative of another theme. It requires 134 * the same information as the $theme object. It should be in 135 * 'oldest first' order, meaning the top level of the chain will 136 * be first. 137 * @param $registry_callback 138 * The callback to invoke to set the theme registry. 139 */ 140 function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callback = '_theme_load_registry') { 141 global $theme_info, $base_theme_info, $theme_engine, $theme_path; 142 $theme_info = $theme; 143 $base_theme_info = $base_theme; 144 145 $theme_path = dirname($theme->filename); 146 147 // Prepare stylesheets from this theme as well as all ancestor themes. 148 // We work it this way so that we can have child themes override parent 149 // theme stylesheets easily. 150 $final_stylesheets = array(); 151 152 // Grab stylesheets from base theme 153 foreach ($base_theme as $base) { 154 if (!empty($base->stylesheets)) { 155 foreach ($base->stylesheets as $media => $stylesheets) { 156 foreach ($stylesheets as $name => $stylesheet) { 157 $final_stylesheets[$media][$name] = $stylesheet; 158 } 159 } 160 } 161 } 162 163 // Add stylesheets used by this theme. 164 if (!empty($theme->stylesheets)) { 165 foreach ($theme->stylesheets as $media => $stylesheets) { 166 foreach ($stylesheets as $name => $stylesheet) { 167 $final_stylesheets[$media][$name] = $stylesheet; 168 } 169 } 170 } 171 172 // And now add the stylesheets properly 173 foreach ($final_stylesheets as $media => $stylesheets) { 174 foreach ($stylesheets as $stylesheet) { 175 drupal_add_css($stylesheet, array('group' => CSS_THEME, 'every_page' => TRUE, 'media' => $media)); 176 } 177 } 178 179 // Do basically the same as the above for scripts 180 $final_scripts = array(); 181 182 // Grab scripts from base theme 183 foreach ($base_theme as $base) { 184 if (!empty($base->scripts)) { 185 foreach ($base->scripts as $name => $script) { 186 $final_scripts[$name] = $script; 187 } 188 } 189 } 190 191 // Add scripts used by this theme. 192 if (!empty($theme->scripts)) { 193 foreach ($theme->scripts as $name => $script) { 194 $final_scripts[$name] = $script; 195 } 196 } 197 198 // Add scripts used by this theme. 199 foreach ($final_scripts as $script) { 200 drupal_add_js($script, array('group' => JS_THEME, 'every_page' => TRUE)); 201 } 202 203 $theme_engine = NULL; 204 205 // Initialize the theme. 206 if (isset($theme->engine)) { 207 // Include the engine. 208 include_once DRUPAL_ROOT . '/' . $theme->owner; 209 210 $theme_engine = $theme->engine; 211 if (function_exists($theme_engine . '_init')) { 212 foreach ($base_theme as $base) { 213 call_user_func($theme_engine . '_init', $base); 214 } 215 call_user_func($theme_engine . '_init', $theme); 216 } 217 } 218 else { 219 // include non-engine theme files 220 foreach ($base_theme as $base) { 221 // Include the theme file or the engine. 222 if (!empty($base->owner)) { 223 include_once DRUPAL_ROOT . '/' . $base->owner; 224 } 225 } 226 // and our theme gets one too. 227 if (!empty($theme->owner)) { 228 include_once DRUPAL_ROOT . '/' . $theme->owner; 229 } 230 } 231 232 if (isset($registry_callback)) { 233 _theme_registry_callback($registry_callback, array($theme, $base_theme, $theme_engine)); 234 } 235 } 236 237 /** 238 * Get the theme registry. 239 * 240 * @param $complete 241 * Optional boolean to indicate whether to return the complete theme registry 242 * array or an instance of the ThemeRegistry class. If TRUE, the complete 243 * theme registry array will be returned. This is useful if you want to 244 * foreach over the whole registry, use array_* functions or inspect it in a 245 * debugger. If FALSE, an instance of the ThemeRegistry class will be 246 * returned, this provides an ArrayObject which allows it to be accessed 247 * with array syntax and isset(), and should be more lightweight 248 * than the full registry. Defaults to TRUE. 249 * 250 * @return 251 * The complete theme registry array, or an instance of the ThemeRegistry 252 * class. 253 */ 254 function theme_get_registry($complete = TRUE) { 255 // Use the advanced drupal_static() pattern, since this is called very often. 256 static $drupal_static_fast; 257 if (!isset($drupal_static_fast)) { 258 $drupal_static_fast['registry'] = &drupal_static('theme_get_registry'); 259 } 260 $theme_registry = &$drupal_static_fast['registry']; 261 262 // Initialize the theme, if this is called early in the bootstrap, or after 263 // static variables have been reset. 264 if (!is_array($theme_registry)) { 265 drupal_theme_initialize(); 266 $theme_registry = array(); 267 } 268 269 $key = (int) $complete; 270 271 if (!isset($theme_registry[$key])) { 272 list($callback, $arguments) = _theme_registry_callback(); 273 if (!$complete) { 274 $arguments[] = FALSE; 275 } 276 $theme_registry[$key] = call_user_func_array($callback, $arguments); 277 } 278 279 return $theme_registry[$key]; 280 } 281 282 /** 283 * Set the callback that will be used by theme_get_registry() to fetch the registry. 284 * 285 * @param $callback 286 * The name of the callback function. 287 * @param $arguments 288 * The arguments to pass to the function. 289 */ 290 function _theme_registry_callback($callback = NULL, array $arguments = array()) { 291 static $stored; 292 if (isset($callback)) { 293 $stored = array($callback, $arguments); 294 } 295 return $stored; 296 } 297 298 /** 299 * Get the theme_registry cache; if it doesn't exist, build it. 300 * 301 * @param $theme 302 * The loaded $theme object as returned by list_themes(). 303 * @param $base_theme 304 * An array of loaded $theme objects representing the ancestor themes in 305 * oldest first order. 306 * @param $theme_engine 307 * The name of the theme engine. 308 * @param $complete 309 * Whether to load the complete theme registry or an instance of the 310 * ThemeRegistry class. 311 * 312 * @return 313 * The theme registry array, or an instance of the ThemeRegistry class. 314 */ 315 function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL, $complete = TRUE) { 316 if ($complete) { 317 // Check the theme registry cache; if it exists, use it. 318 $cached = cache_get("theme_registry:$theme->name"); 319 if (isset($cached->data)) { 320 $registry = $cached->data; 321 } 322 else { 323 // If not, build one and cache it. 324 $registry = _theme_build_registry($theme, $base_theme, $theme_engine); 325 // Only persist this registry if all modules are loaded. This assures a 326 // complete set of theme hooks. 327 if (module_load_all(NULL)) { 328 _theme_save_registry($theme, $registry); 329 } 330 } 331 return $registry; 332 } 333 else { 334 return new ThemeRegistry('theme_registry:runtime:' . $theme->name, 'cache'); 335 } 336 } 337 338 /** 339 * Write the theme_registry cache into the database. 340 */ 341 function _theme_save_registry($theme, $registry) { 342 cache_set("theme_registry:$theme->name", $registry); 343 } 344 345 /** 346 * Force the system to rebuild the theme registry; this should be called 347 * when modules are added to the system, or when a dynamic system needs 348 * to add more theme hooks. 349 */ 350 function drupal_theme_rebuild() { 351 drupal_static_reset('theme_get_registry'); 352 cache_clear_all('theme_registry', 'cache', TRUE); 353 } 354 355 /** 356 * Builds the run-time theme registry. 357 * 358 * Extends DrupalCacheArray to allow the theme registry to be accessed as a 359 * complete registry, while internally caching only the parts of the registry 360 * that are actually in use on the site. On cache misses the complete 361 * theme registry is loaded and used to update the run-time cache. 362 */ 363 class ThemeRegistry Extends DrupalCacheArray { 364 365 /** 366 * Whether the partial registry can be persisted to the cache. 367 * 368 * This is only allowed if all modules and the request method is GET. theme() 369 * should be very rarely called on POST requests and this avoids polluting 370 * the runtime cache. 371 */ 372 protected $persistable; 373 374 /** 375 * The complete theme registry array. 376 */ 377 protected $completeRegistry; 378 379 function __construct($cid, $bin) { 380 $this->cid = $cid; 381 $this->bin = $bin; 382 $this->persistable = module_load_all(NULL) && $_SERVER['REQUEST_METHOD'] == 'GET'; 383 384 $data = array(); 385 if ($this->persistable && $cached = cache_get($this->cid, $this->bin)) { 386 $data = $cached->data; 387 } 388 else { 389 // If there is no runtime cache stored, fetch the full theme registry, 390 // but then initialize each value to NULL. This allows offsetExists() 391 // to function correctly on non-registered theme hooks without triggering 392 // a call to resolveCacheMiss(). 393 $data = $this->initializeRegistry(); 394 if ($this->persistable) { 395 $this->set($data); 396 } 397 } 398 $this->storage = $data; 399 } 400 401 /** 402 * Initializes the full theme registry. 403 * 404 * @return 405 * An array with the keys of the full theme registry, but the values 406 * initialized to NULL. 407 */ 408 function initializeRegistry() { 409 $this->completeRegistry = theme_get_registry(); 410 411 return array_fill_keys(array_keys($this->completeRegistry), NULL); 412 } 413 414 public function offsetExists($offset) { 415 // Since the theme registry allows for theme hooks to be requested that 416 // are not registered, just check the existence of the key in the registry. 417 // Use array_key_exists() here since a NULL value indicates that the theme 418 // hook exists but has not yet been requested. 419 return array_key_exists($offset, $this->storage); 420 } 421 422 public function offsetGet($offset) { 423 // If the offset is set but empty, it is a registered theme hook that has 424 // not yet been requested. Offsets that do not exist at all were not 425 // registered in hook_theme(). 426 if (isset($this->storage[$offset])) { 427 return $this->storage[$offset]; 428 } 429 elseif (array_key_exists($offset, $this->storage)) { 430 return $this->resolveCacheMiss($offset); 431 } 432 } 433 434 public function resolveCacheMiss($offset) { 435 if (!isset($this->completeRegistry)) { 436 $this->completeRegistry = theme_get_registry(); 437 } 438 $this->storage[$offset] = $this->completeRegistry[$offset]; 439 if ($this->persistable) { 440 $this->persist($offset); 441 } 442 return $this->storage[$offset]; 443 } 444 445 public function set($data, $lock = TRUE) { 446 $lock_name = $this->cid . ':' . $this->bin; 447 if (!$lock || lock_acquire($lock_name)) { 448 if ($cached = cache_get($this->cid, $this->bin)) { 449 // Use array merge instead of union so that filled in values in $data 450 // overwrite empty values in the current cache. 451 $data = array_merge($cached->data, $data); 452 } 453 else { 454 $registry = $this->initializeRegistry(); 455 $data = array_merge($registry, $data); 456 } 457 cache_set($this->cid, $data, $this->bin); 458 if ($lock) { 459 lock_release($lock_name); 460 } 461 } 462 } 463 } 464 465 /** 466 * Process a single implementation of hook_theme(). 467 * 468 * @param $cache 469 * The theme registry that will eventually be cached; It is an associative 470 * array keyed by theme hooks, whose values are associative arrays describing 471 * the hook: 472 * - 'type': The passed-in $type. 473 * - 'theme path': The passed-in $path. 474 * - 'function': The name of the function generating output for this theme 475 * hook. Either defined explicitly in hook_theme() or, if neither 'function' 476 * nor 'template' is defined, then the default theme function name is used. 477 * The default theme function name is the theme hook prefixed by either 478 * 'theme_' for modules or '$name_' for everything else. If 'function' is 479 * defined, 'template' is not used. 480 * - 'template': The filename of the template generating output for this 481 * theme hook. The template is in the directory defined by the 'path' key of 482 * hook_theme() or defaults to $path. 483 * - 'variables': The variables for this theme hook as defined in 484 * hook_theme(). If there is more than one implementation and 'variables' is 485 * not specified in a later one, then the previous definition is kept. 486 * - 'render element': The renderable element for this theme hook as defined 487 * in hook_theme(). If there is more than one implementation and 488 * 'render element' is not specified in a later one, then the previous 489 * definition is kept. 490 * - 'preprocess functions': See theme() for detailed documentation. 491 * - 'process functions': See theme() for detailed documentation. 492 * @param $name 493 * The name of the module, theme engine, base theme engine, theme or base 494 * theme implementing hook_theme(). 495 * @param $type 496 * One of 'module', 'theme_engine', 'base_theme_engine', 'theme', or 497 * 'base_theme'. Unlike regular hooks that can only be implemented by modules, 498 * each of these can implement hook_theme(). _theme_process_registry() is 499 * called in aforementioned order and new entries override older ones. For 500 * example, if a theme hook is both defined by a module and a theme, then the 501 * definition in the theme will be used. 502 * @param $theme 503 * The loaded $theme object as returned from list_themes(). 504 * @param $path 505 * The directory where $name is. For example, modules/system or 506 * themes/bartik. 507 * 508 * @see theme() 509 * @see _theme_process_registry() 510 * @see hook_theme() 511 * @see list_themes() 512 */ 513 function _theme_process_registry(&$cache, $name, $type, $theme, $path) { 514 $result = array(); 515 516 // Processor functions work in two distinct phases with the process 517 // functions always being executed after the preprocess functions. 518 $variable_process_phases = array( 519 'preprocess functions' => 'preprocess', 520 'process functions' => 'process', 521 ); 522 523 $hook_defaults = array( 524 'variables' => TRUE, 525 'render element' => TRUE, 526 'pattern' => TRUE, 527 'base hook' => TRUE, 528 ); 529 530 // Invoke the hook_theme() implementation, process what is returned, and 531 // merge it into $cache. 532 $function = $name . '_theme'; 533 if (function_exists($function)) { 534 $result = $function($cache, $type, $theme, $path); 535 foreach ($result as $hook => $info) { 536 // When a theme or engine overrides a module's theme function 537 // $result[$hook] will only contain key/value pairs for information being 538 // overridden. Pull the rest of the information from what was defined by 539 // an earlier hook. 540 541 // Fill in the type and path of the module, theme, or engine that 542 // implements this theme function. 543 $result[$hook]['type'] = $type; 544 $result[$hook]['theme path'] = $path; 545 546 // If function and file are omitted, default to standard naming 547 // conventions. 548 if (!isset($info['template']) && !isset($info['function'])) { 549 $result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name . '_') . $hook; 550 } 551 552 if (isset($cache[$hook]['includes'])) { 553 $result[$hook]['includes'] = $cache[$hook]['includes']; 554 } 555 556 // If the theme implementation defines a file, then also use the path 557 // that it defined. Otherwise use the default path. This allows 558 // system.module to declare theme functions on behalf of core .include 559 // files. 560 if (isset($info['file'])) { 561 $include_file = isset($info['path']) ? $info['path'] : $path; 562 $include_file .= '/' . $info['file']; 563 include_once DRUPAL_ROOT . '/' . $include_file; 564 $result[$hook]['includes'][] = $include_file; 565 } 566 567 // If the default keys are not set, use the default values registered 568 // by the module. 569 if (isset($cache[$hook])) { 570 $result[$hook] += array_intersect_key($cache[$hook], $hook_defaults); 571 } 572 573 // The following apply only to theming hooks implemented as templates. 574 if (isset($info['template'])) { 575 // Prepend the current theming path when none is set. 576 if (!isset($info['path'])) { 577 $result[$hook]['template'] = $path . '/' . $info['template']; 578 } 579 } 580 581 // Allow variable processors for all theming hooks, whether the hook is 582 // implemented as a template or as a function. 583 foreach ($variable_process_phases as $phase_key => $phase) { 584 // Check for existing variable processors. Ensure arrayness. 585 if (!isset($info[$phase_key]) || !is_array($info[$phase_key])) { 586 $info[$phase_key] = array(); 587 $prefixes = array(); 588 if ($type == 'module') { 589 // Default variable processor prefix. 590 $prefixes[] = 'template'; 591 // Add all modules so they can intervene with their own variable 592 // processors. This allows them to provide variable processors even 593 // if they are not the owner of the current hook. 594 $prefixes += module_list(); 595 } 596 elseif ($type == 'theme_engine' || $type == 'base_theme_engine') { 597 // Theme engines get an extra set that come before the normally 598 // named variable processors. 599 $prefixes[] = $name . '_engine'; 600 // The theme engine registers on behalf of the theme using the 601 // theme's name. 602 $prefixes[] = $theme; 603 } 604 else { 605 // This applies when the theme manually registers their own variable 606 // processors. 607 $prefixes[] = $name; 608 } 609 foreach ($prefixes as $prefix) { 610 // Only use non-hook-specific variable processors for theming hooks 611 // implemented as templates. See theme(). 612 if (isset($info['template']) && function_exists($prefix . '_' . $phase)) { 613 $info[$phase_key][] = $prefix . '_' . $phase; 614 } 615 if (function_exists($prefix . '_' . $phase . '_' . $hook)) { 616 $info[$phase_key][] = $prefix . '_' . $phase . '_' . $hook; 617 } 618 } 619 } 620 // Check for the override flag and prevent the cached variable 621 // processors from being used. This allows themes or theme engines to 622 // remove variable processors set earlier in the registry build. 623 if (!empty($info['override ' . $phase_key])) { 624 // Flag not needed inside the registry. 625 unset($result[$hook]['override ' . $phase_key]); 626 } 627 elseif (isset($cache[$hook][$phase_key]) && is_array($cache[$hook][$phase_key])) { 628 $info[$phase_key] = array_merge($cache[$hook][$phase_key], $info[$phase_key]); 629 } 630 $result[$hook][$phase_key] = $info[$phase_key]; 631 } 632 } 633 634 // Merge the newly created theme hooks into the existing cache. 635 $cache = $result + $cache; 636 } 637 638 // Let themes have variable processors even if they didn't register a template. 639 if ($type == 'theme' || $type == 'base_theme') { 640 foreach ($cache as $hook => $info) { 641 // Check only if not registered by the theme or engine. 642 if (empty($result[$hook])) { 643 foreach ($variable_process_phases as $phase_key => $phase) { 644 if (!isset($info[$phase_key])) { 645 $cache[$hook][$phase_key] = array(); 646 } 647 // Only use non-hook-specific variable processors for theming hooks 648 // implemented as templates. See theme(). 649 if (isset($info['template']) && function_exists($name . '_' . $phase)) { 650 $cache[$hook][$phase_key][] = $name . '_' . $phase; 651 } 652 if (function_exists($name . '_' . $phase . '_' . $hook)) { 653 $cache[$hook][$phase_key][] = $name . '_' . $phase . '_' . $hook; 654 $cache[$hook]['theme path'] = $path; 655 } 656 // Ensure uniqueness. 657 $cache[$hook][$phase_key] = array_unique($cache[$hook][$phase_key]); 658 } 659 } 660 } 661 } 662 } 663 664 /** 665 * Build the theme registry cache. 666 * 667 * @param $theme 668 * The loaded $theme object as returned by list_themes(). 669 * @param $base_theme 670 * An array of loaded $theme objects representing the ancestor themes in 671 * oldest first order. 672 * @param $theme_engine 673 * The name of the theme engine. 674 */ 675 function _theme_build_registry($theme, $base_theme, $theme_engine) { 676 $cache = array(); 677 // First, process the theme hooks advertised by modules. This will 678 // serve as the basic registry. Since the list of enabled modules is the same 679 // regardless of the theme used, this is cached in its own entry to save 680 // building it for every theme. 681 if ($cached = cache_get('theme_registry:build:modules')) { 682 $cache = $cached->data; 683 } 684 else { 685 foreach (module_implements('theme') as $module) { 686 _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module)); 687 } 688 // Only cache this registry if all modules are loaded. 689 if (module_load_all(NULL)) { 690 cache_set('theme_registry:build:modules', $cache); 691 } 692 } 693 694 // Process each base theme. 695 foreach ($base_theme as $base) { 696 // If the base theme uses a theme engine, process its hooks. 697 $base_path = dirname($base->filename); 698 if ($theme_engine) { 699 _theme_process_registry($cache, $theme_engine, 'base_theme_engine', $base->name, $base_path); 700 } 701 _theme_process_registry($cache, $base->name, 'base_theme', $base->name, $base_path); 702 } 703 704 // And then the same thing, but for the theme. 705 if ($theme_engine) { 706 _theme_process_registry($cache, $theme_engine, 'theme_engine', $theme->name, dirname($theme->filename)); 707 } 708 709 // Finally, hooks provided by the theme itself. 710 _theme_process_registry($cache, $theme->name, 'theme', $theme->name, dirname($theme->filename)); 711 712 // Let modules alter the registry. 713 drupal_alter('theme_registry', $cache); 714 715 // Optimize the registry to not have empty arrays for functions. 716 foreach ($cache as $hook => $info) { 717 foreach (array('preprocess functions', 'process functions') as $phase) { 718 if (empty($info[$phase])) { 719 unset($cache[$hook][$phase]); 720 } 721 } 722 } 723 return $cache; 724 } 725 726 /** 727 * Return a list of all currently available themes. 728 * 729 * Retrieved from the database, if available and the site is not in maintenance 730 * mode; otherwise compiled freshly from the filesystem. 731 * 732 * @param $refresh 733 * Whether to reload the list of themes from the database. Defaults to FALSE. 734 * 735 * @return 736 * An associative array of the currently available themes. The keys are the 737 * themes' machine names and the values are objects having the following 738 * properties: 739 * - filename: The filepath and name of the .info file. 740 * - name: The machine name of the theme. 741 * - status: 1 for enabled, 0 for disabled themes. 742 * - info: The contents of the .info file. 743 * - stylesheets: A two dimensional array, using the first key for the 744 * media attribute (e.g. 'all'), the second for the name of the file 745 * (e.g. style.css). The value is a complete filepath (e.g. 746 * themes/bartik/style.css). Not set if no stylesheets are defined in the 747 * .info file. 748 * - scripts: An associative array of JavaScripts, using the filename as key 749 * and the complete filepath as value. Not set if no scripts are defined in 750 * the .info file. 751 * - prefix: The base theme engine prefix. 752 * - engine: The machine name of the theme engine. 753 * - base_theme: If this is a sub-theme, the machine name of the base theme 754 * defined in the .info file. Otherwise, the element is not set. 755 * - base_themes: If this is a sub-theme, an associative array of the 756 * base-theme ancestors of this theme, starting with this theme's base 757 * theme, then the base theme's own base theme, etc. Each entry has an 758 * array key equal to the theme's machine name, and a value equal to the 759 * human-readable theme name; if a theme with matching machine name does 760 * not exist in the system, the value will instead be NULL (and since the 761 * system would not know whether that theme itself has a base theme, that 762 * will end the array of base themes). This is not set if the theme is not 763 * a sub-theme. 764 * - sub_themes: An associative array of themes on the system that are 765 * either direct sub-themes (that is, they declare this theme to be 766 * their base theme), direct sub-themes of sub-themes, etc. The keys are 767 * the themes' machine names, and the values are the themes' human-readable 768 * names. This element is not set if there are no themes on the system that 769 * declare this theme as their base theme. 770 */ 771 function list_themes($refresh = FALSE) { 772 $list = &drupal_static(__FUNCTION__, array()); 773 774 if ($refresh) { 775 $list = array(); 776 system_list_reset(); 777 } 778 779 if (empty($list)) { 780 $list = array(); 781 $themes = array(); 782 // Extract from the database only when it is available. 783 // Also check that the site is not in the middle of an install or update. 784 if (!defined('MAINTENANCE_MODE')) { 785 try { 786 $themes = system_list('theme'); 787 } 788 catch (Exception $e) { 789 // If the database is not available, rebuild the theme data. 790 $themes = _system_rebuild_theme_data(); 791 } 792 } 793 else { 794 // Scan the installation when the database should not be read. 795 $themes = _system_rebuild_theme_data(); 796 } 797 798 foreach ($themes as $theme) { 799 foreach ($theme->info['stylesheets'] as $media => $stylesheets) { 800 foreach ($stylesheets as $stylesheet => $path) { 801 $theme->stylesheets[$media][$stylesheet] = $path; 802 } 803 } 804 foreach ($theme->info['scripts'] as $script => $path) { 805 $theme->scripts[$script] = $path; 806 } 807 if (isset($theme->info['engine'])) { 808 $theme->engine = $theme->info['engine']; 809 } 810 if (isset($theme->info['base theme'])) { 811 $theme->base_theme = $theme->info['base theme']; 812 } 813 // Status is normally retrieved from the database. Add zero values when 814 // read from the installation directory to prevent notices. 815 if (!isset($theme->status)) { 816 $theme->status = 0; 817 } 818 $list[$theme->name] = $theme; 819 } 820 } 821 822 return $list; 823 } 824 825 /** 826 * Find all the base themes for the specified theme. 827 * 828 * Themes can inherit templates and function implementations from earlier themes. 829 * 830 * @param $themes 831 * An array of available themes. 832 * @param $key 833 * The name of the theme whose base we are looking for. 834 * @param $used_keys 835 * A recursion parameter preventing endless loops. 836 * @return 837 * Returns an array of all of the theme's ancestors; the first element's value 838 * will be NULL if an error occurred. 839 */ 840 function drupal_find_base_themes($themes, $key, $used_keys = array()) { 841 $base_key = $themes[$key]->info['base theme']; 842 // Does the base theme exist? 843 if (!isset($themes[$base_key])) { 844 return array($base_key => NULL); 845 } 846 847 $current_base_theme = array($base_key => $themes[$base_key]->info['name']); 848 849 // Is the base theme itself a child of another theme? 850 if (isset($themes[$base_key]->info['base theme'])) { 851 // Do we already know the base themes of this theme? 852 if (isset($themes[$base_key]->base_themes)) { 853 return $themes[$base_key]->base_themes + $current_base_theme; 854 } 855 // Prevent loops. 856 if (!empty($used_keys[$base_key])) { 857 return array($base_key => NULL); 858 } 859 $used_keys[$base_key] = TRUE; 860 return drupal_find_base_themes($themes, $base_key, $used_keys) + $current_base_theme; 861 } 862 // If we get here, then this is our parent theme. 863 return $current_base_theme; 864 } 865 866 /** 867 * Generates themed output. 868 * 869 * All requests for themed output must go through this function. It examines 870 * the request and routes it to the appropriate 871 * @link themeable theme function or template @endlink, by checking the theme 872 * registry. 873 * 874 * Most commonly, the first argument to this function is the name of the theme 875 * hook. For instance, to theme a taxonomy term, the theme hook name is 876 * 'taxonomy_term'. Modules register theme hooks within a hook_theme() 877 * implementation and provide a default implementation via a function named 878 * theme_HOOK() (e.g., theme_taxonomy_term()) or via a template file named 879 * according to the value of the 'template' key registered with the theme hook 880 * (see hook_theme() for details). Default templates are implemented with the 881 * PHPTemplate rendering engine and are named the same as the theme hook, with 882 * underscores changed to hyphens, so for the 'taxonomy_term' theme hook, the 883 * default template is 'taxonomy-term.tpl.php'. 884 * 885 * Themes may also register new theme hooks within a hook_theme() 886 * implementation, but it is more common for themes to override default 887 * implementations provided by modules than to register entirely new theme 888 * hooks. Themes can override a default implementation by implementing a 889 * function named THEME_HOOK() (for example, the 'bartik' theme overrides the 890 * default implementation of the 'menu_tree' theme hook by implementing a 891 * bartik_menu_tree() function), or by adding a template file within its folder 892 * structure that follows the template naming structure used by the theme's 893 * rendering engine (for example, since the Bartik theme uses the PHPTemplate 894 * rendering engine, it overrides the default implementation of the 'page' theme 895 * hook by containing a 'page.tpl.php' file within its folder structure). 896 * 897 * If the implementation is a template file, several functions are called 898 * before the template file is invoked, to modify the $variables array. These 899 * fall into the "preprocessing" phase and the "processing" phase, and are 900 * executed (if they exist), in the following order (note that in the following 901 * list, HOOK indicates the theme hook name, MODULE indicates a module name, 902 * THEME indicates a theme name, and ENGINE indicates a theme engine name): 903 * - template_preprocess(&$variables, $hook): Creates a default set of variables 904 * for all theme hooks with template implementations. 905 * - template_preprocess_HOOK(&$variables): Should be implemented by the module 906 * that registers the theme hook, to set up default variables. 907 * - MODULE_preprocess(&$variables, $hook): hook_preprocess() is invoked on all 908 * implementing modules. 909 * - MODULE_preprocess_HOOK(&$variables): hook_preprocess_HOOK() is invoked on 910 * all implementing modules, so that modules that didn't define the theme hook 911 * can alter the variables. 912 * - ENGINE_engine_preprocess(&$variables, $hook): Allows the theme engine to 913 * set necessary variables for all theme hooks with template implementations. 914 * - ENGINE_engine_preprocess_HOOK(&$variables): Allows the theme engine to set 915 * necessary variables for the particular theme hook. 916 * - THEME_preprocess(&$variables, $hook): Allows the theme to set necessary 917 * variables for all theme hooks with template implementations. 918 * - THEME_preprocess_HOOK(&$variables): Allows the theme to set necessary 919 * variables specific to the particular theme hook. 920 * - template_process(&$variables, $hook): Creates an additional set of default 921 * variables for all theme hooks with template implementations. The variables 922 * created in this function are derived from ones created by 923 * template_preprocess(), but potentially altered by the other preprocess 924 * functions listed above. For example, any preprocess function can add to or 925 * modify the $variables['attributes_array'] variable, and after all of them 926 * have finished executing, template_process() flattens it into a 927 * $variables['attributes'] string for convenient use by templates. 928 * - template_process_HOOK(&$variables): Should be implemented by the module 929 * that registers the theme hook, if it needs to perform additional variable 930 * processing after all preprocess functions have finished. 931 * - MODULE_process(&$variables, $hook): hook_process() is invoked on all 932 * implementing modules. 933 * - MODULE_process_HOOK(&$variables): hook_process_HOOK() is invoked on 934 * on all implementing modules, so that modules that didn't define the theme 935 * hook can alter the variables. 936 * - ENGINE_engine_process(&$variables, $hook): Allows the theme engine to 937 * process variables for all theme hooks with template implementations. 938 * - ENGINE_engine_process_HOOK(&$variables): Allows the theme engine to process 939 * the variables specific to the theme hook. 940 * - THEME_process(&$variables, $hook): Allows the theme to process the 941 * variables for all theme hooks with template implementations. 942 * - THEME_process_HOOK(&$variables): Allows the theme to process the 943 * variables specific to the theme hook. 944 * 945 * If the implementation is a function, only the theme-hook-specific preprocess 946 * and process functions (the ones ending in _HOOK) are called from the 947 * list above. This is because theme hooks with function implementations 948 * need to be fast, and calling the non-theme-hook-specific preprocess and 949 * process functions for them would incur a noticeable performance penalty. 950 * 951 * There are two special variables that these preprocess and process functions 952 * can set: 'theme_hook_suggestion' and 'theme_hook_suggestions'. These will be 953 * merged together to form a list of 'suggested' alternate theme hooks to use, 954 * in reverse order of priority. theme_hook_suggestion will always be a higher 955 * priority than items in theme_hook_suggestions. theme() will use the 956 * highest priority implementation that exists. If none exists, theme() will 957 * use the implementation for the theme hook it was called with. These 958 * suggestions are similar to and are used for similar reasons as calling 959 * theme() with an array as the $hook parameter (see below). The difference 960 * is whether the suggestions are determined by the code that calls theme() or 961 * by a preprocess or process function. 962 * 963 * @param $hook 964 * The name of the theme hook to call. If the name contains a 965 * double-underscore ('__') and there isn't an implementation for the full 966 * name, the part before the '__' is checked. This allows a fallback to a more 967 * generic implementation. For example, if theme('links__node', ...) is 968 * called, but there is no implementation of that theme hook, then the 'links' 969 * implementation is used. This process is iterative, so if 970 * theme('links__contextual__node', ...) is called, theme() checks for the 971 * following implementations, and uses the first one that exists: 972 * - links__contextual__node 973 * - links__contextual 974 * - links 975 * This allows themes to create specific theme implementations for named 976 * objects and contexts of otherwise generic theme hooks. The $hook parameter 977 * may also be an array, in which case the first theme hook that has an 978 * implementation is used. This allows for the code that calls theme() to 979 * explicitly specify the fallback order in a situation where using the '__' 980 * convention is not desired or is insufficient. 981 * @param $variables 982 * An associative array of variables to merge with defaults from the theme 983 * registry, pass to preprocess and process functions for modification, and 984 * finally, pass to the function or template implementing the theme hook. 985 * Alternatively, this can be a renderable array, in which case, its 986 * properties are mapped to variables expected by the theme hook 987 * implementations. 988 * 989 * @return 990 * An HTML string representing the themed output. 991 * 992 * @see themeable 993 * @see hook_theme() 994 * @see template_preprocess() 995 * @see template_process() 996 */ 997 function theme($hook, $variables = array()) { 998 // If called before all modules are loaded, we do not necessarily have a full 999 // theme registry to work with, and therefore cannot process the theme 1000 // request properly. See also _theme_load_registry(). 1001 if (!module_load_all(NULL) && !defined('MAINTENANCE_MODE')) { 1002 throw new Exception(t('theme() may not be called until all modules are loaded.')); 1003 } 1004 1005 $hooks = theme_get_registry(FALSE); 1006 1007 // If an array of hook candidates were passed, use the first one that has an 1008 // implementation. 1009 if (is_array($hook)) { 1010 foreach ($hook as $candidate) { 1011 if (isset($hooks[$candidate])) { 1012 break; 1013 } 1014 } 1015 $hook = $candidate; 1016 } 1017 1018 // If there's no implementation, check for more generic fallbacks. If there's 1019 // still no implementation, log an error and return an empty string. 1020 if (!isset($hooks[$hook])) { 1021 // Iteratively strip everything after the last '__' delimiter, until an 1022 // implementation is found. 1023 while ($pos = strrpos($hook, '__')) { 1024 $hook = substr($hook, 0, $pos); 1025 if (isset($hooks[$hook])) { 1026 break; 1027 } 1028 } 1029 if (!isset($hooks[$hook])) { 1030 // Only log a message when not trying theme suggestions ($hook being an 1031 // array). 1032 if (!isset($candidate)) { 1033 watchdog('theme', 'Theme key "@key" not found.', array('@key' => $hook), WATCHDOG_WARNING); 1034 } 1035 return ''; 1036 } 1037 } 1038 1039 $info = $hooks[$hook]; 1040 global $theme_path; 1041 $temp = $theme_path; 1042 // point path_to_theme() to the currently used theme path: 1043 $theme_path = $info['theme path']; 1044 1045 // Include a file if the theme function or variable processor is held elsewhere. 1046 if (!empty($info['includes'])) { 1047 foreach ($info['includes'] as $include_file) { 1048 include_once DRUPAL_ROOT . '/' . $include_file; 1049 } 1050 } 1051 1052 // If a renderable array is passed as $variables, then set $variables to 1053 // the arguments expected by the theme function. 1054 if (isset($variables['#theme']) || isset($variables['#theme_wrappers'])) { 1055 $element = $variables; 1056 $variables = array(); 1057 if (isset($info['variables'])) { 1058 foreach (array_keys($info['variables']) as $name) { 1059 if (isset($element["#$name"])) { 1060 $variables[$name] = $element["#$name"]; 1061 } 1062 } 1063 } 1064 else { 1065 $variables[$info['render element']] = $element; 1066 } 1067 } 1068 1069 // Merge in argument defaults. 1070 if (!empty($info['variables'])) { 1071 $variables += $info['variables']; 1072 } 1073 elseif (!empty($info['render element'])) { 1074 $variables += array($info['render element'] => array()); 1075 } 1076 1077 // Invoke the variable processors, if any. The processors may specify 1078 // alternate suggestions for which hook's template/function to use. If the 1079 // hook is a suggestion of a base hook, invoke the variable processors of 1080 // the base hook, but retain the suggestion as a high priority suggestion to 1081 // be used unless overridden by a variable processor function. 1082 if (isset($info['base hook'])) { 1083 $base_hook = $info['base hook']; 1084 $base_hook_info = $hooks[$base_hook]; 1085 // Include files required by the base hook, since its variable processors 1086 // might reside there. 1087 if (!empty($base_hook_info['includes'])) { 1088 foreach ($base_hook_info['includes'] as $include_file) { 1089 include_once DRUPAL_ROOT . '/' . $include_file; 1090 } 1091 } 1092 if (isset($base_hook_info['preprocess functions']) || isset($base_hook_info['process functions'])) { 1093 $variables['theme_hook_suggestion'] = $hook; 1094 $hook = $base_hook; 1095 $info = $base_hook_info; 1096 } 1097 } 1098 if (isset($info['preprocess functions']) || isset($info['process functions'])) { 1099 $variables['theme_hook_suggestions'] = array(); 1100 foreach (array('preprocess functions', 'process functions') as $phase) { 1101 if (!empty($info[$phase])) { 1102 foreach ($info[$phase] as $processor_function) { 1103 if (function_exists($processor_function)) { 1104 // We don't want a poorly behaved process function changing $hook. 1105 $hook_clone = $hook; 1106 $processor_function($variables, $hook_clone); 1107 } 1108 } 1109 } 1110 } 1111 // If the preprocess/process functions specified hook suggestions, and the 1112 // suggestion exists in the theme registry, use it instead of the hook that 1113 // theme() was called with. This allows the preprocess/process step to 1114 // route to a more specific theme hook. For example, a function may call 1115 // theme('node', ...), but a preprocess function can add 'node__article' as 1116 // a suggestion, enabling a theme to have an alternate template file for 1117 // article nodes. Suggestions are checked in the following order: 1118 // - The 'theme_hook_suggestion' variable is checked first. It overrides 1119 // all others. 1120 // - The 'theme_hook_suggestions' variable is checked in FILO order, so the 1121 // last suggestion added to the array takes precedence over suggestions 1122 // added earlier. 1123 $suggestions = array(); 1124 if (!empty($variables['theme_hook_suggestions'])) { 1125 $suggestions = $variables['theme_hook_suggestions']; 1126 } 1127 if (!empty($variables['theme_hook_suggestion'])) { 1128 $suggestions[] = $variables['theme_hook_suggestion']; 1129 } 1130 foreach (array_reverse($suggestions) as $suggestion) { 1131 if (isset($hooks[$suggestion])) { 1132 $info = $hooks[$suggestion]; 1133 break; 1134 } 1135 } 1136 } 1137 1138 // Generate the output using either a function or a template. 1139 $output = ''; 1140 if (isset($info['function'])) { 1141 if (function_exists($info['function'])) { 1142 $output = $info['function']($variables); 1143 } 1144 } 1145 else { 1146 // Default render function and extension. 1147 $render_function = 'theme_render_template'; 1148 $extension = '.tpl.php'; 1149 1150 // The theme engine may use a different extension and a different renderer. 1151 global $theme_engine; 1152 if (isset($theme_engine)) { 1153 if ($info['type'] != 'module') { 1154 if (function_exists($theme_engine . '_render_template')) { 1155 $render_function = $theme_engine . '_render_template'; 1156 } 1157 $extension_function = $theme_engine . '_extension'; 1158 if (function_exists($extension_function)) { 1159 $extension = $extension_function(); 1160 } 1161 } 1162 } 1163 1164 // In some cases, a template implementation may not have had 1165 // template_preprocess() run (for example, if the default implementation is 1166 // a function, but a template overrides that default implementation). In 1167 // these cases, a template should still be able to expect to have access to 1168 // the variables provided by template_preprocess(), so we add them here if 1169 // they don't already exist. We don't want to run template_preprocess() 1170 // twice (it would be inefficient and mess up zebra striping), so we use the 1171 // 'directory' variable to determine if it has already run, which while not 1172 // completely intuitive, is reasonably safe, and allows us to save on the 1173 // overhead of adding some new variable to track that. 1174 if (!isset($variables['directory'])) { 1175 $default_template_variables = array(); 1176 template_preprocess($default_template_variables, $hook); 1177 $variables += $default_template_variables; 1178 } 1179 1180 // Render the output using the template file. 1181 $template_file = $info['template'] . $extension; 1182 if (isset($info['path'])) { 1183 $template_file = $info['path'] . '/' . $template_file; 1184 } 1185 $output = $render_function($template_file, $variables); 1186 } 1187 1188 // restore path_to_theme() 1189 $theme_path = $temp; 1190 return $output; 1191 } 1192 1193 /** 1194 * Return the path to the current themed element. 1195 * 1196 * It can point to the active theme or the module handling a themed implementation. 1197 * For example, when invoked within the scope of a theming call it will depend 1198 * on where the theming function is handled. If implemented from a module, it 1199 * will point to the module. If implemented from the active theme, it will point 1200 * to the active theme. When called outside the scope of a theming call, it will 1201 * always point to the active theme. 1202 */ 1203 function path_to_theme() { 1204 global $theme_path; 1205 1206 if (!isset($theme_path)) { 1207 drupal_theme_initialize(); 1208 } 1209 1210 return $theme_path; 1211 } 1212 1213 /** 1214 * Allow themes and/or theme engines to easily discover overridden theme functions. 1215 * 1216 * @param $cache 1217 * The existing cache of theme hooks to test against. 1218 * @param $prefixes 1219 * An array of prefixes to test, in reverse order of importance. 1220 * 1221 * @return $implementations 1222 * The functions found, suitable for returning from hook_theme; 1223 */ 1224 function drupal_find_theme_functions($cache, $prefixes) { 1225 $implementations = array(); 1226 $functions = get_defined_functions(); 1227 1228 foreach ($cache as $hook => $info) { 1229 foreach ($prefixes as $prefix) { 1230 // Find theme functions that implement possible "suggestion" variants of 1231 // registered theme hooks and add those as new registered theme hooks. 1232 // The 'pattern' key defines a common prefix that all suggestions must 1233 // start with. The default is the name of the hook followed by '__'. An 1234 // 'base hook' key is added to each entry made for a found suggestion, 1235 // so that common functionality can be implemented for all suggestions of 1236 // the same base hook. To keep things simple, deep hierarchy of 1237 // suggestions is not supported: each suggestion's 'base hook' key 1238 // refers to a base hook, not to another suggestion, and all suggestions 1239 // are found using the base hook's pattern, not a pattern from an 1240 // intermediary suggestion. 1241 $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__'); 1242 if (!isset($info['base hook']) && !empty($pattern)) { 1243 $matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $functions['user']); 1244 if ($matches) { 1245 foreach ($matches as $match) { 1246 $new_hook = substr($match, strlen($prefix) + 1); 1247 $arg_name = isset($info['variables']) ? 'variables' : 'render element'; 1248 $implementations[$new_hook] = array( 1249 'function' => $match, 1250 $arg_name => $info[$arg_name], 1251 'base hook' => $hook, 1252 ); 1253 } 1254 } 1255 } 1256 // Find theme functions that implement registered theme hooks and include 1257 // that in what is returned so that the registry knows that the theme has 1258 // this implementation. 1259 if (function_exists($prefix . '_' . $hook)) { 1260 $implementations[$hook] = array( 1261 'function' => $prefix . '_' . $hook, 1262 ); 1263 } 1264 } 1265 } 1266 1267 return $implementations; 1268 } 1269 1270 /** 1271 * Allow themes and/or theme engines to easily discover overridden templates. 1272 * 1273 * @param $cache 1274 * The existing cache of theme hooks to test against. 1275 * @param $extension 1276 * The extension that these templates will have. 1277 * @param $path 1278 * The path to search. 1279 */ 1280 function drupal_find_theme_templates($cache, $extension, $path) { 1281 $implementations = array(); 1282 1283 // Collect paths to all sub-themes grouped by base themes. These will be 1284 // used for filtering. This allows base themes to have sub-themes in its 1285 // folder hierarchy without affecting the base themes template discovery. 1286 $theme_paths = array(); 1287 foreach (list_themes() as $theme_info) { 1288 if (!empty($theme_info->base_theme)) { 1289 $theme_paths[$theme_info->base_theme][$theme_info->name] = dirname($theme_info->filename); 1290 } 1291 } 1292 foreach ($theme_paths as $basetheme => $subthemes) { 1293 foreach ($subthemes as $subtheme => $subtheme_path) { 1294 if (isset($theme_paths[$subtheme])) { 1295 $theme_paths[$basetheme] = array_merge($theme_paths[$basetheme], $theme_paths[$subtheme]); 1296 } 1297 } 1298 } 1299 global $theme; 1300 $subtheme_paths = isset($theme_paths[$theme]) ? $theme_paths[$theme] : array(); 1301 1302 // Escape the periods in the extension. 1303 $regex = '/' . str_replace('.', '\.', $extension) . '$/'; 1304 // Get a listing of all template files in the path to search. 1305 $files = drupal_system_listing($regex, $path, 'name', 0); 1306 1307 // Find templates that implement registered theme hooks and include that in 1308 // what is returned so that the registry knows that the theme has this 1309 // implementation. 1310 foreach ($files as $template => $file) { 1311 // Ignore sub-theme templates for the current theme. 1312 if (strpos($file->uri, str_replace($subtheme_paths, '', $file->uri)) !== 0) { 1313 continue; 1314 } 1315 // Chop off the remaining extensions if there are any. $template already 1316 // has the rightmost extension removed, but there might still be more, 1317 // such as with .tpl.php, which still has .tpl in $template at this point. 1318 if (($pos = strpos($template, '.')) !== FALSE) { 1319 $template = substr($template, 0, $pos); 1320 } 1321 // Transform - in filenames to _ to match function naming scheme 1322 // for the purposes of searching. 1323 $hook = strtr($template, '-', '_'); 1324 if (isset($cache[$hook])) { 1325 $implementations[$hook] = array( 1326 'template' => $template, 1327 'path' => dirname($file->uri), 1328 ); 1329 } 1330 } 1331 1332 // Find templates that implement possible "suggestion" variants of registered 1333 // theme hooks and add those as new registered theme hooks. See 1334 // drupal_find_theme_functions() for more information about suggestions and 1335 // the use of 'pattern' and 'base hook'. 1336 $patterns = array_keys($files); 1337 foreach ($cache as $hook => $info) { 1338 $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__'); 1339 if (!isset($info['base hook']) && !empty($pattern)) { 1340 // Transform _ in pattern to - to match file naming scheme 1341 // for the purposes of searching. 1342 $pattern = strtr($pattern, '_', '-'); 1343 1344 $matches = preg_grep('/^' . $pattern . '/', $patterns); 1345 if ($matches) { 1346 foreach ($matches as $match) { 1347 $file = substr($match, 0, strpos($match, '.')); 1348 // Put the underscores back in for the hook name and register this pattern. 1349 $arg_name = isset($info['variables']) ? 'variables' : 'render element'; 1350 $implementations[strtr($file, '-', '_')] = array( 1351 'template' => $file, 1352 'path' => dirname($files[$match]->uri), 1353 $arg_name => $info[$arg_name], 1354 'base hook' => $hook, 1355 ); 1356 } 1357 } 1358 } 1359 } 1360 return $implementations; 1361 } 1362 1363 /** 1364 * Retrieve a setting for the current theme or for a given theme. 1365 * 1366 * The final setting is obtained from the last value found in the following 1367 * sources: 1368 * - the default global settings specified in this function 1369 * - the default theme-specific settings defined in any base theme's .info file 1370 * - the default theme-specific settings defined in the theme's .info file 1371 * - the saved values from the global theme settings form 1372 * - the saved values from the theme's settings form 1373 * To only retrieve the default global theme setting, an empty string should be 1374 * given for $theme. 1375 * 1376 * @param $setting_name 1377 * The name of the setting to be retrieved. 1378 * @param $theme 1379 * The name of a given theme; defaults to the current theme. 1380 * 1381 * @return 1382 * The value of the requested setting, NULL if the setting does not exist. 1383 */ 1384 function theme_get_setting($setting_name, $theme = NULL) { 1385 $cache = &drupal_static(__FUNCTION__, array()); 1386 1387 // If no key is given, use the current theme if we can determine it. 1388 if (!isset($theme)) { 1389 $theme = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : ''; 1390 } 1391 1392 if (empty($cache[$theme])) { 1393 // Set the default values for each global setting. 1394 // To add new global settings, add their default values below, and then 1395 // add form elements to system_theme_settings() in system.admin.inc. 1396 $cache[$theme] = array( 1397 'default_logo' => 1, 1398 'logo_path' => '', 1399 'default_favicon' => 1, 1400 'favicon_path' => '', 1401 // Use the IANA-registered MIME type for ICO files as default. 1402 'favicon_mimetype' => 'image/vnd.microsoft.icon', 1403 ); 1404 // Turn on all default features. 1405 $features = _system_default_theme_features(); 1406 foreach ($features as $feature) { 1407 $cache[$theme]['toggle_' . $feature] = 1; 1408 } 1409 1410 // Get the values for the theme-specific settings from the .info files of 1411 // the theme and all its base themes. 1412 if ($theme) { 1413 $themes = list_themes(); 1414 $theme_object = $themes[$theme]; 1415 1416 // Create a list which includes the current theme and all its base themes. 1417 if (isset($theme_object->base_themes)) { 1418 $theme_keys = array_keys($theme_object->base_themes); 1419 $theme_keys[] = $theme; 1420 } 1421 else { 1422 $theme_keys = array($theme); 1423 } 1424 foreach ($theme_keys as $theme_key) { 1425 if (!empty($themes[$theme_key]->info['settings'])) { 1426 $cache[$theme] = array_merge($cache[$theme], $themes[$theme_key]->info['settings']); 1427 } 1428 } 1429 } 1430 1431 // Get the saved global settings from the database. 1432 $cache[$theme] = array_merge($cache[$theme], variable_get('theme_settings', array())); 1433 1434 if ($theme) { 1435 // Get the saved theme-specific settings from the database. 1436 $cache[$theme] = array_merge($cache[$theme], variable_get('theme_' . $theme . '_settings', array())); 1437 1438 // If the theme does not support a particular feature, override the global 1439 // setting and set the value to NULL. 1440 if (!empty($theme_object->info['features'])) { 1441 foreach ($features as $feature) { 1442 if (!in_array($feature, $theme_object->info['features'])) { 1443 $cache[$theme]['toggle_' . $feature] = NULL; 1444 } 1445 } 1446 } 1447 1448 // Generate the path to the logo image. 1449 if ($cache[$theme]['toggle_logo']) { 1450 if ($cache[$theme]['default_logo']) { 1451 $cache[$theme]['logo'] = file_create_url(dirname($theme_object->filename) . '/logo.png'); 1452 } 1453 elseif ($cache[$theme]['logo_path']) { 1454 $cache[$theme]['logo'] = file_create_url($cache[$theme]['logo_path']); 1455 } 1456 } 1457 1458 // Generate the path to the favicon. 1459 if ($cache[$theme]['toggle_favicon']) { 1460 if ($cache[$theme]['default_favicon']) { 1461 if (file_exists($favicon = dirname($theme_object->filename) . '/favicon.ico')) { 1462 $cache[$theme]['favicon'] = file_create_url($favicon); 1463 } 1464 else { 1465 $cache[$theme]['favicon'] = file_create_url('misc/favicon.ico'); 1466 } 1467 } 1468 elseif ($cache[$theme]['favicon_path']) { 1469 $cache[$theme]['favicon'] = file_create_url($cache[$theme]['favicon_path']); 1470 } 1471 else { 1472 $cache[$theme]['toggle_favicon'] = FALSE; 1473 } 1474 } 1475 } 1476 } 1477 1478 return isset($cache[$theme][$setting_name]) ? $cache[$theme][$setting_name] : NULL; 1479 } 1480 1481 /** 1482 * Render a system default template, which is essentially a PHP template. 1483 * 1484 * @param $template_file 1485 * The filename of the template to render. 1486 * @param $variables 1487 * A keyed array of variables that will appear in the output. 1488 * 1489 * @return 1490 * The output generated by the template. 1491 */ 1492 function theme_render_template($template_file, $variables) { 1493 extract($variables, EXTR_SKIP); // Extract the variables to a local namespace 1494 ob_start(); // Start output buffering 1495 include DRUPAL_ROOT . '/' . $template_file; // Include the template file 1496 return ob_get_clean(); // End buffering and return its contents 1497 } 1498 1499 /** 1500 * Enable a given list of themes. 1501 * 1502 * @param $theme_list 1503 * An array of theme names. 1504 */ 1505 function theme_enable($theme_list) { 1506 drupal_clear_css_cache(); 1507 1508 foreach ($theme_list as $key) { 1509 db_update('system') 1510 ->fields(array('status' => 1)) 1511 ->condition('type', 'theme') 1512 ->condition('name', $key) 1513 ->execute(); 1514 } 1515 1516 list_themes(TRUE); 1517 menu_rebuild(); 1518 drupal_theme_rebuild(); 1519 1520 // Invoke hook_themes_enabled() after the themes have been enabled. 1521 module_invoke_all('themes_enabled', $theme_list); 1522 } 1523 1524 /** 1525 * Disable a given list of themes. 1526 * 1527 * @param $theme_list 1528 * An array of theme names. 1529 */ 1530 function theme_disable($theme_list) { 1531 // Don't disable the default theme. 1532 if ($pos = array_search(variable_get('theme_default', 'bartik'), $theme_list) !== FALSE) { 1533 unset($theme_list[$pos]); 1534 if (empty($theme_list)) { 1535 return; 1536 } 1537 } 1538 1539 drupal_clear_css_cache(); 1540 1541 foreach ($theme_list as $key) { 1542 db_update('system') 1543 ->fields(array('status' => 0)) 1544 ->condition('type', 'theme') 1545 ->condition('name', $key) 1546 ->execute(); 1547 } 1548 1549 list_themes(TRUE); 1550 menu_rebuild(); 1551 drupal_theme_rebuild(); 1552 1553 // Invoke hook_themes_disabled after the themes have been disabled. 1554 module_invoke_all('themes_disabled', $theme_list); 1555 } 1556 1557 /** 1558 * @addtogroup themeable 1559 * @{ 1560 */ 1561 1562 /** 1563 * Returns HTML for status and/or error messages, grouped by type. 1564 * 1565 * An invisible heading identifies the messages for assistive technology. 1566 * Sighted users see a colored box. See http://www.w3.org/TR/WCAG-TECHS/H69.html 1567 * for info. 1568 * 1569 * @param $variables 1570 * An associative array containing: 1571 * - display: (optional) Set to 'status' or 'error' to display only messages 1572 * of that type. 1573 */ 1574 function theme_status_messages($variables) { 1575 $display = $variables['display']; 1576 $output = ''; 1577 1578 $status_heading = array( 1579 'status' => t('Status message'), 1580 'error' => t('Error message'), 1581 'warning' => t('Warning message'), 1582 ); 1583 foreach (drupal_get_messages($display) as $type => $messages) { 1584 $output .= "<div class=\"messages $type\">\n"; 1585 if (!empty($status_heading[$type])) { 1586 $output .= '<h2 class="element-invisible">' . $status_heading[$type] . "</h2>\n"; 1587 } 1588 if (count($messages) > 1) { 1589 $output .= " <ul>\n"; 1590 foreach ($messages as $message) { 1591 $output .= ' <li>' . $message . "</li>\n"; 1592 } 1593 $output .= " </ul>\n"; 1594 } 1595 else { 1596 $output .= $messages[0]; 1597 } 1598 $output .= "</div>\n"; 1599 } 1600 return $output; 1601 } 1602 1603 /** 1604 * Returns HTML for a link. 1605 * 1606 * All Drupal code that outputs a link should call the l() function. That 1607 * function performs some initial preprocessing, and then, if necessary, calls 1608 * theme('link') for rendering the anchor tag. 1609 * 1610 * To optimize performance for sites that don't need custom theming of links, 1611 * the l() function includes an inline copy of this function, and uses that copy 1612 * if none of the enabled modules or the active theme implement any preprocess 1613 * or process functions or override this theme implementation. 1614 * 1615 * @param $variables 1616 * An associative array containing the keys 'text', 'path', and 'options'. See 1617 * the l() function for information about these variables. 1618 * 1619 * @see l() 1620 */ 1621 function theme_link($variables) { 1622 return '<a href="' . check_plain(url($variables['path'], $variables['options'])) . '"' . drupal_attributes($variables['options']['attributes']) . '>' . ($variables['options']['html'] ? $variables['text'] : check_plain($variables['text'])) . '</a>'; 1623 } 1624 1625 /** 1626 * Returns HTML for a set of links. 1627 * 1628 * @param $variables 1629 * An associative array containing: 1630 * - links: An associative array of links to be themed. The key for each link 1631 * is used as its CSS class. Each link should be itself an array, with the 1632 * following elements: 1633 * - title: The link text. 1634 * - href: The link URL. If omitted, the 'title' is shown as a plain text 1635 * item in the links list. 1636 * - html: (optional) Whether or not 'title' is HTML. If set, the title 1637 * will not be passed through check_plain(). 1638 * - attributes: (optional) Attributes for the anchor, or for the <span> tag 1639 * used in its place if no 'href' is supplied. If element 'class' is 1640 * included, it must be an array of one or more class names. 1641 * If the 'href' element is supplied, the entire link array is passed to l() 1642 * as its $options parameter. 1643 * - attributes: A keyed array of attributes for the UL containing the 1644 * list of links. 1645 * - heading: (optional) A heading to precede the links. May be an associative 1646 * array or a string. If it's an array, it can have the following elements: 1647 * - text: The heading text. 1648 * - level: The heading level (e.g. 'h2', 'h3'). 1649 * - class: (optional) An array of the CSS classes for the heading. 1650 * When using a string it will be used as the text of the heading and the 1651 * level will default to 'h2'. Headings should be used on navigation menus 1652 * and any list of links that consistently appears on multiple pages. To 1653 * make the heading invisible use the 'element-invisible' CSS class. Do not 1654 * use 'display:none', which removes it from screen-readers and assistive 1655 * technology. Headings allow screen-reader and keyboard only users to 1656 * navigate to or skip the links. See 1657 * http://juicystudio.com/article/screen-readers-display-none.php and 1658 * http://www.w3.org/TR/WCAG-TECHS/H42.html for more information. 1659 */ 1660 function theme_links($variables) { 1661 $links = $variables['links']; 1662 $attributes = $variables['attributes']; 1663 $heading = $variables['heading']; 1664 global $language_url; 1665 $output = ''; 1666 1667 if (count($links) > 0) { 1668 $output = ''; 1669 1670 // Treat the heading first if it is present to prepend it to the 1671 // list of links. 1672 if (!empty($heading)) { 1673 if (is_string($heading)) { 1674 // Prepare the array that will be used when the passed heading 1675 // is a string. 1676 $heading = array( 1677 'text' => $heading, 1678 // Set the default level of the heading. 1679 'level' => 'h2', 1680 ); 1681 } 1682 $output .= '<' . $heading['level']; 1683 if (!empty($heading['class'])) { 1684 $output .= drupal_attributes(array('class' => $heading['class'])); 1685 } 1686 $output .= '>' . check_plain($heading['text']) . '</' . $heading['level'] . '>'; 1687 } 1688 1689 $output .= '<ul' . drupal_attributes($attributes) . '>'; 1690 1691 $num_links = count($links); 1692 $i = 1; 1693 1694 foreach ($links as $key => $link) { 1695 $class = array($key); 1696 1697 // Add first, last and active classes to the list of links to help out themers. 1698 if ($i == 1) { 1699 $class[] = 'first'; 1700 } 1701 if ($i == $num_links) { 1702 $class[] = 'last'; 1703 } 1704 if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page())) 1705 && (empty($link['language']) || $link['language']->language == $language_url->language)) { 1706 $class[] = 'active'; 1707 } 1708 $output .= '<li' . drupal_attributes(array('class' => $class)) . '>'; 1709 1710 if (isset($link['href'])) { 1711 // Pass in $link as $options, they share the same keys. 1712 $output .= l($link['title'], $link['href'], $link); 1713 } 1714 elseif (!empty($link['title'])) { 1715 // Some links are actually not links, but we wrap these in <span> for adding title and class attributes. 1716 if (empty($link['html'])) { 1717 $link['title'] = check_plain($link['title']); 1718 } 1719 $span_attributes = ''; 1720 if (isset($link['attributes'])) { 1721 $span_attributes = drupal_attributes($link['attributes']); 1722 } 1723 $output .= '<span' . $span_attributes . '>' . $link['title'] . '</span>'; 1724 } 1725 1726 $i++; 1727 $output .= "</li>\n"; 1728 } 1729 1730 $output .= '</ul>'; 1731 } 1732 1733 return $output; 1734 } 1735 1736 /** 1737 * Returns HTML for an image. 1738 * 1739 * @param $variables 1740 * An associative array containing: 1741 * - path: Either the path of the image file (relative to base_path()) or a 1742 * full URL. 1743 * - width: The width of the image (if known). 1744 * - height: The height of the image (if known). 1745 * - alt: The alternative text for text-based browsers. HTML 4 and XHTML 1.0 1746 * always require an alt attribute. The HTML 5 draft allows the alt 1747 * attribute to be omitted in some cases. Therefore, this variable defaults 1748 * to an empty string, but can be set to NULL for the attribute to be 1749 * omitted. Usually, neither omission nor an empty string satisfies 1750 * accessibility requirements, so it is strongly encouraged for code calling 1751 * theme('image') to pass a meaningful value for this variable. 1752 * - http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8 1753 * - http://www.w3.org/TR/xhtml1/dtds.html 1754 * - http://dev.w3.org/html5/spec/Overview.html#alt 1755 * - title: The title text is displayed when the image is hovered in some 1756 * popular browsers. 1757 * - attributes: Associative array of attributes to be placed in the img tag. 1758 */ 1759 function theme_image($variables) { 1760 $attributes = $variables['attributes']; 1761 $attributes['src'] = file_create_url($variables['path']); 1762 1763 foreach (array('width', 'height', 'alt', 'title') as $key) { 1764 1765 if (isset($variables[$key])) { 1766 $attributes[$key] = $variables[$key]; 1767 } 1768 } 1769 1770 return '<img' . drupal_attributes($attributes) . ' />'; 1771 } 1772 1773 /** 1774 * Returns HTML for a breadcrumb trail. 1775 * 1776 * @param $variables 1777 * An associative array containing: 1778 * - breadcrumb: An array containing the breadcrumb links. 1779 */ 1780 function theme_breadcrumb($variables) { 1781 $breadcrumb = $variables['breadcrumb']; 1782 1783 if (!empty($breadcrumb)) { 1784 // Provide a navigational heading to give context for breadcrumb links to 1785 // screen-reader users. Make the heading invisible with .element-invisible. 1786 $output = '<h2 class="element-invisible">' . t('You are here') . '</h2>'; 1787 1788 $output .= '<div class="breadcrumb">' . implode(' » ', $breadcrumb) . '</div>'; 1789 return $output; 1790 } 1791 } 1792 1793 /** 1794 * Returns HTML for a table. 1795 * 1796 * @param $variables 1797 * An associative array containing: 1798 * - header: An array containing the table headers. Each element of the array 1799 * can be either a localized string or an associative array with the 1800 * following keys: 1801 * - "data": The localized title of the table column. 1802 * - "field": The database field represented in the table column (required 1803 * if user is to be able to sort on this column). 1804 * - "sort": A default sort order for this column ("asc" or "desc"). 1805 * - Any HTML attributes, such as "colspan", to apply to the column header 1806 * cell. 1807 * - rows: An array of table rows. Every row is an array of cells, or an 1808 * associative array with the following keys: 1809 * - "data": an array of cells 1810 * - Any HTML attributes, such as "class", to apply to the table row. 1811 * - "no_striping": a boolean indicating that the row should receive no 1812 * 'even / odd' styling. Defaults to FALSE. 1813 * Each cell can be either a string or an associative array with the 1814 * following keys: 1815 * - "data": The string to display in the table cell. 1816 * - "header": Indicates this cell is a header. 1817 * - Any HTML attributes, such as "colspan", to apply to the table cell. 1818 * Here's an example for $rows: 1819 * @code 1820 * $rows = array( 1821 * // Simple row 1822 * array( 1823 * 'Cell 1', 'Cell 2', 'Cell 3' 1824 * ), 1825 * // Row with attributes on the row and some of its cells. 1826 * array( 1827 * 'data' => array('Cell 1', array('data' => 'Cell 2', 'colspan' => 2)), 'class' => array('funky') 1828 * ) 1829 * ); 1830 * @endcode 1831 * - attributes: An array of HTML attributes to apply to the table tag. 1832 * - caption: A localized string to use for the <caption> tag. 1833 * - colgroups: An array of column groups. Each element of the array can be 1834 * either: 1835 * - An array of columns, each of which is an associative array of HTML 1836 * attributes applied to the COL element. 1837 * - An array of attributes applied to the COLGROUP element, which must 1838 * include a "data" attribute. To add attributes to COL elements, set the 1839 * "data" attribute with an array of columns, each of which is an 1840 * associative array of HTML attributes. 1841 * Here's an example for $colgroup: 1842 * @code 1843 * $colgroup = array( 1844 * // COLGROUP with one COL element. 1845 * array( 1846 * array( 1847 * 'class' => array('funky'), // Attribute for the COL element. 1848 * ), 1849 * ), 1850 * // Colgroup with attributes and inner COL elements. 1851 * array( 1852 * 'data' => array( 1853 * array( 1854 * 'class' => array('funky'), // Attribute for the COL element. 1855 * ), 1856 * ), 1857 * 'class' => array('jazzy'), // Attribute for the COLGROUP element. 1858 * ), 1859 * ); 1860 * @endcode 1861 * These optional tags are used to group and set properties on columns 1862 * within a table. For example, one may easily group three columns and 1863 * apply same background style to all. 1864 * - sticky: Use a "sticky" table header. 1865 * - empty: The message to display in an extra row if table does not have any 1866 * rows. 1867 */ 1868 function theme_table($variables) { 1869 $header = $variables['header']; 1870 $rows = $variables['rows']; 1871 $attributes = $variables['attributes']; 1872 $caption = $variables['caption']; 1873 $colgroups = $variables['colgroups']; 1874 $sticky = $variables['sticky']; 1875 $empty = $variables['empty']; 1876 1877 // Add sticky headers, if applicable. 1878 if (count($header) && $sticky) { 1879 drupal_add_js('misc/tableheader.js'); 1880 // Add 'sticky-enabled' class to the table to identify it for JS. 1881 // This is needed to target tables constructed by this function. 1882 $attributes['class'][] = 'sticky-enabled'; 1883 } 1884 1885 $output = '<table' . drupal_attributes($attributes) . ">\n"; 1886 1887 if (isset($caption)) { 1888 $output .= '<caption>' . $caption . "</caption>\n"; 1889 } 1890 1891 // Format the table columns: 1892 if (count($colgroups)) { 1893 foreach ($colgroups as $number => $colgroup) { 1894 $attributes = array(); 1895 1896 // Check if we're dealing with a simple or complex column 1897 if (isset($colgroup['data'])) { 1898 foreach ($colgroup as $key => $value) { 1899 if ($key == 'data') { 1900 $cols = $value; 1901 } 1902 else { 1903 $attributes[$key] = $value; 1904 } 1905 } 1906 } 1907 else { 1908 $cols = $colgroup; 1909 } 1910 1911 // Build colgroup 1912 if (is_array($cols) && count($cols)) { 1913 $output .= ' <colgroup' . drupal_attributes($attributes) . '>'; 1914 $i = 0; 1915 foreach ($cols as $col) { 1916 $output .= ' <col' . drupal_attributes($col) . ' />'; 1917 } 1918 $output .= " </colgroup>\n"; 1919 } 1920 else { 1921 $output .= ' <colgroup' . drupal_attributes($attributes) . " />\n"; 1922 } 1923 } 1924 } 1925 1926 // Add the 'empty' row message if available. 1927 if (!count($rows) && $empty) { 1928 $header_count = 0; 1929 foreach ($header as $header_cell) { 1930 if (is_array($header_cell)) { 1931 $header_count += isset($header_cell['colspan']) ? $header_cell['colspan'] : 1; 1932 } 1933 else { 1934 $header_count++; 1935 } 1936 } 1937 $rows[] = array(array('data' => $empty, 'colspan' => $header_count, 'class' => array('empty', 'message'))); 1938 } 1939 1940 // Format the table header: 1941 if (count($header)) { 1942 $ts = tablesort_init($header); 1943 // HTML requires that the thead tag has tr tags in it followed by tbody 1944 // tags. Using ternary operator to check and see if we have any rows. 1945 $output .= (count($rows) ? ' <thead><tr>' : ' <tr>'); 1946 foreach ($header as $cell) { 1947 $cell = tablesort_header($cell, $header, $ts); 1948 $output .= _theme_table_cell($cell, TRUE); 1949 } 1950 // Using ternary operator to close the tags based on whether or not there are rows 1951 $output .= (count($rows) ? " </tr></thead>\n" : "</tr>\n"); 1952 } 1953 else { 1954 $ts = array(); 1955 } 1956 1957 // Format the table rows: 1958 if (count($rows)) { 1959 $output .= "<tbody>\n"; 1960 $flip = array('even' => 'odd', 'odd' => 'even'); 1961 $class = 'even'; 1962 foreach ($rows as $number => $row) { 1963 $attributes = array(); 1964 1965 // Check if we're dealing with a simple or complex row 1966 if (isset($row['data'])) { 1967 foreach ($row as $key => $value) { 1968 if ($key == 'data') { 1969 $cells = $value; 1970 } 1971 else { 1972 $attributes[$key] = $value; 1973 } 1974 } 1975 } 1976 else { 1977 $cells = $row; 1978 } 1979 if (count($cells)) { 1980 // Add odd/even class 1981 if (empty($row['no_striping'])) { 1982 $class = $flip[$class]; 1983 $attributes['class'][] = $class; 1984 } 1985 1986 // Build row 1987 $output .= ' <tr' . drupal_attributes($attributes) . '>'; 1988 $i = 0; 1989 foreach ($cells as $cell) { 1990 $cell = tablesort_cell($cell, $header, $ts, $i++); 1991 $output .= _theme_table_cell($cell); 1992 } 1993 $output .= " </tr>\n"; 1994 } 1995 } 1996 $output .= "</tbody>\n"; 1997 } 1998 1999 $output .= "</table>\n"; 2000 return $output; 2001 } 2002 2003 /** 2004 * Returns HTML for a sort icon. 2005 * 2006 * @param $variables 2007 * An associative array containing: 2008 * - style: Set to either 'asc' or 'desc', this determines which icon to show. 2009 */ 2010 function theme_tablesort_indicator($variables) { 2011 if ($variables['style'] == "asc") { 2012 return theme('image', array('path' => 'misc/arrow-asc.png', 'width' => 13, 'height' => 13, 'alt' => t('sort ascending'), 'title' => t('sort ascending'))); 2013 } 2014 else { 2015 return theme('image', array('path' => 'misc/arrow-desc.png', 'width' => 13, 'height' => 13, 'alt' => t('sort descending'), 'title' => t('sort descending'))); 2016 } 2017 } 2018 2019 /** 2020 * Returns HTML for a marker for new or updated content. 2021 * 2022 * @param $variables 2023 * An associative array containing: 2024 * - type: Number representing the marker type to display. See MARK_NEW, 2025 * MARK_UPDATED, MARK_READ. 2026 */ 2027 function theme_mark($variables) { 2028 $type = $variables['type']; 2029 global $user; 2030 if ($user->uid) { 2031 if ($type == MARK_NEW) { 2032 return ' <span class="marker">' . t('new') . '</span>'; 2033 } 2034 elseif ($type == MARK_UPDATED) { 2035 return ' <span class="marker">' . t('updated') . '</span>'; 2036 } 2037 } 2038 } 2039 2040 /** 2041 * Returns HTML for a list or nested list of items. 2042 * 2043 * @param $variables 2044 * An associative array containing: 2045 * - items: An array of items to be displayed in the list. If an item is a 2046 * string, then it is used as is. If an item is an array, then the "data" 2047 * element of the array is used as the contents of the list item. If an item 2048 * is an array with a "children" element, those children are displayed in a 2049 * nested list. All other elements are treated as attributes of the list 2050 * item element. 2051 * - title: The title of the list. 2052 * - type: The type of list to return (e.g. "ul", "ol"). 2053 * - attributes: The attributes applied to the list element. 2054 */ 2055 function theme_item_list($variables) { 2056 $items = $variables['items']; 2057 $title = $variables['title']; 2058 $type = $variables['type']; 2059 $attributes = $variables['attributes']; 2060 2061 // Only output the list container and title, if there are any list items. 2062 // Check to see whether the block title exists before adding a header. 2063 // Empty headers are not semantic and present accessibility challenges. 2064 $output = '<div class="item-list">'; 2065 if (isset($title) && $title !== '') { 2066 $output .= '<h3>' . $title . '</h3>'; 2067 } 2068 2069 if (!empty($items)) { 2070 $output .= "<$type" . drupal_attributes($attributes) . '>'; 2071 $num_items = count($items); 2072 $i = 0; 2073 foreach ($items as $item) { 2074 $attributes = array(); 2075 $children = array(); 2076 $data = ''; 2077 $i++; 2078 if (is_array($item)) { 2079 foreach ($item as $key => $value) { 2080 if ($key == 'data') { 2081 $data = $value; 2082 } 2083 elseif ($key == 'children') { 2084 $children = $value; 2085 } 2086 else { 2087 $attributes[$key] = $value; 2088 } 2089 } 2090 } 2091 else { 2092 $data = $item; 2093 } 2094 if (count($children) > 0) { 2095 // Render nested list. 2096 $data .= theme_item_list(array('items' => $children, 'title' => NULL, 'type' => $type, 'attributes' => $attributes)); 2097 } 2098 if ($i == 1) { 2099 $attributes['class'][] = 'first'; 2100 } 2101 if ($i == $num_items) { 2102 $attributes['class'][] = 'last'; 2103 } 2104 $output .= '<li' . drupal_attributes($attributes) . '>' . $data . "</li>\n"; 2105 } 2106 $output .= "</$type>"; 2107 } 2108 $output .= '</div>'; 2109 return $output; 2110 } 2111 2112 /** 2113 * Returns HTML for a "more help" link. 2114 * 2115 * @param $variables 2116 * An associative array containing: 2117 * - url: The URL for the link. 2118 */ 2119 function theme_more_help_link($variables) { 2120 return '<div class="more-help-link">' . l(t('More help'), $variables['url']) . '</div>'; 2121 } 2122 2123 /** 2124 * Returns HTML for a feed icon. 2125 * 2126 * @param $variables 2127 * An associative array containing: 2128 * - url: An internal system path or a fully qualified external URL of the 2129 * feed. 2130 * - title: A descriptive title of the feed. 2131 */ 2132 function theme_feed_icon($variables) { 2133 $text = t('Subscribe to !feed-title', array('!feed-title' => $variables['title'])); 2134 if ($image = theme('image', array('path' => 'misc/feed.png', 'width' => 16, 'height' => 16, 'alt' => $text))) { 2135 return l($image, $variables['url'], array('html' => TRUE, 'attributes' => array('class' => array('feed-icon'), 'title' => $text))); 2136 } 2137 } 2138 2139 /** 2140 * Returns HTML for a generic HTML tag with attributes. 2141 * 2142 * @param $variables 2143 * An associative array containing: 2144 * - element: An associative array describing the tag: 2145 * - #tag: The tag name to output. Typical tags added to the HTML HEAD: 2146 * - meta: To provide meta information, such as a page refresh. 2147 * - link: To refer to stylesheets and other contextual information. 2148 * - script: To load JavaScript. 2149 * - #attributes: (optional) An array of HTML attributes to apply to the 2150 * tag. 2151 * - #value: (optional) A string containing tag content, such as inline CSS. 2152 * - #value_prefix: (optional) A string to prepend to #value, e.g. a CDATA 2153 * wrapper prefix. 2154 * - #value_suffix: (optional) A string to append to #value, e.g. a CDATA 2155 * wrapper suffix. 2156 */ 2157 function theme_html_tag($variables) { 2158 $element = $variables['element']; 2159 $attributes = isset($element['#attributes']) ? drupal_attributes($element['#attributes']) : ''; 2160 if (!isset($element['#value'])) { 2161 return '<' . $element['#tag'] . $attributes . " />\n"; 2162 } 2163 else { 2164 $output = '<' . $element['#tag'] . $attributes . '>'; 2165 if (isset($element['#value_prefix'])) { 2166 $output .= $element['#value_prefix']; 2167 } 2168 $output .= $element['#value']; 2169 if (isset($element['#value_suffix'])) { 2170 $output .= $element['#value_suffix']; 2171 } 2172 $output .= '</' . $element['#tag'] . ">\n"; 2173 return $output; 2174 } 2175 } 2176 2177 /** 2178 * Returns HTML for a "more" link, like those used in blocks. 2179 * 2180 * @param $variables 2181 * An associative array containing: 2182 * - url: The URL of the main page. 2183 * - title: A descriptive verb for the link, like 'Read more'. 2184 */ 2185 function theme_more_link($variables) { 2186 return '<div class="more-link">' . l(t('More'), $variables['url'], array('attributes' => array('title' => $variables['title']))) . '</div>'; 2187 } 2188 2189 /** 2190 * Returns HTML for a username, potentially linked to the user's page. 2191 * 2192 * @param $variables 2193 * An associative array containing: 2194 * - account: The user object to format. 2195 * - name: The user's name, sanitized. 2196 * - extra: Additional text to append to the user's name, sanitized. 2197 * - link_path: The path or URL of the user's profile page, home page, or 2198 * other desired page to link to for more information about the user. 2199 * - link_options: An array of options to pass to the l() function's $options 2200 * parameter if linking the user's name to the user's page. 2201 * - attributes_array: An array of attributes to pass to the 2202 * drupal_attributes() function if not linking to the user's page. 2203 * 2204 * @see template_preprocess_username() 2205 * @see template_process_username() 2206 */ 2207 function theme_username($variables) { 2208 if (isset($variables['link_path'])) { 2209 // We have a link path, so we should generate a link using l(). 2210 // Additional classes may be added as array elements like 2211 // $variables['link_options']['attributes']['class'][] = 'myclass'; 2212 $output = l($variables['name'] . $variables['extra'], $variables['link_path'], $variables['link_options']); 2213 } 2214 else { 2215 // Modules may have added important attributes so they must be included 2216 // in the output. Additional classes may be added as array elements like 2217 // $variables['attributes_array']['class'][] = 'myclass'; 2218 $output = '<span' . drupal_attributes($variables['attributes_array']) . '>' . $variables['name'] . $variables['extra'] . '</span>'; 2219 } 2220 return $output; 2221 } 2222 2223 /** 2224 * Returns HTML for a progress bar. 2225 * 2226 * Note that the core Batch API uses this only for non-JavaScript batch jobs. 2227 * 2228 * @param $variables 2229 * An associative array containing: 2230 * - percent: The percentage of the progress. 2231 * - message: A string containing information to be displayed. 2232 */ 2233 function theme_progress_bar($variables) { 2234 $output = '<div id="progress" class="progress">'; 2235 $output .= '<div class="bar"><div class="filled" style="width: ' . $variables['percent'] . '%"></div></div>'; 2236 $output .= '<div class="percentage">' . $variables['percent'] . '%</div>'; 2237 $output .= '<div class="message">' . $variables['message'] . '</div>'; 2238 $output .= '</div>'; 2239 2240 return $output; 2241 } 2242 2243 /** 2244 * Returns HTML for an indentation div; used for drag and drop tables. 2245 * 2246 * @param $variables 2247 * An associative array containing: 2248 * - size: Optional. The number of indentations to create. 2249 */ 2250 function theme_indentation($variables) { 2251 $output = ''; 2252 for ($n = 0; $n < $variables['size']; $n++) { 2253 $output .= '<div class="indentation"> </div>'; 2254 } 2255 return $output; 2256 } 2257 2258 /** 2259 * @} End of "addtogroup themeable". 2260 */ 2261 2262 /** 2263 * Returns HTML output for a single table cell for theme_table(). 2264 * 2265 * @param $cell 2266 * Array of cell information, or string to display in cell. 2267 * @param bool $header 2268 * TRUE if this cell is a table header cell, FALSE if it is an ordinary 2269 * table cell. If $cell is an array with element 'header' set to TRUE, that 2270 * will override the $header parameter. 2271 * 2272 * @return 2273 * HTML for the cell. 2274 */ 2275 function _theme_table_cell($cell, $header = FALSE) { 2276 $attributes = ''; 2277 2278 if (is_array($cell)) { 2279 $data = isset($cell['data']) ? $cell['data'] : ''; 2280 // Cell's data property can be a string or a renderable array. 2281 if (is_array($data)) { 2282 $data = drupal_render($data); 2283 } 2284 $header |= isset($cell['header']); 2285 unset($cell['data']); 2286 unset($cell['header']); 2287 $attributes = drupal_attributes($cell); 2288 } 2289 else { 2290 $data = $cell; 2291 } 2292 2293 if ($header) { 2294 $output = "<th$attributes>$data</th>"; 2295 } 2296 else { 2297 $output = "<td$attributes>$data</td>"; 2298 } 2299 2300 return $output; 2301 } 2302 2303 /** 2304 * Adds a default set of helper variables for variable processors and templates. 2305 * 2306 * This function is called for theme hooks implemented as templates only, not 2307 * for theme hooks implemented as functions. This preprocess function is the 2308 * first in the sequence of preprocessing and processing functions that is 2309 * called when preparing variables for a template. See theme() for more details 2310 * about the full sequence. 2311 * 2312 * @see theme() 2313 * @see template_process() 2314 */ 2315 function template_preprocess(&$variables, $hook) { 2316 global $user; 2317 static $count = array(); 2318 2319 // Track run count for each hook to provide zebra striping. 2320 // See "template_preprocess_block()" which provides the same feature specific to blocks. 2321 $count[$hook] = isset($count[$hook]) && is_int($count[$hook]) ? $count[$hook] : 1; 2322 $variables['zebra'] = ($count[$hook] % 2) ? 'odd' : 'even'; 2323 $variables['id'] = $count[$hook]++; 2324 2325 // Tell all templates where they are located. 2326 $variables['directory'] = path_to_theme(); 2327 2328 // Initialize html class attribute for the current hook. 2329 $variables['classes_array'] = array(drupal_html_class($hook)); 2330 2331 // Merge in variables that don't depend on hook and don't change during a 2332 // single page request. 2333 // Use the advanced drupal_static() pattern, since this is called very often. 2334 static $drupal_static_fast; 2335 if (!isset($drupal_static_fast)) { 2336 $drupal_static_fast['default_variables'] = &drupal_static(__FUNCTION__); 2337 } 2338 $default_variables = &$drupal_static_fast['default_variables']; 2339 // Global $user object shouldn't change during a page request once rendering 2340 // has started, but if there's an edge case where it does, re-fetch the 2341 // variables appropriate for the new user. 2342 if (!isset($default_variables) || ($user !== $default_variables['user'])) { 2343 $default_variables = _template_preprocess_default_variables(); 2344 } 2345 $variables += $default_variables; 2346 } 2347 2348 /** 2349 * Returns hook-independent variables to template_preprocess(). 2350 */ 2351 function _template_preprocess_default_variables() { 2352 global $user; 2353 2354 // Variables that don't depend on a database connection. 2355 $variables = array( 2356 'attributes_array' => array(), 2357 'title_attributes_array' => array(), 2358 'content_attributes_array' => array(), 2359 'title_prefix' => array(), 2360 'title_suffix' => array(), 2361 'user' => $user, 2362 'db_is_active' => !defined('MAINTENANCE_MODE'), 2363 'is_admin' => FALSE, 2364 'logged_in' => FALSE, 2365 ); 2366 2367 // The user object has no uid property when the database does not exist during 2368 // install. The user_access() check deals with issues when in maintenance mode 2369 // as uid is set but the user.module has not been included. 2370 if (isset($user->uid) && function_exists('user_access')) { 2371 $variables['is_admin'] = user_access('access administration pages'); 2372 $variables['logged_in'] = ($user->uid > 0); 2373 } 2374 2375 // drupal_is_front_page() might throw an exception. 2376 try { 2377 $variables['is_front'] = drupal_is_front_page(); 2378 } 2379 catch (Exception $e) { 2380 // If the database is not yet available, set default values for these 2381 // variables. 2382 $variables['is_front'] = FALSE; 2383 $variables['db_is_active'] = FALSE; 2384 } 2385 2386 return $variables; 2387 } 2388 2389 /** 2390 * Adds helper variables derived from variables defined during preprocessing. 2391 * 2392 * When preparing variables for a theme hook implementation, all 'preprocess' 2393 * functions run first, then all 'process' functions (see theme() for details 2394 * about the full sequence). 2395 * 2396 * This function serializes array variables manipulated during the preprocessing 2397 * phase into strings for convenient use by templates. As with 2398 * template_preprocess(), this function does not get called for theme hooks 2399 * implemented as functions. 2400 * 2401 * @see theme() 2402 * @see template_preprocess() 2403 */ 2404 function template_process(&$variables, $hook) { 2405 // Flatten out classes. 2406 $variables['classes'] = implode(' ', $variables['classes_array']); 2407 2408 // Flatten out attributes, title_attributes, and content_attributes. 2409 // Because this function can be called very often, and often with empty 2410 // attributes, optimize performance by only calling drupal_attributes() if 2411 // necessary. 2412 $variables['attributes'] = $variables['attributes_array'] ? drupal_attributes($variables['attributes_array']) : ''; 2413 $variables['title_attributes'] = $variables['title_attributes_array'] ? drupal_attributes($variables['title_attributes_array']) : ''; 2414 $variables['content_attributes'] = $variables['content_attributes_array'] ? drupal_attributes($variables['content_attributes_array']) : ''; 2415 } 2416 2417 /** 2418 * Preprocess variables for html.tpl.php 2419 * 2420 * @see system_elements() 2421 * @see html.tpl.php 2422 */ 2423 function template_preprocess_html(&$variables) { 2424 // Compile a list of classes that are going to be applied to the body element. 2425 // This allows advanced theming based on context (home page, node of certain type, etc.). 2426 // Add a class that tells us whether we're on the front page or not. 2427 $variables['classes_array'][] = $variables['is_front'] ? 'front' : 'not-front'; 2428 // Add a class that tells us whether the page is viewed by an authenticated user or not. 2429 $variables['classes_array'][] = $variables['logged_in'] ? 'logged-in' : 'not-logged-in'; 2430 2431 // Add information about the number of sidebars. 2432 if (!empty($variables['page']['sidebar_first']) && !empty($variables['page']['sidebar_second'])) { 2433 $variables['classes_array'][] = 'two-sidebars'; 2434 } 2435 elseif (!empty($variables['page']['sidebar_first'])) { 2436 $variables['classes_array'][] = 'one-sidebar sidebar-first'; 2437 } 2438 elseif (!empty($variables['page']['sidebar_second'])) { 2439 $variables['classes_array'][] = 'one-sidebar sidebar-second'; 2440 } 2441 else { 2442 $variables['classes_array'][] = 'no-sidebars'; 2443 } 2444 2445 // Populate the body classes. 2446 if ($suggestions = theme_get_suggestions(arg(), 'page', '-')) { 2447 foreach ($suggestions as $suggestion) { 2448 if ($suggestion != 'page-front') { 2449 // Add current suggestion to page classes to make it possible to theme 2450 // the page depending on the current page type (e.g. node, admin, user, 2451 // etc.) as well as more specific data like node-12 or node-edit. 2452 $variables['classes_array'][] = drupal_html_class($suggestion); 2453 } 2454 } 2455 } 2456 2457 // If on an individual node page, add the node type to body classes. 2458 if ($node = menu_get_object()) { 2459 $variables['classes_array'][] = drupal_html_class('node-type-' . $node->type); 2460 } 2461 2462 // RDFa allows annotation of XHTML pages with RDF data, while GRDDL provides 2463 // mechanisms for extraction of this RDF content via XSLT transformation 2464 // using an associated GRDDL profile. 2465 $variables['rdf_namespaces'] = drupal_get_rdf_namespaces(); 2466 $variables['grddl_profile'] = 'http://www.w3.org/1999/xhtml/vocab'; 2467 $variables['language'] = $GLOBALS['language']; 2468 $variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr'; 2469 2470 // Add favicon. 2471 if (theme_get_setting('toggle_favicon')) { 2472 $favicon = theme_get_setting('favicon'); 2473 $type = theme_get_setting('favicon_mimetype'); 2474 drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => drupal_strip_dangerous_protocols($favicon), 'type' => $type)); 2475 } 2476 2477 // Construct page title. 2478 if (drupal_get_title()) { 2479 $head_title = array( 2480 'title' => strip_tags(drupal_get_title()), 2481 'name' => check_plain(variable_get('site_name', 'Drupal')), 2482 ); 2483 } 2484 else { 2485 $head_title = array('name' => check_plain(variable_get('site_name', 'Drupal'))); 2486 if (variable_get('site_slogan', '')) { 2487 $head_title['slogan'] = filter_xss_admin(variable_get('site_slogan', '')); 2488 } 2489 } 2490 $variables['head_title_array'] = $head_title; 2491 $variables['head_title'] = implode(' | ', $head_title); 2492 2493 // Populate the page template suggestions. 2494 if ($suggestions = theme_get_suggestions(arg(), 'html')) { 2495 $variables['theme_hook_suggestions'] = $suggestions; 2496 } 2497 } 2498 2499 /** 2500 * Preprocess variables for page.tpl.php 2501 * 2502 * Most themes utilize their own copy of page.tpl.php. The default is located 2503 * inside "modules/system/page.tpl.php". Look in there for the full list of 2504 * variables. 2505 * 2506 * Uses the arg() function to generate a series of page template suggestions 2507 * based on the current path. 2508 * 2509 * Any changes to variables in this preprocessor should also be changed inside 2510 * template_preprocess_maintenance_page() to keep all of them consistent. 2511 * 2512 * @see drupal_render_page() 2513 * @see template_process_page() 2514 * @see page.tpl.php 2515 */ 2516 function template_preprocess_page(&$variables) { 2517 // Move some variables to the top level for themer convenience and template cleanliness. 2518 $variables['show_messages'] = $variables['page']['#show_messages']; 2519 2520 foreach (system_region_list($GLOBALS['theme']) as $region_key => $region_name) { 2521 if (!isset($variables['page'][$region_key])) { 2522 $variables['page'][$region_key] = array(); 2523 } 2524 } 2525 2526 // Set up layout variable. 2527 $variables['layout'] = 'none'; 2528 if (!empty($variables['page']['sidebar_first'])) { 2529 $variables['layout'] = 'first'; 2530 } 2531 if (!empty($variables['page']['sidebar_second'])) { 2532 $variables['layout'] = ($variables['layout'] == 'first') ? 'both' : 'second'; 2533 } 2534 2535 $variables['base_path'] = base_path(); 2536 $variables['front_page'] = url(); 2537 $variables['feed_icons'] = drupal_get_feeds(); 2538 $variables['language'] = $GLOBALS['language']; 2539 $variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr'; 2540 $variables['logo'] = theme_get_setting('logo'); 2541 $variables['main_menu'] = theme_get_setting('toggle_main_menu') ? menu_main_menu() : array(); 2542 $variables['secondary_menu'] = theme_get_setting('toggle_secondary_menu') ? menu_secondary_menu() : array(); 2543 $variables['action_links'] = menu_local_actions(); 2544 $variables['site_name'] = (theme_get_setting('toggle_name') ? filter_xss_admin(variable_get('site_name', 'Drupal')) : ''); 2545 $variables['site_slogan'] = (theme_get_setting('toggle_slogan') ? filter_xss_admin(variable_get('site_slogan', '')) : ''); 2546 $variables['tabs'] = menu_local_tabs(); 2547 2548 if ($node = menu_get_object()) { 2549 $variables['node'] = $node; 2550 } 2551 2552 // Populate the page template suggestions. 2553 if ($suggestions = theme_get_suggestions(arg(), 'page')) { 2554 $variables['theme_hook_suggestions'] = $suggestions; 2555 } 2556 } 2557 2558 /** 2559 * Process variables for page.tpl.php 2560 * 2561 * Perform final addition of variables before passing them into the template. 2562 * To customize these variables, simply set them in an earlier step. 2563 * 2564 * @see template_preprocess_page() 2565 * @see page.tpl.php 2566 */ 2567 function template_process_page(&$variables) { 2568 if (!isset($variables['breadcrumb'])) { 2569 // Build the breadcrumb last, so as to increase the chance of being able to 2570 // re-use the cache of an already rendered menu containing the active link 2571 // for the current page. 2572 // @see menu_tree_page_data() 2573 $variables['breadcrumb'] = theme('breadcrumb', array('breadcrumb' => drupal_get_breadcrumb())); 2574 } 2575 if (!isset($variables['title'])) { 2576 $variables['title'] = drupal_get_title(); 2577 } 2578 2579 // Generate messages last in order to capture as many as possible for the 2580 // current page. 2581 if (!isset($variables['messages'])) { 2582 $variables['messages'] = $variables['show_messages'] ? theme('status_messages') : ''; 2583 } 2584 } 2585 2586 /** 2587 * Process variables for html.tpl.php 2588 * 2589 * Perform final addition and modification of variables before passing into 2590 * the template. To customize these variables, call drupal_render() on elements 2591 * in $variables['page'] during THEME_preprocess_page(). 2592 * 2593 * @see template_preprocess_html() 2594 * @see html.tpl.php 2595 */ 2596 function template_process_html(&$variables) { 2597 // Render page_top and page_bottom into top level variables. 2598 $variables['page_top'] = drupal_render($variables['page']['page_top']); 2599 $variables['page_bottom'] = drupal_render($variables['page']['page_bottom']); 2600 // Place the rendered HTML for the page body into a top level variable. 2601 $variables['page'] = $variables['page']['#children']; 2602 $variables['page_bottom'] .= drupal_get_js('footer'); 2603 2604 $variables['head'] = drupal_get_html_head(); 2605 $variables['css'] = drupal_add_css(); 2606 $variables['styles'] = drupal_get_css(); 2607 $variables['scripts'] = drupal_get_js(); 2608 } 2609 2610 /** 2611 * Generate an array of suggestions from path arguments. 2612 * 2613 * This is typically called for adding to the 'theme_hook_suggestions' or 2614 * 'classes_array' variables from within preprocess functions, when wanting to 2615 * base the additional suggestions on the path of the current page. 2616 * 2617 * @param $args 2618 * An array of path arguments, such as from function arg(). 2619 * @param $base 2620 * A string identifying the base 'thing' from which more specific suggestions 2621 * are derived. For example, 'page' or 'html'. 2622 * @param $delimiter 2623 * The string used to delimit increasingly specific information. The default 2624 * of '__' is appropriate for theme hook suggestions. '-' is appropriate for 2625 * extra classes. 2626 * 2627 * @return 2628 * An array of suggestions, suitable for adding to 2629 * $variables['theme_hook_suggestions'] within a preprocess function or to 2630 * $variables['classes_array'] if the suggestions represent extra CSS classes. 2631 */ 2632 function theme_get_suggestions($args, $base, $delimiter = '__') { 2633 2634 // Build a list of suggested theme hooks or body classes in order of 2635 // specificity. One suggestion is made for every element of the current path, 2636 // though numeric elements are not carried to subsequent suggestions. For 2637 // example, for $base='page', http://www.example.com/node/1/edit would result 2638 // in the following suggestions and body classes: 2639 // 2640 // page__node page-node 2641 // page__node__% page-node-% 2642 // page__node__1 page-node-1 2643 // page__node__edit page-node-edit 2644 2645 $suggestions = array(); 2646 $prefix = $base; 2647 foreach ($args as $arg) { 2648 // Remove slashes or null per SA-CORE-2009-003 and change - (hyphen) to _ 2649 // (underscore). 2650 // 2651 // When we discover templates in @see drupal_find_theme_templates, 2652 // hyphens (-) are converted to underscores (_) before the theme hook 2653 // is registered. We do this because the hyphens used for delimiters 2654 // in hook suggestions cannot be used in the function names of the 2655 // associated preprocess functions. Any page templates designed to be used 2656 // on paths that contain a hyphen are also registered with these hyphens 2657 // converted to underscores so here we must convert any hyphens in path 2658 // arguments to underscores here before fetching theme hook suggestions 2659 // to ensure the templates are appropriately recognized. 2660 $arg = str_replace(array("/", "\\", "\0", '-'), array('', '', '', '_'), $arg); 2661 // The percent acts as a wildcard for numeric arguments since 2662 // asterisks are not valid filename characters on many filesystems. 2663 if (is_numeric($arg)) { 2664 $suggestions[] = $prefix . $delimiter . '%'; 2665 } 2666 $suggestions[] = $prefix . $delimiter . $arg; 2667 if (!is_numeric($arg)) { 2668 $prefix .= $delimiter . $arg; 2669 } 2670 } 2671 if (drupal_is_front_page()) { 2672 // Front templates should be based on root only, not prefixed arguments. 2673 $suggestions[] = $base . $delimiter . 'front'; 2674 } 2675 2676 return $suggestions; 2677 } 2678 2679 /** 2680 * The variables array generated here is a mirror of template_preprocess_page(). 2681 * This preprocessor will run its course when theme_maintenance_page() is 2682 * invoked. 2683 * 2684 * An alternate template file of "maintenance-page--offline.tpl.php" can be 2685 * used when the database is offline to hide errors and completely replace the 2686 * content. 2687 * 2688 * The $variables array contains the following arguments: 2689 * - $content 2690 * 2691 * @see maintenance-page.tpl.php 2692 */ 2693 function template_preprocess_maintenance_page(&$variables) { 2694 // Add favicon 2695 if (theme_get_setting('toggle_favicon')) { 2696 $favicon = theme_get_setting('favicon'); 2697 $type = theme_get_setting('favicon_mimetype'); 2698 drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => drupal_strip_dangerous_protocols($favicon), 'type' => $type)); 2699 } 2700 2701 global $theme; 2702 // Retrieve the theme data to list all available regions. 2703 $theme_data = list_themes(); 2704 $regions = $theme_data[$theme]->info['regions']; 2705 2706 // Get all region content set with drupal_add_region_content(). 2707 foreach (array_keys($regions) as $region) { 2708 // Assign region to a region variable. 2709 $region_content = drupal_get_region_content($region); 2710 isset($variables[$region]) ? $variables[$region] .= $region_content : $variables[$region] = $region_content; 2711 } 2712 2713 // Setup layout variable. 2714 $variables['layout'] = 'none'; 2715 if (!empty($variables['sidebar_first'])) { 2716 $variables['layout'] = 'first'; 2717 } 2718 if (!empty($variables['sidebar_second'])) { 2719 $variables['layout'] = ($variables['layout'] == 'first') ? 'both' : 'second'; 2720 } 2721 2722 // Construct page title 2723 if (drupal_get_title()) { 2724 $head_title = array( 2725 'title' => strip_tags(drupal_get_title()), 2726 'name' => variable_get('site_name', 'Drupal'), 2727 ); 2728 } 2729 else { 2730 $head_title = array('name' => variable_get('site_name', 'Drupal')); 2731 if (variable_get('site_slogan', '')) { 2732 $head_title['slogan'] = variable_get('site_slogan', ''); 2733 } 2734 } 2735 2736 // set the default language if necessary 2737 $language = isset($GLOBALS['language']) ? $GLOBALS['language'] : language_default(); 2738 2739 $variables['head_title_array'] = $head_title; 2740 $variables['head_title'] = implode(' | ', $head_title); 2741 $variables['base_path'] = base_path(); 2742 $variables['front_page'] = url(); 2743 $variables['breadcrumb'] = ''; 2744 $variables['feed_icons'] = ''; 2745 $variables['help'] = ''; 2746 $variables['language'] = $language; 2747 $variables['language']->dir = $language->direction ? 'rtl' : 'ltr'; 2748 $variables['logo'] = theme_get_setting('logo'); 2749 $variables['messages'] = $variables['show_messages'] ? theme('status_messages') : ''; 2750 $variables['main_menu'] = array(); 2751 $variables['secondary_menu'] = array(); 2752 $variables['site_name'] = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : ''); 2753 $variables['site_slogan'] = (theme_get_setting('toggle_slogan') ? variable_get('site_slogan', '') : ''); 2754 $variables['tabs'] = ''; 2755 $variables['title'] = drupal_get_title(); 2756 2757 // Compile a list of classes that are going to be applied to the body element. 2758 $variables['classes_array'][] = 'in-maintenance'; 2759 if (isset($variables['db_is_active']) && !$variables['db_is_active']) { 2760 $variables['classes_array'][] = 'db-offline'; 2761 } 2762 if ($variables['layout'] == 'both') { 2763 $variables['classes_array'][] = 'two-sidebars'; 2764 } 2765 elseif ($variables['layout'] == 'none') { 2766 $variables['classes_array'][] = 'no-sidebars'; 2767 } 2768 else { 2769 $variables['classes_array'][] = 'one-sidebar sidebar-' . $variables['layout']; 2770 } 2771 2772 // Dead databases will show error messages so supplying this template will 2773 // allow themers to override the page and the content completely. 2774 if (isset($variables['db_is_active']) && !$variables['db_is_active']) { 2775 $variables['theme_hook_suggestion'] = 'maintenance_page__offline'; 2776 } 2777 } 2778 2779 /** 2780 * The variables array generated here is a mirror of template_process_html(). 2781 * This processor will run its course when theme_maintenance_page() is invoked. 2782 * 2783 * @see maintenance-page.tpl.php 2784 */ 2785 function template_process_maintenance_page(&$variables) { 2786 $variables['head'] = drupal_get_html_head(); 2787 $variables['css'] = drupal_add_css(); 2788 $variables['styles'] = drupal_get_css(); 2789 $variables['scripts'] = drupal_get_js(); 2790 } 2791 2792 /** 2793 * Preprocess variables for region.tpl.php 2794 * 2795 * Prepare the values passed to the theme_region function to be passed into a 2796 * pluggable template engine. Uses the region name to generate a template file 2797 * suggestions. If none are found, the default region.tpl.php is used. 2798 * 2799 * @see drupal_region_class() 2800 * @see region.tpl.php 2801 */ 2802 function template_preprocess_region(&$variables) { 2803 // Create the $content variable that templates expect. 2804 $variables['content'] = $variables['elements']['#children']; 2805 $variables['region'] = $variables['elements']['#region']; 2806 2807 $variables['classes_array'][] = drupal_region_class($variables['region']); 2808 $variables['theme_hook_suggestions'][] = 'region__' . $variables['region']; 2809 } 2810 2811 /** 2812 * Preprocesses variables for theme_username(). 2813 * 2814 * Modules that make any changes to variables like 'name' or 'extra' must insure 2815 * that the final string is safe to include directly in the output by using 2816 * check_plain() or filter_xss(). 2817 * 2818 * @see template_process_username() 2819 */ 2820 function template_preprocess_username(&$variables) { 2821 $account = $variables['account']; 2822 2823 $variables['extra'] = ''; 2824 if (empty($account->uid)) { 2825 $variables['uid'] = 0; 2826 if (theme_get_setting('toggle_comment_user_verification')) { 2827 $variables['extra'] = ' (' . t('not verified') . ')'; 2828 } 2829 } 2830 else { 2831 $variables['uid'] = (int) $account->uid; 2832 } 2833 2834 // Set the name to a formatted name that is safe for printing and 2835 // that won't break tables by being too long. Keep an unshortened, 2836 // unsanitized version, in case other preprocess functions want to implement 2837 // their own shortening logic or add markup. If they do so, they must ensure 2838 // that $variables['name'] is safe for printing. 2839 $name = $variables['name_raw'] = format_username($account); 2840 if (drupal_strlen($name) > 20) { 2841 $name = drupal_substr($name, 0, 15) . '...'; 2842 } 2843 $variables['name'] = check_plain($name); 2844 2845 $variables['profile_access'] = user_access('access user profiles'); 2846 $variables['link_attributes'] = array(); 2847 // Populate link path and attributes if appropriate. 2848 if ($variables['uid'] && $variables['profile_access']) { 2849 // We are linking to a local user. 2850 $variables['link_attributes'] = array('title' => t('View user profile.')); 2851 $variables['link_path'] = 'user/' . $variables['uid']; 2852 } 2853 elseif (!empty($account->homepage)) { 2854 // Like the 'class' attribute, the 'rel' attribute can hold a 2855 // space-separated set of values, so initialize it as an array to make it 2856 // easier for other preprocess functions to append to it. 2857 $variables['link_attributes'] = array('rel' => array('nofollow')); 2858 $variables['link_path'] = $account->homepage; 2859 $variables['homepage'] = $account->homepage; 2860 } 2861 // We do not want the l() function to check_plain() a second time. 2862 $variables['link_options']['html'] = TRUE; 2863 // Set a default class. 2864 $variables['attributes_array'] = array('class' => array('username')); 2865 } 2866 2867 /** 2868 * Processes variables for theme_username(). 2869 * 2870 * @see template_preprocess_username() 2871 */ 2872 function template_process_username(&$variables) { 2873 // Finalize the link_options array for passing to the l() function. 2874 // This is done in the process phase so that attributes may be added by 2875 // modules or the theme during the preprocess phase. 2876 if (isset($variables['link_path'])) { 2877 // $variables['attributes_array'] contains attributes that should be applied 2878 // regardless of whether a link is being rendered or not. 2879 // $variables['link_attributes'] contains attributes that should only be 2880 // applied if a link is being rendered. Preprocess functions are encouraged 2881 // to use the former unless they want to add attributes on the link only. 2882 // If a link is being rendered, these need to be merged. Some attributes are 2883 // themselves arrays, so the merging needs to be recursive. 2884 $variables['link_options']['attributes'] = array_merge_recursive($variables['link_attributes'], $variables['attributes_array']); 2885 } 2886 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title