Spike PHPCoverage Details: theme.inc

Line #FrequencySource Line
1 <?php
2 // $Id: theme.inc,v 1.419 2008/04/02 20:23:14 dries 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() {
321  global $theme, $user, $custom_theme, $theme_key;
33 
34   // If $theme is already set, assume the others are set, too, and do nothing
351  if (isset($theme)) {
36     return;
37   }
38 
391  drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
401  $themes = list_themes();
41 
42   // Only select the user selected theme if it is available in the
43   // list of enabled themes.
441  $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.
481  $theme = $custom_theme && $themes[$custom_theme] ? $custom_theme : $theme;
49 
50   // Store the identifier for retrieving theme settings with.
511  $theme_key = $theme;
52 
53   // Find all our ancestor themes and put them in an array.
54   $base_theme = array();
551  $ancestor = $theme;
561  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   }
601  _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') {
891  global $theme_info, $base_theme_info, $theme_engine, $theme_path;
901  $theme_info = $theme;
911  $base_theme_info = $base_theme;
92 
931  $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
1011  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.
1121  if (!empty($theme->stylesheets)) {
1131    foreach ($theme->stylesheets as $media => $stylesheets) {
1141      foreach ($stylesheets as $name => $stylesheet) {
1151        $final_stylesheets[$media][$name] = $stylesheet;
116       }
117     }
118   }
119 
120   // And now add the stylesheets properly
1211  foreach ($final_stylesheets as $media => $stylesheets) {
1221    foreach ($stylesheets as $stylesheet) {
1231      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
1311  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.
1401  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.
1471  foreach ($final_scripts as $script) {
148     drupal_add_js($script, 'theme');
149   }
150 
1511  $theme_engine = NULL;
152 
153   // Initialize the theme.
1541  if (isset($theme->engine)) {
155     // Include the engine.
156     include_once './'. $theme->owner;
157 
1581    $theme_engine = $theme->engine;
1591    if (function_exists($theme_engine .'_init')) {
1601      foreach ($base_theme as $base) {
161         call_user_func($theme_engine .'_init', $base);
162       }
1631      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 
1801  $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) {
1901  static $theme_registry = NULL;
1911  if (isset($registry)) {
1921    $theme_registry = $registry;
193   }
194 
1951  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.
2031  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.
2201  $cache = cache_get("theme_registry:$theme->name", 'cache');
2211  if (isset($cache->data)) {
2221    $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   }
2291  _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 
4111  if ($refresh) {
412     $list = array();
413   }
414 
4151  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.
4201    if (db_is_active() && !defined('MAINTENANCE_MODE')) {
4211      $result = db_query("SELECT * FROM {system} WHERE type = '%s'", 'theme');
4221      while ($theme = db_fetch_object($result)) {
4231        if (file_exists($theme->filename)) {
4241          $theme->info = unserialize($theme->info);
4251          $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 
4341    foreach ($themes as $theme) {
4351      foreach ($theme->info['stylesheets'] as $media => $stylesheets) {
4361        foreach ($stylesheets as $stylesheet => $path) {
4371          if (file_exists($path)) {
4381            $theme->stylesheets[$media][$stylesheet] = $path;
439           }
440         }
441       }
4421      foreach ($theme->info['scripts'] as $script => $path) {
4431        if (file_exists($path)) {
444           $theme->scripts[$script] = $path;
445         }
446       }
4471      if (isset($theme->info['engine'])) {
4481        $theme->engine = $theme->info['engine'];
449       }
4501      if (isset($theme->info['base theme'])) {
4511        $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.
4551      if (!isset($theme->status)) {
456         $theme->status = 0;
457       }
4581      $list[$theme->name] = $theme;
459     }
460   }
461 
4621  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