Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.973 diff -u -r1.973 node.module --- modules/node/node.module 31 Aug 2008 15:50:35 -0000 1.973 +++ modules/node/node.module 6 Sep 2008 03:14:12 -0000 @@ -1404,7 +1404,11 @@ $row[] = drupal_render($form['factors'][$key]); $rows[] = $row; } - $output .= theme('table', $header, $rows); + $table = array( + '#header' => $header, + '#rows' => $rows, + ) + _element_info('table'); + $output .= theme('table', $table); $output .= drupal_render($form); return $output; @@ -1795,17 +1799,17 @@ function node_page_default() { $result = pager_query(db_rewrite_sql('SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), variable_get('default_nodes_main', 10)); - $output = ''; + $output = array(); $num_rows = FALSE; while ($node = db_fetch_object($result)) { - $output .= node_view(node_load($node->nid), 1); + $output[] = array('#markup' => node_view(node_load($node->nid), 1)); $num_rows = TRUE; } if ($num_rows) { $feed_url = url('rss.xml', array('absolute' => TRUE)); drupal_add_feed($feed_url, variable_get('site_name', 'Drupal') . ' ' . t('RSS')); - $output .= theme('pager', NULL, variable_get('default_nodes_main', 10)); + $output[] = array('#markup' => theme('pager', NULL, variable_get('default_nodes_main', 10))); } else { $default_message = '
' . t('For more information, please refer to the help section, or the online Drupal handbooks. You may also post at the Drupal forum, or view the wide range of other support options available.', array('@help' => url('admin/help'), '@handbook' => 'http://drupal.org/handbooks', '@forum' => 'http://drupal.org/forum', '@support' => 'http://drupal.org/support')) . '
'; - $output = '' . t('Most unusual characters can be directly entered without any problems.') . '
'; $output .= '' . t('If you do encounter problems, try using HTML character entities. A common example looks like & for an ampersand & character. For a full list of entities see HTML\'s entities page. Some of the available characters include:', array('@html-entities' => 'http://www.w3.org/TR/html4/sgml/entities.html')) . '
'; @@ -253,7 +257,11 @@ array('data' => $entity[1], 'class' => 'get') ); } - $output .= theme('table', $header, $rows); + $table = array( + '#header' => $header, + '#rows' => $rows, + ) + _element_info('table'); + $output .= theme('table', $table); return $output; } } Index: modules/filter/filter.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.admin.inc,v retrieving revision 1.13 diff -u -r1.13 filter.admin.inc --- modules/filter/filter.admin.inc 24 Jul 2008 16:27:51 -0000 1.13 +++ modules/filter/filter.admin.inc 6 Sep 2008 03:14:12 -0000 @@ -80,8 +80,12 @@ unset($form[$id]); } } - $header = array(t('Name'), t('Roles'), t('Default'), t('Weight'), array('data' => t('Operations'), 'colspan' => 2)); - $output = theme('table', $header, $rows, array('id' => 'input-format-order')); + $table = array( + '#header' => array(t('Name'), t('Roles'), t('Default'), t('Weight'), array('data' => t('Operations'), 'colspan' => 2)), + '#rows' => $rows, + '#attributes' => array('id' => 'input-format-order'), + ) + _element_info('table'); + $output = theme('table', $table); $output .= drupal_render($form); drupal_add_tabledrag('input-format-order', 'order', 'sibling', 'input-format-order-weight'); @@ -375,7 +379,6 @@ * @ingroup themeable */ function theme_filter_admin_order($form) { - $header = array(t('Name'), t('Weight')); $rows = array(); foreach (element_children($form['names']) as $id) { // Don't take form control structures. @@ -387,8 +390,12 @@ ); } } - - $output = theme('table', $header, $rows, array('id' => 'filter-order')); + $table = array( + '#header' => array(t('Name'), t('Weight')), + '#rows' => $rows, + '#attributes' => array('id' => 'filter-order'), + ) + _element_info('table'); + $output = theme('table', $table); $output .= drupal_render($form); drupal_add_tabledrag('filter-order', 'order', 'sibling', 'filter-order-weight', NULL, NULL, FALSE); Index: modules/openid/openid.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/openid/openid.pages.inc,v retrieving revision 1.6 diff -u -r1.6 openid.pages.inc --- modules/openid/openid.pages.inc 14 Apr 2008 17:48:38 -0000 1.6 +++ modules/openid/openid.pages.inc 6 Sep 2008 03:14:12 -0000 @@ -47,8 +47,12 @@ $rows[] = array($identity->authname, l(t('Delete'), 'user/' . $account->uid . '/openid/delete/' . $identity->aid)); } - $output = theme('table', $header, $rows); - $output .= drupal_get_form('openid_user_add'); + $output['accounts'] = array( + '#type' => 'table', + '#header' => $header, + '#rows' => $rows, + ); + $output['openid_user_add'] = drupal_get_form('openid_user_add'); return $output; } Index: modules/comment/comment.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.admin.inc,v retrieving revision 1.10 diff -u -r1.10 comment.admin.inc --- modules/comment/comment.admin.inc 16 Jul 2008 21:59:26 -0000 1.10 +++ modules/comment/comment.admin.inc 6 Sep 2008 03:14:12 -0000 @@ -167,8 +167,12 @@ else { $rows[] = array(array('data' => t('No comments available.'), 'colspan' => '6')); } + $table = array( + '#header' => $form['header']['#value'], + '#rows' => $rows, + ) + _element_info('table'); + $output .= theme('table', $table); - $output .= theme('table', $form['header']['#value'], $rows); if ($form['pager']['#markup']) { $output .= drupal_render($form['pager']); } Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.647 diff -u -r1.647 comment.module --- modules/comment/comment.module 21 Aug 2008 19:36:36 -0000 1.647 +++ modules/comment/comment.module 6 Sep 2008 03:14:12 -0000 @@ -981,7 +981,11 @@ $comments .= ''; } $output .= $comments; - $output .= theme('pager', NULL, $comments_per_page, 0); + $element = array( + '#type' => 'pager', + '#limit' => $comments_per_page, + ) + _element_info('pager'); + $output .= theme('pager', $element); } // If enabled, show new comment form if it's not already being displayed. @@ -1408,7 +1412,7 @@ * The form title. */ function comment_form_box($edit, $title = NULL) { - return theme('box', $title, drupal_get_form('comment_form', $edit, $title)); + return theme('box', $title, drupal_render(drupal_get_form('comment_form', $edit, $title))); } /** Index: includes/theme.maintenance.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/theme.maintenance.inc,v retrieving revision 1.16 diff -u -r1.16 theme.maintenance.inc --- includes/theme.maintenance.inc 21 Aug 2008 19:36:36 -0000 1.16 +++ includes/theme.maintenance.inc 6 Sep 2008 03:14:12 -0000 @@ -164,7 +164,7 @@ drupal_set_header('Content-Type: text/html; charset=utf-8'); // Assign content and show message flag. - $variables['content'] = $content; + $variables['elements']['content'] = array('#value' => $content); $variables['show_messages'] = $show_messages; // The maintenance preprocess function is recycled here. template_preprocess_maintenance_page($variables); @@ -239,6 +239,8 @@ $head_title[] = variable_get('site_slogan', ''); } } + + $variables['content'] = &$variables['elements']['content']); $variables['head_title'] = implode(' | ', $head_title); $variables['base_path'] = base_path(); $variables['front_page'] = url(); Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.788 diff -u -r1.788 common.inc --- includes/common.inc 21 Aug 2008 19:36:36 -0000 1.788 +++ includes/common.inc 6 Sep 2008 03:14:12 -0000 @@ -47,20 +47,18 @@ * @param $region * A specified region to fetch content for. If NULL, all regions will be * returned. - * @param $delimiter - * Content to be inserted between exploded array elements. */ -function drupal_get_content($region = NULL, $delimiter = ' ') { +function drupal_get_content($region = NULL) { $content = drupal_set_content(); if (isset($region)) { if (isset($content[$region]) && is_array($content[$region])) { - return implode($delimiter, $content[$region]); + return $content[$region]; } } else { foreach (array_keys($content) as $region) { - if (is_array($content[$region])) { - $content[$region] = implode($delimiter, $content[$region]); + if (!is_array($content[$region])) { + $content[$region] = array(); } } return $content; @@ -327,8 +325,15 @@ drupal_maintenance_theme(); drupal_set_header('HTTP/1.1 503 Service unavailable'); drupal_set_title(t('Site offline')); - print theme('maintenance_page', filter_xss_admin(variable_get('site_offline_message', - t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))); + + $elements['page'] = array( + '#title' => drupal_get_title(), + '#theme' => 'maintenance_page', + '#content' => array( + '#markup' => filter_xss_admin(variable_get('site_offline_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))), + ), + ); + return $elements; } /** @@ -354,11 +359,11 @@ if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) { drupal_set_title(t('Page not found')); - $return = t('The requested page could not be found.'); + $return = array('#markup' => t('The requested page could not be found.')); } // To conserve CPU and bandwidth, omit the blocks. - print theme('page', $return, FALSE); + return drupal_build_page($return, FALSE, TRUE, FALSE); } /** @@ -383,9 +388,122 @@ if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) { drupal_set_title(t('Access denied')); - $return = t('You are not authorized to access this page.'); + $return = array('#markup' => t('You are not authorized to access this page.')); + } + return drupal_build_page($return, TRUE, TRUE, FALSE); +} + +/** + * Build a structured array with page content and theme parameters. + */ +function drupal_build_page($content, $show_blocks = TRUE, $show_messages = TRUE, $success = TRUE) { + if (!is_array($content)) { + $content = array('#markup' => $content); } - print theme('page', $return); + $elements = array( + '#title' => drupal_get_title(), + '#theme' => 'page', + '#show_blocks' => $show_blocks, + '#show_messages' => $show_messages, + '#success' => $success, + 'content' => $content, + ); + + global $theme; + + $regions = system_region_list($theme); + // Load all region content assigned via blocks. + foreach (array_keys($regions) as $region) { + if (!isset($elements[$region])) { + $elements[$region] = array(); + } + // Prevent left and right regions from rendering blocks when 'show_blocks' == FALSE. + if (!(!$show_blocks && ($region == 'left' || $region == 'right'))) { + $elements[$region]['blocks'] = array('#markup' => theme('blocks', $region)); + } + } + + // Alter any part of the page before it's rendered. + drupal_alter('page', $elements); + return page_elements_builder($elements); +} + +/** + * Adds any required properties non-form elements on the page. + * + * Similar to form_builder(). + */ +function page_elements_builder($elements) { + // Use element defaults. + if ((!empty($elements['#type'])) && ($info = _element_info($elements['#type']))) { + // Overlay $info onto $elements, retaining preexisting keys. + $elements += $info; + } + + // We start off assuming all elements are in the correct order. + $elements['#sorted'] = TRUE; + + // Recurse through all child elements. + $count = 0; + foreach (element_children($elements) as $key) { + // Assign a decimal placeholder weight to preserve original array order. + if (!isset($form[$key]['#weight'])) { + $elements[$key]['#weight'] = $count/1000; + } + else { + // If one of the child elements has a weight then we will need to sort + // later. + unset($elements['#sorted']); + } + if (empty($elements[$key]['#type']) || $elements[$key]['#type'] != 'form') { + $elements[$key] = page_elements_builder($elements[$key]); + } + $count++; + } + return $elements; +} + +function drupal_render_page($elements) { + $render_type = ''; + if (isset($_GET['render'])) { + $render_type = $_GET['render']; + } + // jQuery sets a HTTP_X_REQUESTED_WITH header of 'XMLHttpRequest'. + if (!$render_type) { + // Set the render type based on the request header. + if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') { + $render_type = 'json'; + } + else { + $render_type = 'xhtml'; + } + } + switch ($render_type) { + case 'xhtml': + case 'html': + $render = 'drupal_render'; + break; + + default: + $render = 'drupal_' . $render_type . '_render'; + if (!drupal_function_exists($render)) { + $render = 'drupal_render'; + } + break; + } + + return $render($elements); +} + +/** + * Render the elements into simple XML. + */ +function drupal_xml_render($elements) { + return drupal_render($elements, 'theme_xml'); +} + +function render(&$element) { + echo drupal_render($element); } /** @@ -2723,10 +2841,13 @@ * * @param $elements * The structured array describing the data to be rendered. + * @param $callback + * Optional parameter defining which function to use to mediate conversion of + * each elemement to a string. Use the default, 'theme', for XHTML. * @return - * The rendered HTML. + * The rendered content (XHTML or other depending on $callback). */ -function drupal_render(&$elements) { +function drupal_render(&$elements, $callback = 'theme') { if (!isset($elements) || (isset($elements['#access']) && !$elements['#access'])) { return NULL; } @@ -2745,7 +2866,7 @@ if (isset($elements['#pre_render'])) { foreach ($elements['#pre_render'] as $function) { if (drupal_function_exists($function)) { - $elements = $function($elements); + $elements = $function($elements, $callback); } } } @@ -2776,7 +2897,7 @@ } unset($elements['#prefix'], $elements['#suffix']); - $content = theme($elements['#theme'], $elements); + $content = $callback($elements['#theme'], $elements); foreach (array('#type', '#prefix', '#suffix') as $key) { $elements[$key] = isset($previous[$key]) ? $previous[$key] : NULL; @@ -2785,7 +2906,7 @@ // Render each of the children using drupal_render and concatenate them. if (!isset($content) || $content === '') { foreach ($children as $key) { - $content .= drupal_render($elements[$key]); + $content .= drupal_render($elements[$key], $callback); } } } @@ -2795,7 +2916,8 @@ // Until now, we rendered the children, here we render the element itself if (!isset($elements['#printed'])) { - $content = theme(!empty($elements['#type']) ? $elements['#type'] : 'markup', $elements); + // The default $callback is 'theme'. + $content = $callback(!empty($elements['#type']) ? $elements['#type'] : 'markup', $elements); $elements['#printed'] = TRUE; } @@ -2806,7 +2928,7 @@ if (isset($elements['#post_render'])) { foreach ($elements['#post_render'] as $function) { if (drupal_function_exists($function)) { - $content = $function($content, $elements); + $content = $function($content, $elements, $callback); } } } @@ -2816,6 +2938,139 @@ } } + +/** + * Renders elements of structured array tree, retaining the structure. + * + * Recursively iterates over each of the array elements, generating output. + * This function is usually called from within a another function. + * + * @param $elements + * The structured array describing the data to be rendered. + * @param $callback + * Optional parameter defining which funciton to use to mediate conversion of + * each elemement to a string. Use the default, 'theme', for HTML. + * @return + * The rendered HTML. + */ +function drupal_render_array(&$elements, $callback = 'theme') { + if (!isset($elements) || (isset($elements['#access']) && !$elements['#access'])) { + $elements = NULL; + } + + // If the default values for this element haven't been loaded yet, populate + // them. + if (!isset($elements['#defaults_loaded']) || !$elements['#defaults_loaded']) { + if ((!empty($elements['#type'])) && ($info = _element_info($elements['#type']))) { + $elements += $info; + } + } + + // Make any final changes to the element before it is rendered. This means + // that the $element or the children can be altered or corrected before the + // element is rendered into the final text. + if (isset($elements['#pre_render'])) { + foreach ($elements['#pre_render'] as $function) { + if (drupal_function_exists($function)) { + $elements = $function($elements, $callback); + } + } + } + + $content = ''; + // Either the elements did not go through form_builder or one of the children + // has a #weight. + if (!isset($elements['#sorted'])) { + uasort($elements, "element_sort"); + } + $elements += array('#title' => NULL, '#description' => NULL); + if (!isset($elements['#children'])) { + $children = element_children($elements); + /* Render all the children that use a theme function */ + if (!$children && isset($elements['#theme']) && empty($elements['#theme_used'])) { + $elements['#theme_used'] = TRUE; + + $previous = array(); + foreach (array('#type', '#prefix', '#suffix') as $key) { + $previous[$key] = isset($elements[$key]) ? $elements[$key] : NULL; + } + // If we rendered a single element, then we will skip the renderer. + $elements['#printed'] = TRUE; + $elements['#type'] = 'markup'; + $elements['#value'] = ''; + + unset($elements['#prefix'], $elements['#suffix']); + + $args[] = $elements['#theme']; + $args[] = $elements; + if (!empty($elements['#theme arguments'])) { + foreach($elements['#theme arguments'] as $value) { + $args[] = $value; + } + } + // The default $callback is 'theme'. + $content = call_user_func_array($callback, $args); + + foreach (array('#type', '#prefix', '#suffix') as $key) { + $elements[$key] = isset($previous[$key]) ? $previous[$key] : NULL; + } + } + // Render each of the children using drupal_render_array + if (!isset($content) || $content === '') { + foreach ($children as $key) { + drupal_render_array($elements[$key], $callback); + } + } + } + + // Until now, we rendered the children, here we render the element itself + if (!isset($elements['#printed']) && !$children) { + // The default $callback is 'theme'. + $content = $callback(!empty($elements['#type']) ? $elements['#type'] : 'markup', $elements); + $elements['#printed'] = TRUE; + } + + if (isset($content) && $content !== '') { + // Filter the outputted content and make any last changes before the + // content is sent to the browser. The changes are made on $content + // which allows the output'ed text to be filtered. + if (isset($elements['#post_render'])) { + foreach ($elements['#post_render'] as $function) { + if (drupal_function_exists($function)) { + $content = $function($content, $elements, $callback); + } + } + } + $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : ''; + $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : ''; + $elements['#value'] = $prefix . $content . $suffix; + } +} + +function element_property_preserve_in_array($key) { + switch ($key) { + case '#title': + case '#value': + case '#type': + return TRUE; + + default: + return FALSE; + } +} + +function drupal_json_render($elements) { + drupal_set_header('Content-Type: text/javascript; charset=utf-8'); + drupal_render_array($elements); + return drupal_to_js($elements); +} + +function drupal_printr_render($elements) { + drupal_set_header('Content-Type: text/plain; charset=utf-8'); + drupal_render_array($elements); + return print_r($elements, 1); +} + /** * Function used by uasort to sort structured arrays by weight. */ @@ -2866,18 +3121,18 @@ 'arguments' => array('text' => NULL) ), 'page' => array( - 'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE), + 'arguments' => array('elements' => NULL), 'template' => 'page', ), 'maintenance_page' => array( - 'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE), + 'arguments' => array('elements' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE), 'template' => 'maintenance-page', ), 'update_page' => array( - 'arguments' => array('content' => NULL, 'show_messages' => TRUE), + 'arguments' => array('elements' => NULL, 'show_messages' => TRUE), ), 'install_page' => array( - 'arguments' => array('content' => NULL), + 'arguments' => array('elements' => NULL), ), 'task_list' => array( 'arguments' => array('items' => NULL, 'active' => NULL), Index: includes/theme.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/theme.inc,v retrieving revision 1.433 diff -u -r1.433 theme.inc --- includes/theme.inc 2 Sep 2008 17:38:55 -0000 1.433 +++ includes/theme.inc 6 Sep 2008 03:14:12 -0000 @@ -479,6 +479,14 @@ } /** + * Dummy function for now. + */ +function theme_xml() { + $args = func_get_args(); + return call_user_func_array('theme', $args); +} + +/** * Generate the themed output. * * All requests for theme hooks must go through this function. It examines @@ -640,12 +648,9 @@ } 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 (drupal_function_exists($preprocess_function)) { - call_user_func_array($preprocess_function, $args); + $preprocess_function($variables, $hook); } } } @@ -1281,7 +1286,13 @@ * @return * An HTML string representing the table. */ -function theme_table($header, $rows, $attributes = array(), $caption = NULL, $colgroups = array()) { +function theme_table($element) { + + $header = $element['#header']; + $rows = $element['#rows']; + $attributes = $element['#attributes']; + $caption = $element['#caption']; + $colgroups = $element['#colgroups']; // Add sticky headers, if applicable. if (count($header)) { @@ -1481,7 +1492,12 @@ * @return * A string containing the list output. */ -function theme_item_list($items = array(), $title = NULL, $type = 'ul', $attributes = array()) { +function theme_item_list($list) { + $items = $list['#items']; + $title = $list['#title']; + $type = $list['#list_type']; + $attributes = $list['#attributes']; + $output = '