webmaster@1: theme) && !empty($themes[$user->theme]->status) ? $user->theme : variable_get('theme_default', 'garland'); webmaster@1: webmaster@1: // Allow modules to override the present theme... only select custom theme webmaster@1: // if it is available in the list of installed themes. webmaster@1: $theme = $custom_theme && $themes[$custom_theme] ? $custom_theme : $theme; webmaster@1: webmaster@1: // Store the identifier for retrieving theme settings with. webmaster@1: $theme_key = $theme; webmaster@1: webmaster@1: // Find all our ancestor themes and put them in an array. webmaster@1: $base_theme = array(); webmaster@1: $ancestor = $theme; webmaster@1: while ($ancestor && isset($themes[$ancestor]->base_theme)) { webmaster@1: $base_theme[] = $new_base_theme = $themes[$themes[$ancestor]->base_theme]; webmaster@1: $ancestor = $themes[$ancestor]->base_theme; webmaster@1: } webmaster@1: _init_theme($themes[$theme], array_reverse($base_theme)); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Initialize the theme system given already loaded information. This webmaster@1: * function is useful to initialize a theme when no database is present. webmaster@1: * webmaster@1: * @param $theme webmaster@1: * An object with the following information: webmaster@1: * filename webmaster@1: * The .info file for this theme. The 'path' to webmaster@1: * the theme will be in this file's directory. (Required) webmaster@1: * owner webmaster@1: * The path to the .theme file or the .engine file to load for webmaster@1: * the theme. (Required) webmaster@1: * stylesheet webmaster@1: * The primary stylesheet for the theme. (Optional) webmaster@1: * engine webmaster@1: * The name of theme engine to use. (Optional) webmaster@1: * @param $base_theme webmaster@1: * An optional array of objects that represent the 'base theme' if the webmaster@1: * theme is meant to be derivative of another theme. It requires webmaster@1: * the same information as the $theme object. It should be in webmaster@1: * 'oldest first' order, meaning the top level of the chain will webmaster@1: * be first. webmaster@1: * @param $registry_callback webmaster@1: * The callback to invoke to set the theme registry. webmaster@1: */ webmaster@1: function _init_theme($theme, $base_theme = array(), $registry_callback = '_theme_load_registry') { webmaster@1: global $theme_info, $base_theme_info, $theme_engine, $theme_path; webmaster@1: $theme_info = $theme; webmaster@1: $base_theme_info = $base_theme; webmaster@1: webmaster@1: $theme_path = dirname($theme->filename); webmaster@1: webmaster@1: // Prepare stylesheets from this theme as well as all ancestor themes. webmaster@1: // We work it this way so that we can have child themes override parent webmaster@1: // theme stylesheets easily. webmaster@1: $final_stylesheets = array(); webmaster@1: webmaster@1: // Grab stylesheets from base theme webmaster@1: foreach ($base_theme as $base) { webmaster@1: if (!empty($base->stylesheets)) { webmaster@1: foreach ($base->stylesheets as $media => $stylesheets) { webmaster@1: foreach ($stylesheets as $name => $stylesheet) { webmaster@1: $final_stylesheets[$media][$name] = $stylesheet; webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: // Add stylesheets used by this theme. webmaster@1: if (!empty($theme->stylesheets)) { webmaster@1: foreach ($theme->stylesheets as $media => $stylesheets) { webmaster@1: foreach ($stylesheets as $name => $stylesheet) { webmaster@1: $final_stylesheets[$media][$name] = $stylesheet; webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: // And now add the stylesheets properly webmaster@1: foreach ($final_stylesheets as $media => $stylesheets) { webmaster@1: foreach ($stylesheets as $stylesheet) { webmaster@1: drupal_add_css($stylesheet, 'theme', $media); webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: // Do basically the same as the above for scripts webmaster@1: $final_scripts = array(); webmaster@1: webmaster@1: // Grab scripts from base theme webmaster@1: foreach ($base_theme as $base) { webmaster@1: if (!empty($base->scripts)) { webmaster@1: foreach ($base->scripts as $name => $script) { webmaster@1: $final_scripts[$name] = $script; webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: // Add scripts used by this theme. webmaster@1: if (!empty($theme->scripts)) { webmaster@1: foreach ($theme->scripts as $name => $script) { webmaster@1: $final_scripts[$name] = $script; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: // Add scripts used by this theme. webmaster@1: foreach ($final_scripts as $script) { webmaster@1: drupal_add_js($script, 'theme'); webmaster@1: } webmaster@1: webmaster@1: $theme_engine = NULL; webmaster@1: webmaster@1: // Initialize the theme. webmaster@1: if (isset($theme->engine)) { webmaster@1: // Include the engine. webmaster@1: include_once './'. $theme->owner; webmaster@1: webmaster@1: $theme_engine = $theme->engine; webmaster@1: if (function_exists($theme_engine .'_init')) { webmaster@1: foreach ($base_theme as $base) { webmaster@1: call_user_func($theme_engine .'_init', $base); webmaster@1: } webmaster@1: call_user_func($theme_engine .'_init', $theme); webmaster@1: } webmaster@1: } webmaster@1: else { webmaster@1: // include non-engine theme files webmaster@1: foreach ($base_theme as $base) { webmaster@1: // Include the theme file or the engine. webmaster@1: if (!empty($base->owner)) { webmaster@1: include_once './'. $base->owner; webmaster@1: } webmaster@1: } webmaster@1: // and our theme gets one too. webmaster@1: if (!empty($theme->owner)) { webmaster@1: include_once './'. $theme->owner; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: $registry_callback($theme, $base_theme, $theme_engine); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Retrieve the stored theme registry. If the theme registry is already webmaster@1: * in memory it will be returned; otherwise it will attempt to load the webmaster@1: * registry from cache. If this fails, it will construct the registry and webmaster@1: * cache it. webmaster@1: */ webmaster@1: function theme_get_registry($registry = NULL) { webmaster@1: static $theme_registry = NULL; webmaster@1: if (isset($registry)) { webmaster@1: $theme_registry = $registry; webmaster@1: } webmaster@1: webmaster@1: return $theme_registry; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Store the theme registry in memory. webmaster@1: */ webmaster@1: function _theme_set_registry($registry) { webmaster@1: // Pass through for setting of static variable. webmaster@1: return theme_get_registry($registry); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Get the theme_registry cache from the database; if it doesn't exist, build webmaster@1: * it. webmaster@1: * webmaster@1: * @param $theme webmaster@1: * The loaded $theme object. webmaster@1: * @param $base_theme webmaster@1: * An array of loaded $theme objects representing the ancestor themes in webmaster@1: * oldest first order. webmaster@1: * @param theme_engine webmaster@1: * The name of the theme engine. webmaster@1: */ webmaster@1: function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL) { webmaster@1: // Check the theme registry cache; if it exists, use it. webmaster@1: $cache = cache_get("theme_registry:$theme->name", 'cache'); webmaster@1: if (isset($cache->data)) { webmaster@1: $registry = $cache->data; webmaster@1: } webmaster@1: else { webmaster@1: // If not, build one and cache it. webmaster@1: $registry = _theme_build_registry($theme, $base_theme, $theme_engine); webmaster@1: _theme_save_registry($theme, $registry); webmaster@1: } webmaster@1: _theme_set_registry($registry); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Write the theme_registry cache into the database. webmaster@1: */ webmaster@1: function _theme_save_registry($theme, $registry) { webmaster@1: cache_set("theme_registry:$theme->name", $registry); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Force the system to rebuild the theme registry; this should be called webmaster@1: * when modules are added to the system, or when a dynamic system needs webmaster@1: * to add more theme hooks. webmaster@1: */ webmaster@1: function drupal_rebuild_theme_registry() { webmaster@1: cache_clear_all('theme_registry', 'cache', TRUE); webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Process a single invocation of the theme hook. $type will be one webmaster@11: * of 'module', 'theme_engine', 'base_theme_engine', 'theme', or 'base_theme' webmaster@11: * and it tells us some important information. webmaster@1: * webmaster@1: * Because $cache is a reference, the cache will be continually webmaster@1: * expanded upon; new entries will replace old entries in the webmaster@1: * array_merge, but we are careful to ensure some data is carried webmaster@1: * forward, such as the arguments a theme hook needs. webmaster@1: * webmaster@1: * An override flag can be set for preprocess functions. When detected the webmaster@1: * cached preprocessors for the hook will not be merged with the newly set. webmaster@1: * This can be useful to themes and theme engines by giving them more control webmaster@1: * over how and when the preprocess functions are run. webmaster@1: */ webmaster@1: function _theme_process_registry(&$cache, $name, $type, $theme, $path) { webmaster@15: $result = array(); webmaster@1: $function = $name .'_theme'; webmaster@1: if (function_exists($function)) { webmaster@1: $result = $function($cache, $type, $theme, $path); webmaster@1: webmaster@1: foreach ($result as $hook => $info) { webmaster@1: $result[$hook]['type'] = $type; webmaster@1: $result[$hook]['theme path'] = $path; webmaster@1: // if function and file are left out, default to standard naming webmaster@1: // conventions. webmaster@1: if (!isset($info['template']) && !isset($info['function'])) { webmaster@1: $result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name .'_') . $hook; webmaster@1: } webmaster@1: // If a path is set in the info, use what was set. Otherwise use the webmaster@1: // default path. This is mostly so system.module can declare theme webmaster@1: // functions on behalf of core .include files. webmaster@1: // All files are included to be safe. Conditionally included webmaster@1: // files can prevent them from getting registered. webmaster@1: if (isset($info['file']) && !isset($info['path'])) { webmaster@1: $result[$hook]['file'] = $path .'/'. $info['file']; webmaster@1: include_once($result[$hook]['file']); webmaster@1: } webmaster@1: elseif (isset($info['file']) && isset($info['path'])) { webmaster@1: include_once($info['path'] .'/'. $info['file']); webmaster@1: } webmaster@1: webmaster@1: if (isset($info['template']) && !isset($info['path'])) { webmaster@1: $result[$hook]['template'] = $path .'/'. $info['template']; webmaster@1: } webmaster@1: // If 'arguments' have been defined previously, carry them forward. webmaster@1: // This should happen if a theme overrides a Drupal defined theme webmaster@1: // function, for example. webmaster@1: if (!isset($info['arguments']) && isset($cache[$hook])) { webmaster@1: $result[$hook]['arguments'] = $cache[$hook]['arguments']; webmaster@1: } webmaster@1: // Likewise with theme paths. These are used for template naming suggestions. webmaster@1: // Theme implementations can occur in multiple paths. Suggestions should follow. webmaster@1: if (!isset($info['theme paths']) && isset($cache[$hook])) { webmaster@1: $result[$hook]['theme paths'] = $cache[$hook]['theme paths']; webmaster@1: } webmaster@1: // Check for sub-directories. webmaster@1: $result[$hook]['theme paths'][] = isset($info['path']) ? $info['path'] : $path; webmaster@1: webmaster@1: // Check for default _preprocess_ functions. Ensure arrayness. webmaster@1: if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) { webmaster@1: $info['preprocess functions'] = array(); webmaster@1: $prefixes = array(); webmaster@1: if ($type == 'module') { webmaster@1: // Default preprocessor prefix. webmaster@1: $prefixes[] = 'template'; webmaster@1: // Add all modules so they can intervene with their own preprocessors. This allows them webmaster@1: // to provide preprocess functions even if they are not the owner of the current hook. webmaster@1: $prefixes += module_list(); webmaster@1: } webmaster@11: elseif ($type == 'theme_engine' || $type == 'base_theme_engine') { webmaster@1: // Theme engines get an extra set that come before the normally named preprocessors. webmaster@1: $prefixes[] = $name .'_engine'; webmaster@1: // The theme engine also registers on behalf of the theme. The theme or engine name can be used. webmaster@1: $prefixes[] = $name; webmaster@1: $prefixes[] = $theme; webmaster@1: } webmaster@1: else { webmaster@1: // This applies when the theme manually registers their own preprocessors. webmaster@1: $prefixes[] = $name; webmaster@1: } webmaster@1: webmaster@1: foreach ($prefixes as $prefix) { webmaster@1: if (function_exists($prefix .'_preprocess')) { webmaster@1: $info['preprocess functions'][] = $prefix .'_preprocess'; webmaster@1: } webmaster@7: webmaster@1: if (function_exists($prefix .'_preprocess_'. $hook)) { webmaster@1: $info['preprocess functions'][] = $prefix .'_preprocess_'. $hook; webmaster@1: } webmaster@7: webmaster@7: if (!empty($info['original hook']) && function_exists($prefix .'_preprocess_'. $info['original hook'])) { webmaster@7: $info['preprocess functions'][] = $prefix .'_preprocess_'. $info['original hook']; webmaster@7: } webmaster@1: } webmaster@1: } webmaster@1: // Check for the override flag and prevent the cached preprocess functions from being used. webmaster@1: // This allows themes or theme engines to remove preprocessors set earlier in the registry build. webmaster@1: if (!empty($info['override preprocess functions'])) { webmaster@1: // Flag not needed inside the registry. webmaster@1: unset($result[$hook]['override preprocess functions']); webmaster@1: } webmaster@1: elseif (isset($cache[$hook]['preprocess functions']) && is_array($cache[$hook]['preprocess functions'])) { webmaster@1: $info['preprocess functions'] = array_merge($cache[$hook]['preprocess functions'], $info['preprocess functions']); webmaster@1: } webmaster@7: elseif (isset($info['original hook']) && isset($cache[$info['original hook']]['preprocess functions']) && is_array($cache[$info['original hook']]['preprocess functions'])) { webmaster@7: $info['preprocess functions'] = array_merge($cache[$info['original hook']]['preprocess functions'], $info['preprocess functions']); webmaster@7: } webmaster@1: $result[$hook]['preprocess functions'] = $info['preprocess functions']; webmaster@1: } webmaster@1: webmaster@1: // Merge the newly created theme hooks into the existing cache. webmaster@1: $cache = array_merge($cache, $result); webmaster@1: } webmaster@15: webmaster@15: // Let themes have preprocess functions even if they didn't register a template. webmaster@15: if ($type == 'theme' || $type == 'base_theme') { webmaster@15: foreach ($cache as $hook => $info) { webmaster@15: // Check only if it's a template and not registered by the theme or engine. webmaster@15: if (!empty($info['template']) && empty($result[$hook])) { webmaster@15: if (!isset($info['preprocess functions'])) { webmaster@15: $cache[$hook]['preprocess functions'] = array(); webmaster@15: } webmaster@15: if (function_exists($name .'_preprocess')) { webmaster@15: $cache[$hook]['preprocess functions'][] = $name .'_preprocess'; webmaster@15: } webmaster@15: if (function_exists($name .'_preprocess_'. $hook)) { webmaster@15: $cache[$hook]['preprocess functions'][] = $name .'_preprocess_'. $hook; webmaster@15: } webmaster@15: // Ensure uniqueness. webmaster@15: $cache[$hook]['preprocess functions'] = array_unique($cache[$hook]['preprocess functions']); webmaster@15: } webmaster@15: } webmaster@15: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Rebuild the hook theme_registry cache. webmaster@1: * webmaster@1: * @param $theme webmaster@1: * The loaded $theme object. webmaster@1: * @param $base_theme webmaster@1: * An array of loaded $theme objects representing the ancestor themes in webmaster@1: * oldest first order. webmaster@1: * @param theme_engine webmaster@1: * The name of the theme engine. webmaster@1: */ webmaster@1: function _theme_build_registry($theme, $base_theme, $theme_engine) { webmaster@1: $cache = array(); webmaster@1: // First, process the theme hooks advertised by modules. This will webmaster@1: // serve as the basic registry. webmaster@1: foreach (module_implements('theme') as $module) { webmaster@1: _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module)); webmaster@1: } webmaster@1: webmaster@1: // Process each base theme. webmaster@1: foreach ($base_theme as $base) { webmaster@11: // If the base theme uses a theme engine, process its hooks. webmaster@1: $base_path = dirname($base->filename); webmaster@1: if ($theme_engine) { webmaster@1: _theme_process_registry($cache, $theme_engine, 'base_theme_engine', $base->name, $base_path); webmaster@1: } webmaster@1: _theme_process_registry($cache, $base->name, 'base_theme', $base->name, $base_path); webmaster@1: } webmaster@1: webmaster@1: // And then the same thing, but for the theme. webmaster@1: if ($theme_engine) { webmaster@1: _theme_process_registry($cache, $theme_engine, 'theme_engine', $theme->name, dirname($theme->filename)); webmaster@1: } webmaster@1: webmaster@1: // Finally, hooks provided by the theme itself. webmaster@1: _theme_process_registry($cache, $theme->name, 'theme', $theme->name, dirname($theme->filename)); webmaster@1: webmaster@1: // Let modules alter the registry webmaster@1: drupal_alter('theme_registry', $cache); webmaster@1: return $cache; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Provides a list of currently available themes. webmaster@1: * webmaster@1: * If the database is active then it will be retrieved from the database. webmaster@1: * Otherwise it will retrieve a new list. webmaster@1: * webmaster@1: * @param $refresh webmaster@1: * Whether to reload the list of themes from the database. webmaster@1: * @return webmaster@1: * An array of the currently available themes. webmaster@1: */ webmaster@1: function list_themes($refresh = FALSE) { webmaster@1: static $list = array(); webmaster@1: webmaster@1: if ($refresh) { webmaster@1: $list = array(); webmaster@1: } webmaster@1: webmaster@1: if (empty($list)) { webmaster@1: $list = array(); webmaster@1: $themes = array(); webmaster@1: // Extract from the database only when it is available. webmaster@1: // Also check that the site is not in the middle of an install or update. webmaster@1: if (db_is_active() && !defined('MAINTENANCE_MODE')) { webmaster@1: $result = db_query("SELECT * FROM {system} WHERE type = '%s'", 'theme'); webmaster@1: while ($theme = db_fetch_object($result)) { webmaster@1: if (file_exists($theme->filename)) { webmaster@1: $theme->info = unserialize($theme->info); webmaster@1: $themes[] = $theme; webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: else { webmaster@1: // Scan the installation when the database should not be read. webmaster@1: $themes = _system_theme_data(); webmaster@1: } webmaster@1: webmaster@1: foreach ($themes as $theme) { webmaster@1: foreach ($theme->info['stylesheets'] as $media => $stylesheets) { webmaster@1: foreach ($stylesheets as $stylesheet => $path) { webmaster@7: $theme->stylesheets[$media][$stylesheet] = $path; webmaster@1: } webmaster@1: } webmaster@1: foreach ($theme->info['scripts'] as $script => $path) { webmaster@1: if (file_exists($path)) { webmaster@1: $theme->scripts[$script] = $path; webmaster@1: } webmaster@1: } webmaster@1: if (isset($theme->info['engine'])) { webmaster@1: $theme->engine = $theme->info['engine']; webmaster@1: } webmaster@1: if (isset($theme->info['base theme'])) { webmaster@1: $theme->base_theme = $theme->info['base theme']; webmaster@1: } webmaster@1: // Status is normally retrieved from the database. Add zero values when webmaster@1: // read from the installation directory to prevent notices. webmaster@1: if (!isset($theme->status)) { webmaster@1: $theme->status = 0; webmaster@1: } webmaster@1: $list[$theme->name] = $theme; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: return $list; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Generate the themed output. webmaster@1: * webmaster@1: * All requests for theme hooks must go through this function. It examines webmaster@1: * the request and routes it to the appropriate theme function. The theme webmaster@1: * registry is checked to determine which implementation to use, which may webmaster@1: * be a function or a template. webmaster@1: * webmaster@1: * If the implementation is a function, it is executed and its return value webmaster@1: * passed along. webmaster@1: * webmaster@1: * If the implementation is a template, the arguments are converted to a webmaster@1: * $variables array. This array is then modified by the module implementing webmaster@1: * the hook, theme engine (if applicable) and the theme. The following webmaster@1: * functions may be used to modify the $variables array. They are processed in webmaster@1: * this order when available: webmaster@1: * webmaster@1: * - template_preprocess(&$variables) webmaster@1: * This sets a default set of variables for all template implementations. webmaster@1: * webmaster@1: * - template_preprocess_HOOK(&$variables) webmaster@1: * This is the first preprocessor called specific to the hook; it should be webmaster@1: * implemented by the module that registers it. webmaster@1: * webmaster@1: * - MODULE_preprocess(&$variables) webmaster@1: * This will be called for all templates; it should only be used if there webmaster@1: * is a real need. It's purpose is similar to template_preprocess(). webmaster@1: * webmaster@1: * - MODULE_preprocess_HOOK(&$variables) webmaster@1: * This is for modules that want to alter or provide extra variables for webmaster@1: * theming hooks not registered to itself. For example, if a module named webmaster@1: * "foo" wanted to alter the $submitted variable for the hook "node" a webmaster@1: * preprocess function of foo_preprocess_node() can be created to intercept webmaster@1: * and alter the variable. webmaster@1: * webmaster@1: * - ENGINE_engine_preprocess(&$variables) webmaster@1: * This function should only be implemented by theme engines and exists webmaster@1: * so that it can set necessary variables for all hooks. webmaster@1: * webmaster@1: * - ENGINE_engine_preprocess_HOOK(&$variables) webmaster@1: * This is the same as the previous function, but it is called for a single webmaster@1: * theming hook. webmaster@1: * webmaster@1: * - ENGINE_preprocess(&$variables) webmaster@1: * This is meant to be used by themes that utilize a theme engine. It is webmaster@1: * provided so that the preprocessor is not locked into a specific theme. webmaster@1: * This makes it easy to share and transport code but theme authors must be webmaster@1: * careful to prevent fatal re-declaration errors when using sub-themes that webmaster@1: * have their own preprocessor named exactly the same as its base theme. In webmaster@1: * the default theme engine (PHPTemplate), sub-themes will load their own webmaster@1: * template.php file in addition to the one used for its parent theme. This webmaster@1: * increases the risk for these errors. A good practice is to use the engine webmaster@1: * name for the base theme and the theme name for the sub-themes to minimize webmaster@1: * this possibility. webmaster@1: * webmaster@1: * - ENGINE_preprocess_HOOK(&$variables) webmaster@1: * The same applies from the previous function, but it is called for a webmaster@1: * specific hook. webmaster@1: * webmaster@1: * - THEME_preprocess(&$variables) webmaster@1: * These functions are based upon the raw theme; they should primarily be webmaster@1: * used by themes that do not use an engine or by sub-themes. It serves the webmaster@1: * same purpose as ENGINE_preprocess(). webmaster@1: * webmaster@1: * - THEME_preprocess_HOOK(&$variables) webmaster@1: * The same applies from the previous function, but it is called for a webmaster@1: * specific hook. webmaster@1: * webmaster@1: * There are two special variables that these hooks can set: webmaster@1: * 'template_file' and 'template_files'. These will be merged together webmaster@1: * to form a list of 'suggested' alternate template files to use, in webmaster@1: * reverse order of priority. template_file will always be a higher webmaster@1: * priority than items in template_files. theme() will then look for these webmaster@1: * files, one at a time, and use the first one webmaster@1: * that exists. webmaster@1: * @param $hook webmaster@1: * The name of the theme function to call. May be an array, in which webmaster@1: * case the first hook that actually has an implementation registered webmaster@1: * will be used. This can be used to choose 'fallback' theme implementations, webmaster@1: * so that if the specific theme hook isn't implemented anywhere, a more webmaster@1: * generic one will be used. This can allow themes to create specific theme webmaster@1: * implementations for named objects. webmaster@1: * @param ... webmaster@1: * Additional arguments to pass along to the theme function. webmaster@1: * @return webmaster@1: * An HTML string that generates the themed output. webmaster@1: */ webmaster@1: function theme() { webmaster@1: $args = func_get_args(); webmaster@1: $hook = array_shift($args); webmaster@1: webmaster@1: static $hooks = NULL; webmaster@1: if (!isset($hooks)) { webmaster@1: init_theme(); webmaster@1: $hooks = theme_get_registry(); webmaster@1: } webmaster@1: webmaster@1: if (is_array($hook)) { webmaster@1: foreach ($hook as $candidate) { webmaster@1: if (isset($hooks[$candidate])) { webmaster@1: break; webmaster@1: } webmaster@1: } webmaster@1: $hook = $candidate; webmaster@1: } webmaster@1: webmaster@1: if (!isset($hooks[$hook])) { webmaster@1: return; webmaster@1: } webmaster@1: webmaster@1: $info = $hooks[$hook]; webmaster@1: global $theme_path; webmaster@1: $temp = $theme_path; webmaster@1: // point path_to_theme() to the currently used theme path: webmaster@1: $theme_path = $hooks[$hook]['theme path']; webmaster@1: webmaster@1: // Include a file if the theme function or preprocess function is held elsewhere. webmaster@1: if (!empty($info['file'])) { webmaster@1: $include_file = $info['file']; webmaster@1: if (isset($info['path'])) { webmaster@1: $include_file = $info['path'] .'/'. $include_file; webmaster@1: } webmaster@1: include_once($include_file); webmaster@1: } webmaster@1: if (isset($info['function'])) { webmaster@1: // The theme call is a function. webmaster@1: $output = call_user_func_array($info['function'], $args); webmaster@1: } webmaster@1: else { webmaster@1: // The theme call is a template. webmaster@1: $variables = array( webmaster@1: 'template_files' => array() webmaster@1: ); webmaster@1: if (!empty($info['arguments'])) { webmaster@1: $count = 0; webmaster@1: foreach ($info['arguments'] as $name => $default) { webmaster@1: $variables[$name] = isset($args[$count]) ? $args[$count] : $default; webmaster@1: $count++; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: // default render function and extension. webmaster@1: $render_function = 'theme_render_template'; webmaster@1: $extension = '.tpl.php'; webmaster@1: webmaster@1: // Run through the theme engine variables, if necessary webmaster@1: global $theme_engine; webmaster@1: if (isset($theme_engine)) { webmaster@1: // If theme or theme engine is implementing this, it may have webmaster@1: // a different extension and a different renderer. webmaster@1: if ($hooks[$hook]['type'] != 'module') { webmaster@1: if (function_exists($theme_engine .'_render_template')) { webmaster@1: $render_function = $theme_engine .'_render_template'; webmaster@1: } webmaster@1: $extension_function = $theme_engine .'_extension'; webmaster@1: if (function_exists($extension_function)) { webmaster@1: $extension = $extension_function(); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: if (isset($info['preprocess functions']) && is_array($info['preprocess functions'])) { webmaster@1: // This construct ensures that we can keep a reference through webmaster@1: // call_user_func_array. webmaster@1: $args = array(&$variables, $hook); webmaster@1: foreach ($info['preprocess functions'] as $preprocess_function) { webmaster@1: if (function_exists($preprocess_function)) { webmaster@1: call_user_func_array($preprocess_function, $args); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: // Get suggestions for alternate templates out of the variables webmaster@1: // that were set. This lets us dynamically choose a template webmaster@1: // from a list. The order is FILO, so this array is ordered from webmaster@1: // least appropriate first to most appropriate last. webmaster@1: $suggestions = array(); webmaster@1: webmaster@1: if (isset($variables['template_files'])) { webmaster@1: $suggestions = $variables['template_files']; webmaster@1: } webmaster@1: if (isset($variables['template_file'])) { webmaster@1: $suggestions[] = $variables['template_file']; webmaster@1: } webmaster@1: webmaster@1: if ($suggestions) { webmaster@1: $template_file = drupal_discover_template($info['theme paths'], $suggestions, $extension); webmaster@1: } webmaster@1: webmaster@1: if (empty($template_file)) { webmaster@1: $template_file = $hooks[$hook]['template'] . $extension; webmaster@1: if (isset($hooks[$hook]['path'])) { webmaster@1: $template_file = $hooks[$hook]['path'] .'/'. $template_file; webmaster@1: } webmaster@1: } webmaster@1: $output = $render_function($template_file, $variables); webmaster@1: } webmaster@1: // restore path_to_theme() webmaster@1: $theme_path = $temp; webmaster@1: return $output; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Choose which template file to actually render. These are all suggested webmaster@1: * templates from themes and modules. Theming implementations can occur on webmaster@1: * multiple levels. All paths are checked to account for this. webmaster@1: */ webmaster@1: function drupal_discover_template($paths, $suggestions, $extension = '.tpl.php') { webmaster@1: global $theme_engine; webmaster@1: webmaster@1: // Loop through all paths and suggestions in FIFO order. webmaster@1: $suggestions = array_reverse($suggestions); webmaster@1: $paths = array_reverse($paths); webmaster@1: foreach ($suggestions as $suggestion) { webmaster@1: if (!empty($suggestion)) { webmaster@1: foreach ($paths as $path) { webmaster@1: if (file_exists($file = $path .'/'. $suggestion . $extension)) { webmaster@1: return $file; webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: /** webmaster@13: * Return the path to the current themed element. webmaster@13: * webmaster@13: * It can point to the active theme or the module handling a themed implementation. webmaster@13: * For example, when invoked within the scope of a theming call it will depend webmaster@13: * on where the theming function is handled. If implemented from a module, it webmaster@13: * will point to the module. If implemented from the active theme, it will point webmaster@13: * to the active theme. When called outside the scope of a theming call, it will webmaster@13: * always point to the active theme. webmaster@1: */ webmaster@1: function path_to_theme() { webmaster@1: global $theme_path; webmaster@1: webmaster@1: if (!isset($theme_path)) { webmaster@1: init_theme(); webmaster@1: } webmaster@1: webmaster@1: return $theme_path; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Find overridden theme functions. Called by themes and/or theme engines to webmaster@1: * easily discover theme functions. webmaster@1: * webmaster@1: * @param $cache webmaster@1: * The existing cache of theme hooks to test against. webmaster@1: * @param $prefixes webmaster@1: * An array of prefixes to test, in reverse order of importance. webmaster@1: * webmaster@1: * @return $templates webmaster@1: * The functions found, suitable for returning from hook_theme; webmaster@1: */ webmaster@1: function drupal_find_theme_functions($cache, $prefixes) { webmaster@1: $templates = array(); webmaster@1: $functions = get_defined_functions(); webmaster@1: webmaster@1: foreach ($cache as $hook => $info) { webmaster@1: foreach ($prefixes as $prefix) { webmaster@1: if (!empty($info['pattern'])) { webmaster@1: $matches = preg_grep('/^'. $prefix .'_'. $info['pattern'] .'/', $functions['user']); webmaster@1: if ($matches) { webmaster@1: foreach ($matches as $match) { webmaster@1: $new_hook = str_replace($prefix .'_', '', $match); webmaster@1: $templates[$new_hook] = array( webmaster@1: 'function' => $match, webmaster@1: 'arguments' => $info['arguments'], webmaster@7: 'original hook' => $hook, webmaster@1: ); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: if (function_exists($prefix .'_'. $hook)) { webmaster@1: $templates[$hook] = array( webmaster@1: 'function' => $prefix .'_'. $hook, webmaster@1: ); webmaster@15: // Ensure that the pattern is maintained from base themes to its sub-themes. webmaster@15: // Each sub-theme will have their functions scanned so the pattern must be webmaster@15: // held for subsequent runs. webmaster@15: if (isset($info['pattern'])) { webmaster@15: $templates[$hook]['pattern'] = $info['pattern']; webmaster@15: } webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: return $templates; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Find overridden theme templates. Called by themes and/or theme engines to webmaster@1: * easily discover templates. webmaster@1: * webmaster@1: * @param $cache webmaster@1: * The existing cache of theme hooks to test against. webmaster@1: * @param $extension webmaster@1: * The extension that these templates will have. webmaster@1: * @param $path webmaster@1: * The path to search. webmaster@1: */ webmaster@1: function drupal_find_theme_templates($cache, $extension, $path) { webmaster@1: $templates = array(); webmaster@1: webmaster@1: // Collect paths to all sub-themes grouped by base themes. These will be webmaster@1: // used for filtering. This allows base themes to have sub-themes in its webmaster@1: // folder hierarchy without affecting the base themes template discovery. webmaster@1: $theme_paths = array(); webmaster@1: foreach (list_themes() as $theme_info) { webmaster@1: if (!empty($theme_info->base_theme)) { webmaster@1: $theme_paths[$theme_info->base_theme][$theme_info->name] = dirname($theme_info->filename); webmaster@1: } webmaster@1: } webmaster@1: foreach ($theme_paths as $basetheme => $subthemes) { webmaster@1: foreach ($subthemes as $subtheme => $subtheme_path) { webmaster@1: if (isset($theme_paths[$subtheme])) { webmaster@1: $theme_paths[$basetheme] = array_merge($theme_paths[$basetheme], $theme_paths[$subtheme]); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: global $theme; webmaster@1: $subtheme_paths = isset($theme_paths[$theme]) ? $theme_paths[$theme] : array(); webmaster@1: webmaster@1: // Escape the periods in the extension. webmaster@1: $regex = str_replace('.', '\.', $extension) .'$'; webmaster@1: // Because drupal_system_listing works the way it does, we check for real webmaster@1: // templates separately from checking for patterns. webmaster@1: $files = drupal_system_listing($regex, $path, 'name', 0); webmaster@1: foreach ($files as $template => $file) { webmaster@1: // Ignore sub-theme templates for the current theme. webmaster@1: if (strpos($file->filename, str_replace($subtheme_paths, '', $file->filename)) !== 0) { webmaster@1: continue; webmaster@1: } webmaster@1: // Chop off the remaining extensions if there are any. $template already webmaster@1: // has the rightmost extension removed, but there might still be more, webmaster@1: // such as with .tpl.php, which still has .tpl in $template at this point. webmaster@1: if (($pos = strpos($template, '.')) !== FALSE) { webmaster@1: $template = substr($template, 0, $pos); webmaster@1: } webmaster@1: // Transform - in filenames to _ to match function naming scheme webmaster@1: // for the purposes of searching. webmaster@1: $hook = strtr($template, '-', '_'); webmaster@1: if (isset($cache[$hook])) { webmaster@1: $templates[$hook] = array( webmaster@1: 'template' => $template, webmaster@1: 'path' => dirname($file->filename), webmaster@1: ); webmaster@1: } webmaster@15: // Ensure that the pattern is maintained from base themes to its sub-themes. webmaster@15: // Each sub-theme will have their templates scanned so the pattern must be webmaster@15: // held for subsequent runs. webmaster@15: if (isset($cache[$hook]['pattern'])) { webmaster@15: $templates[$hook]['pattern'] = $cache[$hook]['pattern']; webmaster@15: } webmaster@1: } webmaster@1: webmaster@1: $patterns = array_keys($files); webmaster@1: webmaster@1: foreach ($cache as $hook => $info) { webmaster@1: if (!empty($info['pattern'])) { webmaster@1: // Transform _ in pattern to - to match file naming scheme webmaster@1: // for the purposes of searching. webmaster@1: $pattern = strtr($info['pattern'], '_', '-'); webmaster@1: webmaster@1: $matches = preg_grep('/^'. $pattern .'/', $patterns); webmaster@1: if ($matches) { webmaster@1: foreach ($matches as $match) { webmaster@1: $file = substr($match, 0, strpos($match, '.')); webmaster@1: // Put the underscores back in for the hook name and register this pattern. webmaster@1: $templates[strtr($file, '-', '_')] = array( webmaster@1: 'template' => $file, webmaster@1: 'path' => dirname($files[$match]->filename), webmaster@1: 'arguments' => $info['arguments'], webmaster@7: 'original hook' => $hook, webmaster@1: ); webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: return $templates; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Retrieve an associative array containing the settings for a theme. webmaster@1: * webmaster@1: * The final settings are arrived at by merging the default settings, webmaster@1: * the site-wide settings, and the settings defined for the specific theme. webmaster@1: * If no $key was specified, only the site-wide theme defaults are retrieved. webmaster@1: * webmaster@1: * The default values for each of settings are also defined in this function. webmaster@1: * To add new settings, add their default values here, and then add form elements webmaster@1: * to system_theme_settings() in system.module. webmaster@1: * webmaster@1: * @param $key webmaster@1: * The template/style value for a given theme. webmaster@1: * webmaster@1: * @return webmaster@1: * An associative array containing theme settings. webmaster@1: */ webmaster@1: function theme_get_settings($key = NULL) { webmaster@1: $defaults = array( webmaster@1: 'mission' => '', webmaster@1: 'default_logo' => 1, webmaster@1: 'logo_path' => '', webmaster@1: 'default_favicon' => 1, webmaster@1: 'favicon_path' => '', webmaster@1: 'primary_links' => 1, webmaster@1: 'secondary_links' => 1, webmaster@1: 'toggle_logo' => 1, webmaster@1: 'toggle_favicon' => 1, webmaster@1: 'toggle_name' => 1, webmaster@1: 'toggle_search' => 1, webmaster@1: 'toggle_slogan' => 0, webmaster@1: 'toggle_mission' => 1, webmaster@1: 'toggle_node_user_picture' => 0, webmaster@1: 'toggle_comment_user_picture' => 0, webmaster@1: 'toggle_primary_links' => 1, webmaster@1: 'toggle_secondary_links' => 1, webmaster@1: ); webmaster@1: webmaster@1: if (module_exists('node')) { webmaster@1: foreach (node_get_types() as $type => $name) { webmaster@1: $defaults['toggle_node_info_'. $type] = 1; webmaster@1: } webmaster@1: } webmaster@1: $settings = array_merge($defaults, variable_get('theme_settings', array())); webmaster@1: webmaster@1: if ($key) { webmaster@1: $settings = array_merge($settings, variable_get(str_replace('/', '_', 'theme_'. $key .'_settings'), array())); webmaster@1: } webmaster@1: webmaster@1: // Only offer search box if search.module is enabled. webmaster@1: if (!module_exists('search') || !user_access('search content')) { webmaster@1: $settings['toggle_search'] = 0; webmaster@1: } webmaster@1: webmaster@1: return $settings; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Retrieve a setting for the current theme. webmaster@1: * This function is designed for use from within themes & engines webmaster@1: * to determine theme settings made in the admin interface. webmaster@1: * webmaster@1: * Caches values for speed (use $refresh = TRUE to refresh cache) webmaster@1: * webmaster@1: * @param $setting_name webmaster@1: * The name of the setting to be retrieved. webmaster@1: * webmaster@1: * @param $refresh webmaster@1: * Whether to reload the cache of settings. webmaster@1: * webmaster@1: * @return webmaster@1: * The value of the requested setting, NULL if the setting does not exist. webmaster@1: */ webmaster@1: function theme_get_setting($setting_name, $refresh = FALSE) { webmaster@1: global $theme_key; webmaster@1: static $settings; webmaster@1: webmaster@1: if (empty($settings) || $refresh) { webmaster@1: $settings = theme_get_settings($theme_key); webmaster@1: webmaster@1: $themes = list_themes(); webmaster@1: $theme_object = $themes[$theme_key]; webmaster@1: webmaster@1: if ($settings['mission'] == '') { webmaster@1: $settings['mission'] = variable_get('site_mission', ''); webmaster@1: } webmaster@1: webmaster@1: if (!$settings['toggle_mission']) { webmaster@1: $settings['mission'] = ''; webmaster@1: } webmaster@1: webmaster@1: if ($settings['toggle_logo']) { webmaster@1: if ($settings['default_logo']) { webmaster@1: $settings['logo'] = base_path() . dirname($theme_object->filename) .'/logo.png'; webmaster@1: } webmaster@1: elseif ($settings['logo_path']) { webmaster@1: $settings['logo'] = base_path() . $settings['logo_path']; webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: if ($settings['toggle_favicon']) { webmaster@1: if ($settings['default_favicon']) { webmaster@1: if (file_exists($favicon = dirname($theme_object->filename) .'/favicon.ico')) { webmaster@1: $settings['favicon'] = base_path() . $favicon; webmaster@1: } webmaster@1: else { webmaster@1: $settings['favicon'] = base_path() .'misc/favicon.ico'; webmaster@1: } webmaster@1: } webmaster@1: elseif ($settings['favicon_path']) { webmaster@1: $settings['favicon'] = base_path() . $settings['favicon_path']; webmaster@1: } webmaster@1: else { webmaster@1: $settings['toggle_favicon'] = FALSE; webmaster@1: } webmaster@1: } webmaster@1: } webmaster@1: webmaster@1: return isset($settings[$setting_name]) ? $settings[$setting_name] : NULL; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Render a system default template, which is essentially a PHP template. webmaster@1: * webmaster@11: * @param $template_file webmaster@11: * The filename of the template to render. Note that this will overwrite webmaster@11: * anything stored in $variables['template_file'] if using a preprocess hook. webmaster@1: * @param $variables webmaster@1: * A keyed array of variables that will appear in the output. webmaster@1: * webmaster@1: * @return webmaster@1: * The output generated by the template. webmaster@1: */ webmaster@11: function theme_render_template($template_file, $variables) { webmaster@1: extract($variables, EXTR_SKIP); // Extract the variables to a local namespace webmaster@1: ob_start(); // Start output buffering webmaster@11: include "./$template_file"; // Include the template file webmaster@1: $contents = ob_get_contents(); // Get the contents of the buffer webmaster@1: ob_end_clean(); // End buffering and discard webmaster@1: return $contents; // Return the contents webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * @defgroup themeable Default theme implementations webmaster@1: * @{ webmaster@1: * Functions and templates that present output to the user, and can be webmaster@1: * implemented by themes. webmaster@1: * webmaster@1: * Drupal's presentation layer is a pluggable system known as the theme webmaster@1: * layer. Each theme can take control over most of Drupal's output, and webmaster@1: * has complete control over the CSS. webmaster@1: * webmaster@1: * Inside Drupal, the theme layer is utilized by the use of the theme() webmaster@1: * function, which is passed the name of a component (the theme hook) webmaster@1: * and several arguments. For example, theme('table', $header, $rows); webmaster@1: * Additionally, the theme() function can take an array of theme webmaster@1: * hooks, which can be used to provide 'fallback' implementations to webmaster@1: * allow for more specific control of output. For example, the function: webmaster@1: * theme(array('table__foo', 'table'), $header, $rows) would look to see if webmaster@1: * 'table__foo' is registered anywhere; if it is not, it would 'fall back' webmaster@1: * to the generic 'table' implementation. This can be used to attach specific webmaster@1: * theme functions to named objects, allowing the themer more control over webmaster@1: * specific types of output. webmaster@1: * webmaster@1: * As of Drupal 6, every theme hook is required to be registered by the webmaster@1: * module that owns it, so that Drupal can tell what to do with it and webmaster@1: * to make it simple for themes to identify and override the behavior webmaster@1: * for these calls. webmaster@1: * webmaster@1: * The theme hooks are registered via hook_theme(), which returns an webmaster@1: * array of arrays with information about the hook. It describes the webmaster@1: * arguments the function or template will need, and provides webmaster@1: * defaults for the template in case they are not filled in. If the default webmaster@1: * implementation is a function, by convention it is named theme_HOOK(). webmaster@1: * webmaster@7: * Each module should provide a default implementation for theme_hooks that webmaster@1: * it registers. This implementation may be either a function or a template; webmaster@1: * if it is a function it must be specified via hook_theme(). By convention, webmaster@1: * default implementations of theme hooks are named theme_HOOK. Default webmaster@1: * template implementations are stored in the module directory. webmaster@1: * webmaster@1: * Drupal's default template renderer is a simple PHP parsing engine that webmaster@1: * includes the template and stores the output. Drupal's theme engines webmaster@1: * can provide alternate template engines, such as XTemplate, Smarty and webmaster@1: * PHPTal. The most common template engine is PHPTemplate (included with webmaster@1: * Drupal and implemented in phptemplate.engine, which uses Drupal's default webmaster@1: * template renderer. webmaster@1: * webmaster@1: * In order to create theme-specific implementations of these hooks, webmaster@1: * themes can implement their own version of theme hooks, either as functions webmaster@1: * or templates. These implementations will be used instead of the default webmaster@1: * implementation. If using a pure .theme without an engine, the .theme is webmaster@1: * required to implement its own version of hook_theme() to tell Drupal what webmaster@1: * it is implementing; themes utilizing an engine will have their well-named webmaster@1: * theming functions automatically registered for them. While this can vary webmaster@1: * based upon the theme engine, the standard set by phptemplate is that theme webmaster@1: * functions should be named either phptemplate_HOOK or THEMENAME_HOOK. For webmaster@1: * example, for Drupal's default theme (Garland) to implement the 'table' hook, webmaster@1: * the phptemplate.engine would find phptemplate_table() or garland_table(). webmaster@1: * The ENGINE_HOOK() syntax is preferred, as this can be used by sub-themes webmaster@1: * (which are themes that share code but use different stylesheets). webmaster@1: * webmaster@1: * The theme system is described and defined in theme.inc. webmaster@1: * webmaster@1: * @see theme() webmaster@1: * @see hook_theme() webmaster@1: */ webmaster@1: webmaster@1: /** webmaster@1: * Formats text for emphasized display in a placeholder inside a sentence. webmaster@1: * Used automatically by t(). webmaster@1: * webmaster@1: * @param $text webmaster@1: * The text to format (plain-text). webmaster@1: * @return webmaster@1: * The formatted text (html). webmaster@1: */ webmaster@1: function theme_placeholder($text) { webmaster@1: return ''. check_plain($text) .''; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Return a themed set of status and/or error messages. The messages are grouped webmaster@1: * by type. webmaster@1: * webmaster@1: * @param $display webmaster@1: * (optional) Set to 'status' or 'error' to display only messages of that type. webmaster@1: * webmaster@1: * @return webmaster@1: * A string containing the messages. webmaster@1: */ webmaster@1: function theme_status_messages($display = NULL) { webmaster@1: $output = ''; webmaster@1: foreach (drupal_get_messages($display) as $type => $messages) { webmaster@1: $output .= "
\n"; webmaster@1: } webmaster@1: return $output; webmaster@1: } webmaster@1: webmaster@1: /** webmaster@1: * Return a themed set of links. webmaster@1: * webmaster@1: * @param $links webmaster@1: * A keyed array of links to be themed. webmaster@1: * @param $attributes webmaster@1: * A keyed array of attributes webmaster@1: * @return webmaster@1: * A string containing an unordered list of links. webmaster@1: */ webmaster@1: function theme_links($links, $attributes = array('class' => 'links')) { webmaster@1: $output = ''; webmaster@1: webmaster@1: if (count($links) > 0) { webmaster@1: $output = '