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