2 // $Id: theme.inc 144 2007-03-28 07:52:20Z thierry $
6 * The theme system, which controls the output of Drupal.
8 * The theme system allows for nearly all output of the Drupal system to be
9 * customized by user themes.
11 * @see <a href="http://drupal.org/node/253">Theme system</a>
16 * @name Content markers
18 * Markers used by theme_mark() and node_mark() to designate content.
19 * @see theme_mark(), node_mark()
21 define('MARK_READ', 0);
22 define('MARK_NEW', 1);
23 define('MARK_UPDATED', 2);
25 * @} End of "Content markers".
29 * Initialize the theme system by loading the theme.
32 function init_theme() {
33 global $theme, $user, $custom_theme, $theme_engine, $theme_key;
35 // If $theme is already set, assume the others are set, too, and do nothing
40 drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
41 $themes = list_themes();
43 // Only select the user selected theme if it is available in the
44 // list of enabled themes.
45 $theme = $user->theme && $themes[$user->theme]->status ? $user->theme : variable_get('theme_default', 'bluemarine');
47 // Allow modules to override the present theme... only select custom theme
48 // if it is available in the list of installed themes.
49 $theme = $custom_theme && $themes[$custom_theme] ? $custom_theme : $theme;
51 // Store the identifier for retrieving theme settings with.
54 // If we're using a style, load its appropriate theme,
55 // which is stored in the style's description field.
56 // Also load the stylesheet using theme_add_style().
57 // Otherwise, load the theme.
58 if (strpos($themes[$theme]->filename, '.css')) {
59 // File is a style; loads its CSS.
60 // Set theme to its template/theme
61 theme_add_style($themes[$theme]->filename);
62 $theme = basename(dirname($themes[$theme]->description));
65 // File is a template/theme
66 // Load its CSS, if it exists
67 if (file_exists($stylesheet = dirname($themes[$theme]->filename) .'/style.css')) {
68 theme_add_style($stylesheet);
72 if (strpos($themes[$theme]->filename, '.theme')) {
73 // file is a theme; include it
74 include_once './' . $themes[$theme]->filename;
76 elseif (strpos($themes[$theme]->description, '.engine')) {
77 // file is a template; include its engine
78 include_once './' . $themes[$theme]->description;
79 $theme_engine = basename($themes[$theme]->description, '.engine');
80 if (function_exists($theme_engine .'_init')) {
81 call_user_func($theme_engine .'_init', $themes[$theme]);
87 * Provides a list of currently available themes.
90 * Whether to reload the list of themes from the database.
92 * An array of the currently available themes.
94 function list_themes($refresh = FALSE) {
103 $result = db_query("SELECT * FROM {system} WHERE type = 'theme'");
104 while ($theme = db_fetch_object($result)) {
105 if (file_exists($theme->filename)) {
106 $list[$theme->name] = $theme;
115 * Provides a list of currently available theme engines
118 * Whether to reload the list of themes from the database.
120 * An array of the currently available theme engines.
122 function list_theme_engines($refresh = FALSE) {
131 $result = db_query("SELECT * FROM {system} WHERE type = 'theme_engine' AND status = '1' ORDER BY name");
132 while ($engine = db_fetch_object($result)) {
133 if (file_exists($engine->filename)) {
134 $list[$engine->name] = $engine;
143 * Generate the themed representation of a Drupal object.
145 * All requests for themed functions must go through this function. It examines
146 * the request and routes it to the appropriate theme function. If the current
147 * theme does not implement the requested function, then the current theme
148 * engine is checked. If neither the engine nor theme implement the requested
149 * function, then the base theme function is called.
151 * For example, to retrieve the HTML that is output by theme_page($output), a
152 * module should call theme('page', $output).
155 * The name of the theme function to call.
157 * Additional arguments to pass along to the theme function.
159 * An HTML string that generates the themed output.
162 $args = func_get_args();
163 $function = array_shift($args);
165 if ($func = theme_get_function($function)) {
166 return call_user_func_array($func, $args);
171 * Determine if a theme function exists, and if so return which one was found.
174 * The name of the theme function to test.
176 * The name of the theme function that should be used, or false if no function exists.
178 function theme_get_function($function) {
179 global $theme, $theme_engine;
181 // Because theme() is called a lot, calling init_theme() only to have it
182 // smartly return is a noticeable performance hit. Don't do it.
183 if (!isset($theme)) {
187 if (($theme != '') && function_exists($theme .'_'. $function)) {
188 // call theme function
189 return $theme .'_'. $function;
191 elseif (($theme != '') && isset($theme_engine) && function_exists($theme_engine .'_'. $function)) {
192 // call engine function
193 return $theme_engine .'_'. $function;
195 elseif (function_exists('theme_'. $function)){
196 // call Drupal function
197 return 'theme_'. $function;
203 * Return the path to the currently selected theme.
205 function path_to_theme() {
208 $themes = list_themes();
210 return dirname($themes[$theme]->filename);
214 * Retrieve an associative array containing the settings for a theme.
216 * The final settings are arrived at by merging the default settings,
217 * the site-wide settings, and the settings defined for the specific theme.
218 * If no $key was specified, only the site-wide theme defaults are retrieved.
220 * The default values for each of settings are also defined in this function.
221 * To add new settings, add their default values here, and then add form elements
222 * to system_theme_settings() in system.module.
225 * The template/style value for a given theme.
228 * An associative array containing theme settings.
230 function theme_get_settings($key = NULL) {
235 'default_favicon' => 1,
236 'favicon_path' => '',
238 'toggle_favicon' => 1,
240 'toggle_search' => 1,
241 'toggle_slogan' => 0,
242 'toggle_mission' => 1,
243 'toggle_node_user_picture' => 0,
244 'toggle_comment_user_picture' => 0,
247 if (module_exist('node')) {
248 foreach (node_get_types() as $type => $name) {
249 $defaults['toggle_node_info_' . $type] = 1;
252 $settings = array_merge($defaults, variable_get('theme_settings', array()));
255 $settings = array_merge($settings, variable_get(str_replace('/', '_', 'theme_'. $key .'_settings'), array()));
258 // Only offer search box if search.module is enabled.
259 if (!module_exist('search') || !user_access('search content')) {
260 $settings['toggle_search'] = 0;
267 * Retrieve a setting for the current theme.
268 * This function is designed for use from within themes & engines
269 * to determine theme settings made in the admin interface.
271 * Caches values for speed (use $refresh = TRUE to refresh cache)
273 * @param $setting_name
274 * The name of the setting to be retrieved.
277 * Whether to reload the cache of settings.
280 * The value of the requested setting, NULL if the setting does not exist.
282 function theme_get_setting($setting_name, $refresh = FALSE) {
286 if (empty($settings) || $refresh) {
287 $settings = theme_get_settings($theme_key);
289 $themes = list_themes();
290 $theme_object = $themes[$theme_key];
292 if ($settings['mission'] == '') {
293 $settings['mission'] = variable_get('site_mission', '');
296 if (!$settings['toggle_mission']) {
297 $settings['mission'] = '';
300 if ($settings['toggle_logo']) {
301 if ($settings['default_logo']) {
302 $settings['logo'] = base_path() . dirname($theme_object->filename) .'/logo.png';
304 elseif ($settings['logo_path']) {
305 $settings['logo'] = base_path() . $settings['logo_path'];
309 if ($settings['toggle_favicon']) {
310 if ($settings['default_favicon']) {
311 if (file_exists($favicon = dirname($theme_object->filename) .'/favicon.ico')) {
312 $settings['favicon'] = base_path() . $favicon;
315 $settings['favicon'] = base_path() . 'misc/favicon.ico';
318 elseif ($settings['favicon_path']) {
319 $settings['favicon'] = base_path() . $settings['favicon_path'];
324 return isset($settings[$setting_name]) ? $settings[$setting_name] : NULL;
328 * Add a theme stylesheet to be included later. This is handled separately from
329 * drupal_set_html_head() to enforce the correct CSS cascading order.
331 function theme_add_style($path = '', $media = 'all') {
332 static $styles = array();
334 if ($path && !isset($styles["$media:$path"])) {
335 $style = new stdClass();
336 $style->path = base_path() . $path;
337 $style->media = $media;
338 $styles["$media:$path"] = $style;
344 * Return the HTML for a theme's stylesheets.
346 function theme_get_styles() {
348 foreach (theme_add_style() as $style) {
349 $output .= theme('stylesheet_import', $style->path, $style->media);
355 * @defgroup themeable Themeable functions
357 * Functions that display HTML, and which can be customized by themes.
359 * All functions that produce HTML for display should be themeable. This means
360 * that they should be named with the theme_ prefix, and invoked using theme()
361 * rather than being called directly. This allows themes to override the display
362 * of any Drupal object.
364 * The theme system is described and defined in theme.inc.
368 * Format a dynamic text string for emphasized display in a placeholder.
370 * E.g. t('Added term %term', array('%term' => theme('placeholder', $term)))
373 * The text to format (plain-text).
375 * The formatted text (html).
377 function theme_placeholder($text) {
378 return '<em>'. check_plain($text) .'</em>';
382 * Return an entire Drupal page displaying the supplied content.
385 * A string to display in the main content area of the page.
387 * A string containing the entire HTML page.
389 function theme_page($content) {
390 $output = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
391 $output .= '<html xmlns="http://www.w3.org/1999/xhtml">';
393 $output .= ' <title>'. (drupal_get_title() ? strip_tags(drupal_get_title()) : variable_get('site_name', 'drupal')) .'</title>';
394 $output .= drupal_get_html_head();
395 $output .= theme_get_styles();
397 $output .= ' </head>';
398 $output .= ' <body style="background-color: #fff; color: #000;">';
399 $output .= '<table border="0" cellspacing="4" cellpadding="4"><tr><td style="vertical-align: top; width: 170px;">';
401 $output .= theme('blocks', 'all');
402 $output .= '</td><td style="vertical-align: top;">';
404 $output .= theme('breadcrumb', drupal_get_breadcrumb());
405 $output .= '<h1>' . drupal_get_title() . '</h1>';
407 if ($tabs = theme('menu_local_tasks')) {
411 $output .= theme('help');
413 $output .= theme('status_messages');
415 $output .= "\n<!-- begin content -->\n";
417 $output .= "\n<!-- end content -->\n";
419 $output .= '</td></tr></table>';
420 $output .= theme_closure();
421 $output .= '</body></html>';
426 function theme_maintenance_page($content, $messages = TRUE, $partial = FALSE) {
427 drupal_set_header('Content-Type: text/html; charset=utf-8');
428 theme('add_style', 'misc/maintenance.css');
429 drupal_set_html_head('<link rel="shortcut icon" href="'. base_path() .'misc/favicon.ico" type="image/x-icon" />');
431 $output = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
432 $output .= '<html xmlns="http://www.w3.org/1999/xhtml">';
434 $output .= ' <title>'. drupal_get_title() .'</title>';
435 $output .= drupal_get_html_head();
436 $output .= theme_get_styles();
437 $output .= '</head>';
439 $output .= '<h1>' . drupal_get_title() . '</h1>';
442 $output .= theme('status_messages');
445 $output .= "\n<!-- begin content -->\n";
447 $output .= "\n<!-- end content -->\n";
450 $output .= '</body></html>';
457 * Returns themed set of status and/or error messages. The messages are grouped
461 * A string containing the messages.
463 function theme_status_messages() {
464 if ($data = drupal_get_messages()) {
466 foreach ($data as $type => $messages) {
467 $output .= "<div class=\"messages $type\">\n";
468 if (count($messages) > 1) {
469 $output .= " <ul>\n";
470 foreach($messages as $message) {
471 $output .= ' <li>'. $message ."</li>\n";
473 $output .= " </ul>\n";
476 $output .= $messages[0];
478 $output .= "</div>\n";
486 * Return a themed set of links.
489 * An array of links to be themed.
491 * A string used to separate the links.
493 * A string containing the themed links.
495 function theme_links($links, $delimiter = ' | ') {
496 if (!is_array($links)) {
499 return implode($delimiter, $links);
503 * Return a themed image.
506 * The path of the image file.
508 * The alternative text for text-based browsers.
510 * The title text is displayed when the image is hovered in some popular browsers.
512 * Associative array of attributes to be placed in the img tag.
514 * If set to true, the image's dimension are fetched and added as width/height attributes.
516 * A string containing the image tag.
518 function theme_image($path, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE) {
519 if (!$getsize || (is_file($path) && (list($width, $height, $type, $image_attributes) = @getimagesize($path)))) {
520 $attributes = drupal_attributes($attributes);
521 return '<img src="'. check_url(base_path() . $path) .'" alt="'. check_plain($alt) .'" title="'. check_plain($title) .'" '. $image_attributes . $attributes .' />';
526 * Return a themed breadcrumb trail.
529 * An array containing the breadcrumb links.
530 * @return a string containing the breadcrumb output.
532 function theme_breadcrumb($breadcrumb) {
533 if (!empty($breadcrumb)) {
534 return '<div class="breadcrumb">'. implode(' » ', $breadcrumb) .'</div>';
539 * Return a themed help message.
541 * @return a string containing the helptext for the current page.
543 function theme_help() {
544 if ($help = menu_get_active_help()) {
545 return '<div class="help">'. $help .'</div>';
550 * Return a themed node.
553 * An object providing all relevant information for displaying a node:
554 * - $node->nid: The ID of the node.
555 * - $node->type: The content type (story, blog, forum...).
556 * - $node->title: The title of the node.
557 * - $node->created: The creation date, as a UNIX timestamp.
558 * - $node->teaser: A shortened version of the node body.
559 * - $node->body: The entire node contents.
560 * - $node->changed: The last modification date, as a UNIX timestamp.
561 * - $node->uid: The ID of the author.
562 * - $node->username: The username of the author.
564 * Whether to display the teaser only, as on the main page.
566 * Whether to display the node as a standalone page. If TRUE, do not display
567 * the title because it will be provided by the menu system.
569 * A string containing the node output.
571 function theme_node($node, $teaser = FALSE, $page = FALSE) {
572 if (!$node->status) {
573 $output = '<div class="node-unpublished">';
576 if (module_exist('taxonomy')) {
577 $terms = taxonomy_link('taxonomy terms', $node);
581 $output .= t('%title by %name', array('%title' => '<h2 class="title">'. check_plain($node->title) .'</h2>', '%name' => theme('username', $node)));
584 $output .= t('by %name', array('%name' => theme('username', $node)));
588 $output .= ' <small>('. theme('links', $terms) .')</small><br />';
591 if ($teaser && $node->teaser) {
592 $output .= $node->teaser;
595 $output .= $node->body;
599 $output .= '<div class="links">'. theme('links', $node->links) .'</div>';
602 if (!$node->status) {
610 * Return a themed form element.
612 * @param $title the form element's title
613 * @param $value the form element's data
614 * @param $description the form element's description or explanation
615 * @param $id the form element's ID used by the <label> tag
616 * @param $required a boolean to indicate whether this is a required field or not
617 * @param $error a string with an error message filed against this form element
619 * @return a string representing the form element
621 function theme_form_element($title, $value, $description = NULL, $id = NULL, $required = FALSE, $error = FALSE) {
623 $output = '<div class="form-item">'."\n";
624 $required = $required ? '<span class="form-required" title="'. t('This field is required.') .'">*</span>' : '';
628 $output .= ' <label for="'. form_clean_id($id) .'">'. t('%title: %required', array('%title' => $title, '%required' => $required)) . "</label>\n";
631 $output .= ' <label>'. t('%title: %required', array('%title' => $title, '%required' => $required)) . "</label>\n";
635 $output .= " $value\n";
638 $output .= ' <div class="description">'. $description ."</div>\n";
641 $output .= "</div>\n";
647 * Return a themed submenu, typically displayed under the tabs.
652 function theme_submenu($links) {
653 return '<div class="submenu">'. implode(' | ', $links) .'</div>';
657 * Return a themed table.
660 * An array containing the table headers. Each element of the array can be
661 * either a localized string or an associative array with the following keys:
662 * - "data": The localized title of the table column.
663 * - "field": The database field represented in the table column (required if
664 * user is to be able to sort on this column).
665 * - "sort": A default sort order for this column ("asc" or "desc").
666 * - Any HTML attributes, such as "colspan", to apply to the column header cell.
668 * An array of table rows. Every row is an array of cells, or an associative
669 * array with the following keys:
670 * - "data": an array of cells
671 * - Any HTML attributes, such as "class", to apply to the table row.
673 * Each cell can be either a string or an associative array with the following keys:
674 * - "data": The string to display in the table cell.
675 * - Any HTML attributes, such as "colspan", to apply to the table cell.
677 * Here's an example for $rows:
682 * 'Cell 1', 'Cell 2', 'Cell 3'
684 * // Row with attributes on the row and some of its cells.
686 * 'data' => array('Cell 1', array('data' => 'Cell 2', 'colspan' => 2)), 'class' => 'funky'
692 * An array of HTML attributes to apply to the table tag.
694 * A localized string to use for the <caption> tag.
696 * An HTML string representing the table.
698 function theme_table($header, $rows, $attributes = array(), $caption = NULL) {
699 $output = '<table'. drupal_attributes($attributes) .">\n";
701 if (isset($caption)) {
702 $output .= '<caption>'. $caption ."</caption>\n";
705 // Format the table header:
706 if (count($header)) {
707 $ts = tablesort_init($header);
708 $output .= ' <thead><tr>';
709 foreach ($header as $cell) {
710 $cell = tablesort_header($cell, $header, $ts);
711 $output .= _theme_table_cell($cell, 1);
713 $output .= " </tr></thead>\n";
716 // Format the table rows:
717 $output .= "<tbody>\n";
719 foreach ($rows as $number => $row) {
720 $attributes = array();
722 // Check if we're dealing with a simple or complex row
723 if (isset($row['data'])) {
724 foreach ($row as $key => $value) {
725 if ($key == 'data') {
729 $attributes[$key] = $value;
737 // Add odd/even class
738 $class = ($number % 2 == 1) ? 'even': 'odd';
739 if (isset($attributes['class'])) {
740 $attributes['class'] .= ' '. $class;
743 $attributes['class'] = $class;
747 $output .= ' <tr'. drupal_attributes($attributes) .'>';
749 foreach ($cells as $cell) {
750 $cell = tablesort_cell($cell, $header, $ts, $i++);
751 $output .= _theme_table_cell($cell, 0);
753 $output .= " </tr>\n";
757 $output .= "</tbody></table>\n";
762 * Return a themed sort icon.
765 * Set to either asc or desc. This sets which icon to show.
767 * A themed sort icon.
769 function theme_tablesort_indicator($style) {
770 if ($style == "asc"){
771 return theme('image', 'misc/arrow-asc.png', t('sort icon'), t('sort ascending'));
774 return theme('image', 'misc/arrow-desc.png', t('sort icon'), t('sort descending'));
779 * Return a themed box.
782 * The subject of the box.
784 * The content of the box.
786 * The region in which the box is displayed.
788 * A string containing the box output.
790 function theme_box($title, $content, $region = 'main') {
791 $output = '<h2 class="title">'. $title .'</h2><div>'. $content .'</div>';
796 * Return a themed block.
798 * You can style your blocks by defining .block (all blocks),
799 * .block-<i>module</i> (all blocks of module <i>module</i>), and
800 * \#block-<i>module</i>-<i>delta</i> (specific block of module <i>module</i>
801 * with delta <i>delta</i>) in your theme's CSS.
804 * An object populated with fields from the "blocks" database table
805 * ($block->module, $block->delta ...) and fields returned by
806 * <i>module</i>_block('view') ($block->subject, $block->content, ...).
808 * A string containing the block output.
810 function theme_block($block) {
811 $output = "<div class=\"block block-$block->module\" id=\"block-$block->module-$block->delta\">\n";
812 $output .= " <h2 class=\"title\">$block->subject</h2>\n";
813 $output .= " <div class=\"content\">$block->content</div>\n";
814 $output .= "</div>\n";
819 * Return a themed marker, useful for marking new or updated
823 * Number representing the marker type to display
824 * @see MARK_NEW, MARK_UPDATED, MARK_READ
826 * A string containing the marker.
828 function theme_mark($type = MARK_NEW) {
831 if ($type == MARK_NEW) {
832 return ' <span class="marker">'. t('new') .'</span>';
834 else if ($type == MARK_UPDATED) {
835 return ' <span class="marker">'. t('updated') .'</span>';
841 * Import a stylesheet using @import.
844 * The path to the stylesheet.
847 * The media type to specify for the stylesheet
850 * A string containing the HTML for the stylesheet import.
852 function theme_stylesheet_import($path, $media = 'all') {
853 return '<style type="text/css" media="'. $media .'">@import "'. $path .'";</style>';
857 * Return a themed list of items.
860 * An array of items to be displayed in the list.
862 * The title of the list.
864 * The type of list to return (e.g. "ul", "ol")
866 * A string containing the list output.
868 function theme_item_list($items = array(), $title = NULL, $type = 'ul') {
869 $output = '<div class="item-list">';
871 $output .= '<h3>'. $title .'</h3>';
874 if (!empty($items)) {
875 $output .= "<$type>";
876 foreach ($items as $item) {
877 $output .= '<li>'. $item .'</li>';
879 $output .= "</$type>";
886 * Returns code that emits the 'more help'-link.
888 function theme_more_help_link($url) {
889 return '<div class="more-help-link">' . t('[<a href="%link">more help...</a>]', array('%link' => check_url($url))) . '</div>';
893 * Return code that emits an XML icon.
895 function theme_xml_icon($url) {
896 if ($image = theme('image', 'misc/xml.png', t('XML feed'), t('XML feed'))) {
897 return '<a href="'. check_url($url) .'" class="xml-icon">'. $image. '</a>';
902 * Return code that emits an feed icon.
904 function theme_feed_icon($url) {
905 if ($image = theme('image', 'misc/feed.png', t('Syndicate content'), t('Syndicate content'))) {
906 return '<a href="'. check_url($url) .'" class="feed-icon">'. $image. '</a>';
911 * Execute hook_footer() which is run at the end of the page right before the
912 * close of the body tag.
914 * @param $main (optional)
917 * A string containing the results of the hook_footer() calls.
919 function theme_closure($main = 0) {
920 $footer = module_invoke_all('footer', $main);
921 return implode("\n", $footer);
925 * Return a set of blocks available for the current user.
928 * Which set of blocks to retrieve.
930 * A string containing the themed blocks for this region.
932 function theme_blocks($region) {
935 if ($list = block_list($region)) {
936 foreach ($list as $key => $block) {
937 // $key == <i>module</i>_<i>delta</i>
938 $output .= theme('block', $block);
942 // Add any content assigned to this region through drupal_set_content() calls.
943 $output .= drupal_get_content($region);
952 * The user object to format, usually returned from user_load().
954 * A string containing an HTML link to the user's page if the passed object
955 * suggests that this is a site user. Otherwise, only the username is returned.
957 function theme_username($object) {
959 if ($object->uid && $object->name) {
960 // Shorten the name when it is too long or it will break many tables.
961 if (drupal_strlen($object->name) > 20) {
962 $name = drupal_substr($object->name, 0, 15) .'...';
965 $name = $object->name;
968 if (user_access('access user profiles')) {
969 $output = l($name, 'user/'. $object->uid, array('title' => t('View user profile.')));
972 $output = check_plain($name);
975 else if ($object->name) {
976 // Sometimes modules display content composed by people who are
977 // not registered members of the site (e.g. mailing list or news
978 // aggregator modules). This clause enables modules to display
979 // the true author of the content.
980 if ($object->homepage) {
981 $output = l($object->name, $object->homepage);
984 $output = check_plain($object->name);
987 $output .= ' ('. t('not verified') .')';
990 $output = variable_get('anonymous', 'Anonymous');
996 function theme_progress_bar($percent, $message) {
997 $output = '<div id="progress" class="progress">';
998 $output .= '<div class="percentage">'. $percent .'%</div>';
999 $output .= '<div class="status">'. $message .'</div>';
1000 $output .= '<div class="bar"><div class="filled" style="width: '. $percent .'%"></div></div>';
1001 $output .= '</div>';
1007 * @} End of "defgroup themeable".
1010 function _theme_table_cell($cell, $header = 0) {
1013 if (is_array($cell)) {
1014 $data = $cell['data'];
1015 foreach ($cell as $key => $value) {
1016 if ($key != 'data') {
1017 $attributes .= " $key=\"$value\"";
1026 $output = "<th$attributes>$data</th>";
1029 $output = "<td$attributes>$data</td>";