Theme guide * @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_engine, $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; // If we're using a style, load its appropriate theme, // which is stored in the style's description field. // Also add the stylesheet using drupal_add_css(). // Otherwise, load the theme. if (strpos($themes[$theme]->filename, '.css')) { // File is a style; loads its CSS. // Set theme to its template/theme drupal_add_css($themes[$theme]->filename, 'theme'); $theme = basename(dirname($themes[$theme]->description)); } else { // File is a template/theme // Load its CSS, if it exists if (file_exists($stylesheet = dirname($themes[$theme]->filename) .'/style.css')) { drupal_add_css($stylesheet, 'theme'); } } if (strpos($themes[$theme]->filename, '.theme')) { // file is a theme; include it include_once './' . $themes[$theme]->filename; } elseif (strpos($themes[$theme]->description, '.engine')) { // file is a template; include its engine include_once './' . $themes[$theme]->description; $theme_engine = basename($themes[$theme]->description, '.engine'); if (function_exists($theme_engine .'_init')) { call_user_func($theme_engine .'_init', $themes[$theme]); } } } /** * Provides a list of currently available themes. * * @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; if ($refresh) { unset($list); } if (!$list) { $list = array(); $result = db_query("SELECT * FROM {system} WHERE type = 'theme'"); while ($theme = db_fetch_object($result)) { if (file_exists($theme->filename)) { $list[$theme->name] = $theme; } } } return $list; } /** * Provides a list of currently available theme engines * * @param $refresh * Whether to reload the list of themes from the database. * @return * An array of the currently available theme engines. */ function list_theme_engines($refresh = FALSE) { static $list; if ($refresh) { unset($list); } if (!$list) { $list = array(); $result = db_query("SELECT * FROM {system} WHERE type = 'theme_engine' AND status = '1' ORDER BY name"); while ($engine = db_fetch_object($result)) { if (file_exists($engine->filename)) { $list[$engine->name] = $engine; } } } return $list; } /** * Generate the themed representation of a Drupal object. * * 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. * * For example, to retrieve the HTML that is output by theme_page($output), a * module should call theme('page', $output). * * @param $function * The name of the theme function to call. * @param ... * Additional arguments to pass along to the theme function. * @return * An HTML string that generates the themed output. */ function theme() { static $functions; $args = func_get_args(); $function = array_shift($args); if (!isset($functions[$function])) { $functions[$function] = theme_get_function($function); } if ($functions[$function]) { return call_user_func_array($functions[$function], $args); } } /** * 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; // 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 (($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; } elseif (function_exists('theme_'. $function)){ // call Drupal function return 'theme_'. $function; } return FALSE; } /** * Return the path to the currently selected theme. */ function path_to_theme() { global $theme; if (!isset($theme)) { init_theme(); } $themes = list_themes(); return dirname($themes[$theme]->filename); } /** * Return the path to the currently selected engine. */ function path_to_engine() { global $theme, $theme_engine; if (!isset($theme)) { init_theme(); } $engines = list_theme_engines(); return dirname($engines[$theme_engine]->filename); } /** * 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' => '', '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, ); 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; } /** * @defgroup themeable Themeable functions * @{ * Functions that display HTML, and which can be customized by themes. * * 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. * * The theme system is described and defined in theme.inc. */ /** * 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 an entire Drupal page displaying the supplied content. * * @param $content * A string to display in the main content area of the page. * @return * A string containing the entire HTML page. */ function theme_page($content) { // Get blocks before so that they can alter the header (JavaScript, Stylesheets etc.) $blocks = theme('blocks', 'all'); $output = "\n"; $output .= ''; $output .= ''; $output .= ' '. (drupal_get_title() ? strip_tags(drupal_get_title()) : variable_get('site_name', 'Drupal')) .''; $output .= drupal_get_html_head(); $output .= drupal_get_css(); $output .= drupal_get_js(); $output .= ' '; $output .= ' '; $output .= '
'; $output .= $blocks; $output .= ''; $output .= theme('breadcrumb', drupal_get_breadcrumb()); $output .= '

' . drupal_get_title() . '

'; if ($tabs = theme('menu_local_tasks')) { $output .= $tabs; } $output .= theme('help'); $output .= theme('status_messages'); $output .= "\n\n"; $output .= $content; $output .= drupal_get_feeds(); $output .= "\n\n"; $output .= '
'; $output .= theme('closure'); $output .= ''; return $output; } function theme_maintenance_page($content, $messages = TRUE, $partial = FALSE) { drupal_set_header('Content-Type: text/html; charset=utf-8'); drupal_set_html_head(''); drupal_set_html_head(''); drupal_set_html_head(''); drupal_set_html_head(''); $output = "\n"; $output .= ''; $output .= ''; $output .= ' '. strip_tags(drupal_get_title()) .''; $output .= drupal_get_html_head(); $output .= drupal_get_js(); $output .= ''; $output .= ''; $output .= '

' . drupal_get_title() . '

'; if ($messages) { $output .= theme('status_messages'); } $output .= "\n\n"; $output .= $content; $output .= "\n\n"; if (!$partial) { $output .= ''; } return $output; } function theme_install_page($content) { drupal_set_header('Content-Type: text/html; charset=utf-8'); drupal_add_css('misc/maintenance.css', 'module', 'all', FALSE); drupal_set_html_head(''); $output = "\n"; $output .= ''; $output .= ''; $output .= ' '. strip_tags(drupal_get_title()) .''; $output .= drupal_get_html_head(); $output .= drupal_get_css(); $output .= drupal_get_js(); $output .= ''; $output .= ''; $output .= '

' . drupal_get_title() . '

'; $messages = drupal_set_message(); if (isset($messages['error'])) { $title = count($messages['error']) > 1 ? st('The following errors must be resolved before you can continue the installation process') : st('The following error must be resolved before you can continue the installation process'); $output .= '

' .$title. ':

'; $output .= theme('status_messages', 'error'); } if (isset($messages['status'])) { $warnings = count($messages['status']) > 1 ? st('The following installation warnings should be carefully reviewed, but in most cases may be safely ignored') : st('The following installation warning should be carefully reviewed, but in most cases may be safely ignored'); $output .= '

' .$title. ':

'; $output .= theme('status_messages', 'status'); } $output .= "\n\n"; $output .= $content; $output .= "\n\n"; $output .= ''; return $output; } /** * 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; // Automatically add a class to each link and also to each LI if (isset($link['attributes']) && isset($link['attributes']['class'])) { $link['attributes']['class'] .= ' ' . $key; } else { $link['attributes']['class'] = $key; } // Add first and last classes to the list of links to help out themers. $extra_class = ''; if ($i == 1) { $extra_class .= 'first '; } if ($i == $num_links) { $extra_class .= 'last '; } $output .= '
  • $extra_class . $class)) .'>'; // Is the title HTML? $html = isset($link['html']) && $link['html']; // Initialize fragment and query variables. $link['query'] = isset($link['query']) ? $link['query'] : NULL; $link['fragment'] = isset($link['fragment']) ? $link['fragment'] : NULL; if (isset($link['href'])) { $output .= l($link['title'], $link['href'], $link['attributes'], $link['query'], $link['fragment'], FALSE, $html); } else if ($link['title']) { //Some links are actually not links, but we wrap these in for adding title and class attributes if (!$html) { $link['title'] = check_plain($link['title']); } $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 node. * * @param $node * An object providing all relevant information for displaying a node: * - $node->nid: The ID of the node. * - $node->type: The content type (story, blog, forum...). * - $node->title: The title of the node. * - $node->created: The creation date, as a UNIX timestamp. * - $node->teaser: A shortened version of the node body. * - $node->body: The entire node contents. * - $node->changed: The last modification date, as a UNIX timestamp. * - $node->uid: The ID of the author. * - $node->username: The username of the author. * @param $teaser * Whether to display the teaser only, as on the main page. * @param $page * Whether to display the node as a standalone page. If TRUE, do not display * the title because it will be provided by the menu system. * @return * A string containing the node output. */ function theme_node($node, $teaser = FALSE, $page = FALSE) { if (!$node->status) { $output = '
    '; } if (module_exists('taxonomy')) { $terms = taxonomy_link('taxonomy terms', $node); } if ($page == 0) { $output .= t('!title by !name', array('!title' => '

    '. check_plain($node->title) .'

    ', '!name' => theme('username', $node))); } else { $output .= t('by !name', array('!name' => theme('username', $node))); } if (count($terms)) { $output .= ' ('. theme('links', $terms) .')
    '; } if ($teaser && $node->teaser) { $output .= $node->teaser; } else { $output .= $node->body; } if ($node->links) { $output .= ''; } if (!$node->status) { $output .= '
    '; } return $output; } /** * 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)) { $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"); } // 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; } // 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(array('tableSelect' => array('selectAll' => t('Select all rows in this table'), 'selectNone' => t('Deselect all rows in this table'))), 'setting'); 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 block. * * You can style your blocks by defining .block (all blocks), * .block-module (all blocks of module module), and * \#block-module-delta (specific block of module module * with delta delta) in your theme's CSS. * * @param $block * An object populated with fields from the "blocks" database table * ($block->module, $block->delta ...) and fields returned by * module_block('view') ($block->subject, $block->content, ...). * @return * A string containing the block output. */ function theme_block($block) { $output = "
    module\" id=\"block-$block->module-$block->delta\">\n"; $output .= "

    $block->subject

    \n"; $output .= "
    $block->content
    \n"; $output .= "
    \n"; 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 $type * The type of list to return (e.g. "ul", "ol") * @param $attributes * The attributes applied to the list element. * @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) . '>'; foreach ($items as $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 } $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. */ function theme_feed_icon($url) { if ($image = theme('image', 'misc/feed.png', t('Syndicate content'), t('Syndicate content'))) { return ''. $image. ''; } } /** * 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 ($object->homepage) { $output = l($object->name, $object->homepage); } else { $output = check_plain($object->name); } $output .= ' ('. t('not verified') .')'; } else { $output = variable_get('anonymous', t('Anonymous')); } return $output; } function theme_progress_bar($percent, $message) { $output = '
    '; $output .= '
    '. $percent .'%
    '; $output .= '
    '. $message .'
    '; $output .= '
    '; $output .= '
    '; return $output; } /** * @} End of "defgroup themeable". */ function _theme_table_cell($cell, $header = FALSE) { $attributes = ''; if (is_array($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; }