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

    '. $title .'

    '. $content .'
    '; return $output; } /** * Return a themed marker, useful for marking new or updated * content. * * @param $type * Number representing the marker type to display * @see MARK_NEW, MARK_UPDATED, MARK_READ * @return * A string containing the marker. */ function theme_mark($type = MARK_NEW) { global $user; if ($user->uid) { if ($type == MARK_NEW) { return ' '. t('new') .''; } else if ($type == MARK_UPDATED) { return ' '. t('updated') .''; } } } /** * Return a themed list of items. * * @param $items * An array of items to be displayed in the list. If an item is a string, * then it is used as is. If an item is an array, then the "data" element of * the array is used as the contents of the list item. If an item is an array * with a "children" element, those children are displayed in a nested list. * All other elements are treated as attributes of the list item element. * @param $title * The title of the list. * @param $attributes * The attributes applied to the list element. * @param $type * The type of list to return (e.g. "ul", "ol") * @return * A string containing the list output. */ function theme_item_list($items = array(), $title = NULL, $type = 'ul', $attributes = NULL) { $output = '
    '; if (isset($title)) { $output .= '

    '. $title .'

    '; } if (!empty($items)) { $output .= "<$type". drupal_attributes($attributes) .'>'; $num_items = count($items); foreach ($items as $i => $item) { $attributes = array(); $children = array(); if (is_array($item)) { foreach ($item as $key => $value) { if ($key == 'data') { $data = $value; } elseif ($key == 'children') { $children = $value; } else { $attributes[$key] = $value; } } } else { $data = $item; } if (count($children) > 0) { $data .= theme_item_list($children, NULL, $type, $attributes); // Render nested list } if ($i == 0) { $attributes['class'] = empty($attributes['class']) ? 'first' : ($attributes['class'] .' first'); } if ($i == $num_items - 1) { $attributes['class'] = empty($attributes['class']) ? 'last' : ($attributes['class'] .' last'); } $output .= ''. $data .''; } $output .= ""; } $output .= '
    '; return $output; } /** * Returns code that emits the 'more help'-link. */ function theme_more_help_link($url) { return ''; } /** * Return code that emits an XML icon. */ function theme_xml_icon($url) { if ($image = theme('image', 'misc/xml.png', t('XML feed'), t('XML feed'))) { return ''. $image .''; } } /** * Return code that emits an feed icon. * * @param $url * The url of the feed. * @param $title * A descriptive title of the feed. */ function theme_feed_icon($url, $title) { if ($image = theme('image', 'misc/feed.png', t('Syndicate content'), $title)) { return ''. $image .''; } } /** * Returns code that emits the 'more' link used on blocks. * * @param $url * The url of the main page * @param $title * A descriptive verb for the link, like 'Read more' */ function theme_more_link($url, $title) { return ''; } /** * Execute hook_footer() which is run at the end of the page right before the * close of the body tag. * * @param $main (optional) * Whether the current page is the front page of the site. * @return * A string containing the results of the hook_footer() calls. */ function theme_closure($main = 0) { $footer = module_invoke_all('footer', $main); return implode("\n", $footer) . drupal_get_js('footer'); } /** * Return a set of blocks available for the current user. * * @param $region * Which set of blocks to retrieve. * @return * A string containing the themed blocks for this region. */ function theme_blocks($region) { $output = ''; if ($list = block_list($region)) { foreach ($list as $key => $block) { // $key == module_delta $output .= theme('block', $block); } } // Add any content assigned to this region through drupal_set_content() calls. $output .= drupal_get_content($region); return $output; } /** * Format a username. * * @param $object * The user object to format, usually returned from user_load(). * @return * A string containing an HTML link to the user's page if the passed object * suggests that this is a site user. Otherwise, only the username is returned. */ function theme_username($object) { if ($object->uid && $object->name) { // Shorten the name when it is too long or it will break many tables. if (drupal_strlen($object->name) > 20) { $name = drupal_substr($object->name, 0, 15) .'...'; } else { $name = $object->name; } if (user_access('access user profiles')) { $output = l($name, 'user/'. $object->uid, array('title' => t('View user profile.'))); } else { $output = check_plain($name); } } else if ($object->name) { // Sometimes modules display content composed by people who are // not registered members of the site (e.g. mailing list or news // aggregator modules). This clause enables modules to display // the true author of the content. if (!empty($object->homepage)) { $output = l($object->name, $object->homepage, array('rel' => 'nofollow')); } else { $output = check_plain($object->name); } $output .= ' ('. t('not verified') .')'; } else { $output = variable_get('anonymous', t('Anonymous')); } return $output; } /** * Return a themed progress bar. * * @param $percent * The percentage of the progress. * @param $message * A string containing information to be displayed. * @return * A themed HTML string representing the progress bar. */ function theme_progress_bar($percent, $message) { $output = '
    '; $output .= '
    '; $output .= '
    '. $percent .'%
    '; $output .= '
    '. $message .'
    '; $output .= '
    '; return $output; } /** * Create a standard indentation div. Used for drag and drop tables. * * @param $size * Optional. The number of indentations to create. * @return * A string containing indentations. */ function theme_indentation($size = 1) { $output = ''; for ($n = 0; $n < $size; $n++) { $output .= '
     
    '; } return $output; } /** * @} End of "defgroup themeable". */ function _theme_table_cell($cell, $header = FALSE) { $attributes = ''; if (is_array($cell)) { $data = isset($cell['data']) ? $cell['data'] : ''; $header |= isset($cell['header']); unset($cell['data']); unset($cell['header']); $attributes = drupal_attributes($cell); } else { $data = $cell; } if ($header) { $output = "$data"; } else { $output = "$data"; } return $output; } /** * Adds a default set of helper variables for preprocess functions and * templates. This comes in before any other preprocess function which makes * it possible to be used in default theme implementations (non-overriden * theme functions). */ function template_preprocess(&$variables, $hook) { global $user; static $count = array(); // Track run count for each hook to provide zebra striping. // See "template_preprocess_block()" which provides the same feature specific to blocks. $count[$hook] = isset($count[$hook]) && is_int($count[$hook]) ? $count[$hook] : 1; $variables['zebra'] = ($count[$hook] % 2) ? 'odd' : 'even'; $variables['id'] = $count[$hook]++; // Tell all templates where they are located. $variables['directory'] = path_to_theme(); // Set default variables that depend on the database. $variables['is_admin'] = FALSE; $variables['is_front'] = FALSE; $variables['logged_in'] = FALSE; if ($variables['db_is_active'] = db_is_active() && !defined('MAINTENANCE_MODE')) { // Check for administrators. if (user_access('access administration pages')) { $variables['is_admin'] = TRUE; } // Flag front page status. $variables['is_front'] = drupal_is_front_page(); // Tell all templates by which kind of user they're viewed. $variables['logged_in'] = ($user->uid > 0); // Provide user object to all templates $variables['user'] = $user; } } /** * Process variables for page.tpl.php * * Most themes utilize their own copy of page.tpl.php. The default is located * inside "modules/system/page.tpl.php". Look in there for the full list of * variables. * * Uses the arg() function to generate a series of page template suggestions * based on the current path. * * Any changes to variables in this preprocessor should also be changed inside * template_preprocess_maintenance_page() to keep all them consistent. * * The $variables array contains the following arguments: * - $content * - $show_blocks * * @see page.tpl.php */ function template_preprocess_page(&$variables) { // Add favicon if (theme_get_setting('toggle_favicon')) { drupal_set_html_head(''); } global $theme; // Populate all block regions. $regions = system_region_list($theme); // Load all region content assigned via blocks. foreach (array_keys($regions) as $region) { // Prevent left and right regions from rendering blocks when 'show_blocks' == FALSE. if (!(!$variables['show_blocks'] && ($region == 'left' || $region == 'right'))) { $blocks = theme('blocks', $region); } else { $blocks = ''; } // Assign region to a region variable. isset($variables[$region]) ? $variables[$region] .= $blocks : $variables[$region] = $blocks; } // Set up layout variable. $variables['layout'] = 'none'; if (!empty($variables['left'])) { $variables['layout'] = 'left'; } if (!empty($variables['right'])) { $variables['layout'] = ($variables['layout'] == 'left') ? 'both' : 'right'; } // Set mission when viewing the frontpage. if (drupal_is_front_page()) { $mission = filter_xss_admin(theme_get_setting('mission')); } // Construct page title if (drupal_get_title()) { $head_title = array(strip_tags(drupal_get_title()), variable_get('site_name', 'Drupal')); } else { $head_title = array(variable_get('site_name', 'Drupal')); if (variable_get('site_slogan', '')) { $head_title[] = variable_get('site_slogan', ''); } } $variables['head_title'] = implode(' | ', $head_title); $variables['base_path'] = base_path(); $variables['front_page'] = url(); $variables['breadcrumb'] = theme('breadcrumb', drupal_get_breadcrumb()); $variables['feed_icons'] = drupal_get_feeds(); $variables['footer_message'] = filter_xss_admin(variable_get('site_footer', FALSE)); $variables['head'] = drupal_get_html_head(); $variables['help'] = theme('help'); $variables['language'] = $GLOBALS['language']; $variables['logo'] = theme_get_setting('logo'); $variables['messages'] = $variables['show_messages'] ? theme('status_messages') : ''; $variables['mission'] = isset($mission) ? $mission : ''; $variables['primary_links'] = theme_get_setting('toggle_primary_links') ? menu_primary_links() : array(); $variables['secondary_links'] = theme_get_setting('toggle_secondary_links') ? menu_secondary_links() : array(); $variables['search_box'] = (theme_get_setting('toggle_search') ? drupal_get_form('search_theme_form') : ''); $variables['site_name'] = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : ''); $variables['site_slogan'] = (theme_get_setting('toggle_slogan') ? variable_get('site_slogan', '') : ''); $variables['css'] = drupal_add_css(); $variables['styles'] = drupal_get_css(); $variables['scripts'] = drupal_get_js(); $variables['tabs'] = theme('menu_local_tasks'); $variables['title'] = drupal_get_title(); // Closure should be filled last. $variables['closure'] = theme('closure'); if ($node = menu_get_object()) { $variables['node'] = $node; } // Compile a list of classes that are going to be applied to the body element. // This allows advanced theming based on context (home page, node of certain type, etc.). $body_classes = array(); // Add a class that tells us whether we're on the front page or not. $body_classes[] = $variables['is_front'] ? 'front' : 'not-front'; // Add a class that tells us whether the page is viewed by an authenticated user or not. $body_classes[] = $variables['logged_in'] ? 'logged-in' : 'not-logged-in'; // Add arg(0) to make it possible to theme the page depending on the current page // type (e.g. node, admin, user, etc.). To avoid illegal characters in the class, // we're removing everything disallowed. We are not using 'a-z' as that might leave // in certain international characters (e.g. German umlauts). $body_classes[] = preg_replace('![^abcdefghijklmnopqrstuvwxyz0-9-_]+!s', '', 'page-'. form_clean_id(drupal_strtolower(arg(0)))); // If on an individual node page, add the node type. if (isset($variables['node']) && $variables['node']->type) { $body_classes[] = 'node-type-'. form_clean_id($variables['node']->type); } // Add information about the number of sidebars. if ($variables['layout'] == 'both') { $body_classes[] = 'two-sidebars'; } elseif ($variables['layout'] == 'none') { $body_classes[] = 'no-sidebars'; } else { $body_classes[] = 'one-sidebar sidebar-'. $variables['layout']; } // Implode with spaces. $variables['body_classes'] = implode(' ', $body_classes); // Build a list of suggested template files in order of specificity. One // suggestion is made for every element of the current path, though // numeric elements are not carried to subsequent suggestions. For example, // http://www.example.com/node/1/edit would result in the following // suggestions: // // page-node-edit.tpl.php // page-node-1.tpl.php // page-node.tpl.php // page.tpl.php $i = 0; $suggestion = 'page'; $suggestions = array(); while ($arg = arg($i++)) { $suggestions[] = $suggestion .'-'. $arg; if (!is_numeric($arg)) { $suggestion .= '-'. $arg; } } if (drupal_is_front_page()) { $suggestions[] = 'page-front'; } if ($suggestions) { $variables['template_files'] = $suggestions; } } /** * Process variables for node.tpl.php * * Most themes utilize their own copy of node.tpl.php. The default is located * inside "modules/node/node.tpl.php". Look in there for the full list of * variables. * * The $variables array contains the following arguments: * - $node * - $teaser * - $page * * @see node.tpl.php */ function template_preprocess_node(&$variables) { $node = $variables['node']; if (module_exists('taxonomy')) { $variables['taxonomy'] = taxonomy_link('taxonomy terms', $node); } else { $variables['taxonomy'] = array(); } if ($variables['teaser'] && $node->teaser) { $variables['content'] = $node->teaser; } elseif (isset($node->body)) { $variables['content'] = $node->body; } else { $variables['content'] = ''; } $variables['date'] = format_date($node->created); $variables['links'] = !empty($node->links) ? theme('links', $node->links, array('class' => 'links inline')) : ''; $variables['name'] = theme('username', $node); $variables['node_url'] = url('node/'. $node->nid); $variables['terms'] = theme('links', $variables['taxonomy'], array('class' => 'links inline')); $variables['title'] = check_plain($node->title); // Flatten the node object's member fields. $variables = array_merge((array)$node, $variables); // Display info only on certain node types. if (theme_get_setting('toggle_node_info_'. $node->type)) { $variables['submitted'] = theme('node_submitted', $node); $variables['picture'] = theme_get_setting('toggle_node_user_picture') ? theme('user_picture', $node) : ''; } else { $variables['submitted'] = ''; $variables['picture'] = ''; } // Clean up name so there are no underscores. $variables['template_files'][] = 'node-'. $node->type; } /** * Process variables for block.tpl.php * * Prepare the values passed to the theme_block function to be passed * into a pluggable template engine. Uses block properties to generate a * series of template file suggestions. If none are found, the default * block.tpl.php is used. * * Most themes utilize their own copy of page.tpl.php. The default is located * inside "modules/system/block.tpl.php". Look in there for the full list of * variables. * * The $variables array contains the following arguments: * - $block * * @see block.tpl.php */ function template_preprocess_block(&$variables) { static $block_counter = array(); // All blocks get an independent counter for each region. if (!isset($block_counter[$variables['block']->region])) { $block_counter[$variables['block']->region] = 1; } // Same with zebra striping. $variables['block_zebra'] = ($block_counter[$variables['block']->region] % 2) ? 'odd' : 'even'; $variables['block_id'] = $block_counter[$variables['block']->region]++; $variables['template_files'][] = 'block-'. $variables['block']->region; $variables['template_files'][] = 'block-'. $variables['block']->module; $variables['template_files'][] = 'block-'. $variables['block']->module .'-'. $variables['block']->delta; }