diff options
author | Dries Buytaert <dries@buytaert.net> | 2007-04-06 13:27:23 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2007-04-06 13:27:23 +0000 |
commit | 5bbbf10ba84042b8576d67576d98922c0063c6d6 (patch) | |
tree | a2eef7bccd7d5289426b3c8edc23f52bc9f6e2ed /includes | |
parent | 21c5b71795aec277a8b01ecea74e809a24be0229 (diff) | |
download | brdo-5bbbf10ba84042b8576d67576d98922c0063c6d6.tar.gz brdo-5bbbf10ba84042b8576d67576d98922c0063c6d6.tar.bz2 |
- Patch #130987 by merlinofchaos: added theme registry for easier themability.
Diffstat (limited to 'includes')
-rw-r--r-- | includes/bootstrap.inc | 9 | ||||
-rw-r--r-- | includes/common.inc | 190 | ||||
-rw-r--r-- | includes/form.inc | 4 | ||||
-rw-r--r-- | includes/theme.inc | 381 |
4 files changed, 530 insertions, 54 deletions
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index adece9912..390768c66 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -929,6 +929,15 @@ function drupal_maintenance_theme() { drupal_add_css(drupal_get_path('module', 'system') .'/defaults.css', 'module'); drupal_add_css(drupal_get_path('module', 'system') .'/system.css', 'module'); $theme = ''; + + // Special case registry of theme functions used by the installer + $themes = drupal_common_themes(); + foreach ($themes as $hook => $info) { + if (!isset($info['file']) && !isset($info['function'])) { + $themes[$hook]['function'] = 'theme_' . $hook; + } + } + _theme_set_registry($themes); } /** diff --git a/includes/common.inc b/includes/common.inc index 7ddf2ac82..5f4e85922 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -2292,3 +2292,193 @@ function element_child($key) { function element_children($element) { return array_filter(array_keys((array) $element), 'element_child'); } + +/** + * Provide theme registration for themes across .inc files. + */ +function drupal_common_themes() { + return array( + // theme.inc + 'placeholder' => array( + 'arguments' => array('text' => NULL) + ), + 'page' => array( + 'arguments' => array('content' => NULL, 'show_blocks' => TRUE), + ), + 'maintenance_page' => array( + 'arguments' => array('content' => NULL, 'messages' => TRUE), + ), + 'install_page' => array( + 'arguments' => array('content' => NULL), + ), + 'task_list' => array( + 'arguments' => array('items' => NULL, 'active' => NULL), + ), + 'status_messages' => array( + 'arguments' => array('display' => NULL), + ), + 'links' => array( + 'arguments' => array('links' => NULL, 'attributes' => array('class' => 'links')), + ), + 'image' => array( + 'arguments' => array('path' => NULL, 'alt' => '', 'title' => '', 'attributes' => NULL, 'getsize' => TRUE), + ), + 'breadcrumb' => array( + 'arguments' => array('breadcrumb' => NULL), + ), + 'help' => array( + 'arguments' => array(), + ), + 'node' => array( + 'arguments' => array('node' => NULL, 'teaser' => FALSE, 'page' => FALSE), + ), + 'submenu' => array( + 'arguments' => array('links' => NULL), + ), + 'table' => array( + 'arguments' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL), + ), + 'table_select_header_cell' => array( + 'arguments' => array(), + ), + 'tablesort_indicator' => array( + 'arguments' => array('style' => NULL), + ), + 'box' => array( + 'arguments' => array('title' => NULL, 'content' => NULL, 'region' => 'main'), + ), + 'block' => array( + 'arguments' => array('block' => NULL), + ), + 'mark' => array( + 'arguments' => array('type' => MARK_NEW), + ), + 'item_list' => array( + 'arguments' => array('items' => array(), 'title' => NULL, 'type' => 'ul', 'attributes' => NULL), + ), + 'more_help_link' => array( + 'arguments' => array('url' => NULL), + ), + 'xml_icon' => array( + 'arguments' => array('url' => NULL), + ), + 'feed_icon' => array( + 'arguments' => array('url' => NULL), + ), + 'closure' => array( + 'arguments' => array('main' => 0), + ), + 'blocks' => array( + 'arguments' => array('region' => NULL), + ), + 'username' => array( + 'arguments' => array('object' => NULL), + ), + 'progress_bar' => array( + 'arguments' => array('percent' => NULL, 'message' => NULL), + ), + // from pager.inc + 'pager' => array( + 'arguments' => array('tags' => array(), 'limit' => 10, 'element' => 0, 'parameters' => array()), + ), + 'pager_first' => array( + 'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'parameters' => array()), + ), + 'pager_previous' => array( + 'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'interval' => 1, 'parameters' => array()), + ), + 'pager_next' => array( + 'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'interval' => 1, 'parameters' => array()), + ), + 'pager_last' => array( + 'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'parameters' => array()), + ), + 'pager_list' => array( + 'arguments' => array('limit' => NULL, 'element' => 0, 'quantity' => 5, 'text' => '', 'parameters' => array()), + ), + 'pager_link' => array( + 'arguments' => array('text' => NULL, 'page_new' => NULL, 'element' => NULL, 'parameters' => array(), 'attributes' => array()), + ), + // from locale.inc + 'locale_admin_manage_screen' => array( + 'arguments' => array('form' => NULL), + ), + // from menu.inc + 'menu_item_link' => array( + 'arguments' => array('item' => NULL), + ), + 'menu_tree' => array( + 'arguments' => array('tree' => NULL), + ), + 'menu_item' => array( + 'arguments' => array('link' => NULL, 'has_children' => NULL, 'menu' => ''), + ), + 'menu_local_task' => array( + 'arguments' => array('link' => NULL, 'active' => FALSE), + ), + 'menu_local_tasks' => array( + 'arguments' => array(), + ), + // from form.inc + 'select' => array( + 'arguments' => array('element' => NULL), + ), + 'fieldset' => array( + 'arguments' => array('element' => NULL), + ), + 'radio' => array( + 'arguments' => array('element' => NULL), + ), + 'radios' => array( + 'arguments' => array('element' => NULL), + ), + 'password_confirm' => array( + 'arguments' => array('element' => NULL), + ), + 'date' => array( + 'arguments' => array('element' => NULL), + ), + 'item' => array( + 'arguments' => array('element' => NULL), + ), + 'checkbox' => array( + 'arguments' => array('element' => NULL), + ), + 'checkboxes' => array( + 'arguments' => array('element' => NULL), + ), + 'submit' => array( + 'arguments' => array('element' => NULL), + ), + 'button' => array( + 'arguments' => array('element' => NULL), + ), + 'hidden' => array( + 'arguments' => array('element' => NULL), + ), + 'token' => array( + 'arguments' => array('element' => NULL), + ), + 'textfield' => array( + 'arguments' => array('element' => NULL), + ), + 'form' => array( + 'arguments' => array('element' => NULL), + ), + 'textarea' => array( + 'arguments' => array('element' => NULL), + ), + 'markup' => array( + 'arguments' => array('element' => NULL), + ), + 'password' => array( + 'arguments' => array('element' => NULL), + ), + 'file' => array( + 'arguments' => array('element' => NULL), + ), + 'form_element' => array( + 'arguments' => array('element' => NULL, 'value' => NULL), + ), + ); +} diff --git a/includes/form.inc b/includes/form.inc index 63b3b52dd..0ce8b3d3a 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -457,7 +457,9 @@ function drupal_render_form($form_id, &$form) { // Don't override #theme if someone already set it. if (!isset($form['#theme'])) { - if (theme_get_function($form_id)) { + init_theme(); + $registry = theme_get_registry(); + if (isset($registry[$form_id])) { $form['#theme'] = $form_id; } } diff --git a/includes/theme.inc b/includes/theme.inc index 267240b16..762885f86 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -72,6 +72,7 @@ function init_theme() { if (strpos($themes[$theme]->filename, '.theme')) { // file is a theme; include it include_once './' . $themes[$theme]->filename; + _theme_load_registry($theme); } elseif (strpos($themes[$theme]->description, '.engine')) { // file is a template; include its engine @@ -80,10 +81,116 @@ function init_theme() { if (function_exists($theme_engine .'_init')) { call_user_func($theme_engine .'_init', $themes[$theme]); } + _theme_load_registry($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. + */ +function _theme_load_registry($theme, $theme_engine = NULL) { + $cache = cache_get("theme_registry:$theme", 'cache'); + if (isset($cache->data)) { + $registry = unserialize($cache->data); + } + else { + $registry = _theme_build_registry($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", 'cache', serialize($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. + */ +function _theme_process_registry(&$cache, $name, $type) { + $function = $name .'_theme'; + if (function_exists($function)) { + $result = $function($cache); + + // Automatically find paths + $path = drupal_get_path($type, $name); + foreach ($result as $hook => $info) { + $result[$hook]['type'] = $type; + // if function and file are left out, default to standard naming + // conventions. + if (!isset($info['file']) && !isset($info['function'])) { + $result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name .'_') . $hook; + } + if (isset($info['file']) && !isset($info['path'])) { + $result[$hook]['file'] = $path .'/'. $info['file']; + } + // 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']; + } + } + + $cache = array_merge($cache, $result); + } +} + +/** + * Rebuild the hook theme_registry cache. + */ +function _theme_build_registry($theme, $theme_engine) { + $cache = array(); + foreach (module_implements('theme') as $module) { + _theme_process_registry($cache, $module, 'module'); + } + + if ($theme_engine) { + _theme_process_registry($cache, $theme_engine, 'theme_engine'); + } + + _theme_process_registry($cache, $theme, 'theme'); + + return $cache; +} + +/** * Provides a list of currently available themes. * * @param $refresh @@ -140,18 +247,48 @@ function list_theme_engines($refresh = FALSE) { } /** - * Generate the themed representation of a Drupal object. + * 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. * - * All requests for themed functions must go through this function. It examines - * the request and routes it to the appropriate theme function. If the current - * theme does not implement the requested function, then the current theme - * engine is checked. If neither the engine nor theme implement the requested - * function, then the base theme function is called. + * If the implementation is a template, the arguments are converted to a + * $variables array. This array is then modified by the theme engine (if + * applicable) and the theme. The following functions may be used to modify + * the $variables array: * - * For example, to retrieve the HTML that is output by theme_page($output), a - * module should call theme('page', $output). + * ENGINE_engine_variables(&$variables) + * This function should only be implemented by theme engines and is exists + * so that the theme engine can set necessary variables. It is commonly + * used to set global variables such as $directory and $is_front_page. + * ENGINE_engine_variables_HOOK(&$variables) + * This is the same as the previous function, but is called only per hook. + * ENGINE_variables_HOOK(&$variables) + * ENGINE_variables(&$variables) + * This is meant to be used by themes that utilize a theme engine; as it is + * good practice for these themes to use the theme engine's name for + * their functions so that they may share code. In PHPTemplate, these + * functions will appear in template.php + * THEME_variables_HOOK(&$variables) + * THEME_variables(&$variables) + * These functions are based upon the raw theme; they should primarily be + * used by themes that do not use an engine or by themes that need small + * changes to what has already been established in the theme engine version + * of the function. * - * @param $function + * 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. * @param ... * Additional arguments to pass along to the theme function. @@ -159,48 +296,125 @@ function list_theme_engines($refresh = FALSE) { * An HTML string that generates the themed output. */ function theme() { - static $functions; $args = func_get_args(); - $function = array_shift($args); + $hook = array_shift($args); - if (!isset($functions[$function])) { - $functions[$function] = theme_get_function($function); + static $hooks = NULL; + if (!isset($hooks)) { + init_theme(); + $hooks = theme_get_registry(); } - if ($functions[$function]) { - return call_user_func_array($functions[$function], $args); + + if (!isset($hooks[$hook])) { + return; } -} -/** - * Determine if a theme function exists, and if so return which one was found. - * - * @param $function - * The name of the theme function to test. - * @return - * The name of the theme function that should be used, or FALSE if no function exists. - */ -function theme_get_function($function) { - global $theme, $theme_engine; + $info = $hooks[$hook]; - // Because theme() is called a lot, calling init_theme() only to have it - // smartly return is a noticeable performance hit. Don't do it. - if (!isset($theme)) { - init_theme(); + if (isset($info['function'])) { + // The theme call is a function. + // Include a file if this theme function is held elsewhere. + if (!empty($info['file'])) { + include_once($info['file']); + } + return 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++; + } + } - if (($theme != '') && function_exists($theme .'_'. $function)) { - // call theme function - return $theme .'_'. $function; - } - elseif (($theme != '') && isset($theme_engine) && function_exists($theme_engine .'_'. $function)) { - // call engine function - return $theme_engine .'_'. $function; + // default render function and extension. + $render_function = 'theme_render_template'; + $extension = '.tpl.php'; + $variables_list = array(); + + // Run through the theme engine variables, if necessary + global $theme_engine; + if (isset($theme_engine)) { + // Call each of our variable override functions. We allow + // several to create cleaner code. + $variables_list[] = $theme_engine .'_engine_variables'; + $variables_list[] = $theme_engine .'_engine_variables_'. $hook; + $variables_list[] = $theme_engine .'_variables'; + $variables_list[] = $theme_engine .'_variables_'. $hook; + + // 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(); + } + } + } + + // Add theme specific variable substitution: + global $theme; + $variables_list[] = $theme .'_variables'; + $variables_list[] = $theme .'_variables_'. $hook; + + // This construct ensures that we can keep a reference through + // call_user_func_array. + $args = array(&$variables, $hook); + foreach ($variables_list as $variables_function) { + if (function_exists($variables_function)) { + call_user_func_array($variables_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($suggestions, $extension); + } + + if (empty($template_file)) { + $template_file = $hooks[$hook]['file'] . $extension; + if (isset($hooks[$hook]['path'])) { + $template_file = $hooks[$hook]['path'] .'/'. $template_file; + } + } + return $render_function($template_file, $variables); } - elseif (function_exists('theme_'. $function)){ - // call Drupal function - return 'theme_'. $function; +} + +/** + * Choose which template file to actually render; these are all + * suggested templates from the theme. + */ +function drupal_discover_template($suggestions, $extension = '.tpl.php') { + global $theme_engine; + + // Loop through any suggestions in FIFO order. + $suggestions = array_reverse($suggestions); + foreach ($suggestions as $suggestion) { + if (!empty($suggestion) && file_exists($file = path_to_theme() .'/'. $suggestion . $extension)) { + return $file; + } } - return FALSE; } /** @@ -348,16 +562,81 @@ function theme_get_setting($setting_name, $refresh = FALSE) { } /** - * @defgroup themeable Themeable functions + * 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 that display HTML, and which can be customized by themes. + * 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. * - * All functions that produce HTML for display should be themeable. This means - * that they should be named with the theme_ prefix, and invoked using theme() - * rather than being called directly. This allows themes to override the display - * of any Drupal object. + * 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); + * + * 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() */ /** @@ -458,9 +737,7 @@ function theme_maintenance_page($content, $messages = TRUE) { 'content' => $content, ); - // Render simplified PHPTemplate. - include_once './themes/engines/phptemplate/phptemplate.engine'; - $output = _phptemplate_render('misc/maintenance.tpl.php', $variables); + $output = theme_render_template('misc/maintenance.tpl.php', $variables); return $output; } @@ -509,9 +786,7 @@ function theme_install_page($content) { $variables['messages'] .= theme('status_messages', 'status'); } - // Render simplified PHPTemplate. - include_once './themes/engines/phptemplate/phptemplate.engine'; - return _phptemplate_render('misc/maintenance.tpl.php', $variables); + return theme_render_template('misc/maintenance.tpl.php', $variables); } /** |