comparison includes/theme.inc @ 1:c1f4ac30525a 6.0

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