Index: index.php
===================================================================
RCS file: /cvs/drupal/drupal/index.php,v
retrieving revision 1.96
diff -u -F^f -p -r1.96 index.php
--- index.php	20 Sep 2008 20:22:23 -0000	1.96
+++ index.php	5 Jan 2009 21:02:51 -0000
@@ -21,7 +21,7 @@ require_once DRUPAL_ROOT . '/includes/bo
 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
 $return = menu_execute_active_handler();
 
-// Menu status constants are integers; page content is a string.
+// Menu status constants are integers; page content is a string or array.
 if (is_int($return)) {
   switch ($return) {
     case MENU_NOT_FOUND:
@@ -36,8 +36,8 @@ if (is_int($return)) {
   }
 }
 elseif (isset($return)) {
-  // Print any value (including an empty string) except NULL or undefined:
-  print theme('page', $return);
+  // Print anything besides a menu constant, assuming it's not NULL or undefined.
+  drupal_render_page($return);
 }
 
 drupal_page_footer();
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.841
diff -u -F^f -p -r1.841 common.inc
--- includes/common.inc	30 Dec 2008 16:43:14 -0000	1.841
+++ includes/common.inc	5 Jan 2009 21:02:52 -0000
@@ -301,8 +301,7 @@ function drupal_get_destination() {
  * Drupal will ensure that messages set by drupal_set_message() and other
  * session data are written to the database before the user is redirected.
  *
- * This function ends the request; use it rather than a print theme('page')
- * statement in your menu callback.
+ * This function ends the request; use it rather than a return in your menu callback.
  *
  * @param $path
  *   A Drupal path or a full URL.
@@ -377,19 +376,32 @@ function drupal_not_found() {
 
   $path = drupal_get_normal_path(variable_get('site_404', ''));
   if ($path && $path != $_GET['q']) {
-    // Set the active item in case there are tabs to display, or other
-    // dependencies on the path.
+    // Custom 404 handler. Set the active item in case there are tabs to
+    // display, or other dependencies on the path.
     menu_set_active_item($path);
     $return = menu_execute_active_handler($path);
   }
 
   if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
+    // Standard 404 handler.
     drupal_set_title(t('Page not found'));
     $return = t('The requested page could not be found.');
   }
+  
+  $page = drupal_get_page();
+  
+  // Since we disable blocks, we must normalize to an array.
+  if (is_string($return)) {
+    $body = array('#markup' => $return);
+    $page['content'] = $body;
+  }
+  else {
+    $page['content'] = $return;
+  }
 
   // To conserve CPU and bandwidth, omit the blocks.
-  print theme('page', $return, FALSE);
+  $page['#show_blocks'] = FALSE;
+  print drupal_render_page($page);
 }
 
 /**
@@ -406,17 +418,19 @@ function drupal_access_denied() {
 
   $path = drupal_get_normal_path(variable_get('site_403', ''));
   if ($path && $path != $_GET['q']) {
-    // Set the active item in case there are tabs to display or other
-    // dependencies on the path.
+    // Custom 403 handler. Set the active item in case there are tabs to 
+    // display or other dependencies on the path.
     menu_set_active_item($path);
     $return = menu_execute_active_handler($path);
   }
 
   if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
+    // Standard 403 handler.
     drupal_set_title(t('Access denied'));
     $return = t('You are not authorized to access this page.');
   }
-  print theme('page', $return);
+  
+  print drupal_render_page($return);
 }
 
 /**
@@ -821,7 +835,15 @@ function _drupal_log_error($error, $fata
     drupal_set_header($_SERVER['SERVER_PROTOCOL'] . ' Service unavailable');
     drupal_set_title(t('Error'));
     if (!defined('MAINTENANCE_MODE') && drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) {
-      print theme('page', t('The website encountered an unexpected error. Please try again later.'), FALSE);
+      // To conserve CPU and bandwidth, omit the blocks.
+      $page = drupal_get_page();
+      $page += array(
+        '#show_blocks' => FALSE,
+        'content' => array(
+          '#markup' => t('The website encountered an unexpected error. Please try again later.'),
+        ),
+      );
+      print drupal_render_page($page);
     }
     else {
       print theme('maintenance_page', t('The website encountered an unexpected error. Please try again later.'), FALSE);
@@ -3176,6 +3198,50 @@ function drupal_alter($type, &$data) {
 }
 
 /**
+ * Retrieve a bare $page element that is ready for decorating. 
+ *
+ * Used by menu callbacks in order to populate the page with content 
+ * and behavior (e.g. #show_blocks). Callers should append renderable
+ * arrays beneath 'content' element in order to add content to the page.
+ *
+ * @see drupal_render_page().
+ * 
+ * @return array
+ *   A $page element that should be decorated and then passed to drupal_render_page().
+ */
+function drupal_get_page() {
+  // Initialize page array with defaults. @see hook_elements() - 'page' element.
+  $page = _element_info('page');
+  $page['#type'] = 'page';
+  return $page;
+}
+
+/**
+ * Renders the page, including all theming.
+ * 
+ * @param $page
+ *   A string or array representing the content of a page. Recognized elements:
+ *     '#type': Value is always 'page'. This pushes the themeing through page.tpl.php.
+ *     'content': A renderable array as built by the menu callback.
+ *     '#show_blocks': A marker which suppresses left/right regions if FALSE.
+ *     '#show_messages': Suppress drupal_get_message() items. Used by Batch API.  
+ */
+function drupal_render_page($page) {  
+  // Backward compatibility - allow menu callbacks to return strings.
+  if (is_string($page)) {
+    $content = array('content' => array('#markup' => $page));
+    $page = drupal_get_page() + $content;
+  }
+  
+  // Modules alter the $page as needed. Blocks are populated into regions like 
+  // 'left', 'footer', etc.
+  drupal_alter('page', $page);
+
+  // Recursively render and concatenate each level of the $page.
+  print drupal_render($page);
+}
+
+/**
  * Renders HTML given a structured array tree.
  *
  * Recursively iterates over each of the array elements, generating HTML code.
@@ -3246,7 +3312,19 @@ function drupal_render(&$elements) {
     // Render each of the children using drupal_render and concatenate them.
     if (!isset($content) || $content === '') {
       foreach ($children as $key) {
-        $content .= drupal_render($elements[$key]);
+        // By default, drupal_render() processes all the element's children and 
+        // converts them into a string. In some cases it is desirable to 
+        // selectively render parts of the array and leave the final string
+        // conversion to a theme function. If the #concatenate_children
+        // property is FALSE the element's children are rendered and stored 
+        // in the #children property as an associative array of strings. The 
+        // #page element uses this feature. @see template_preprocess_page().
+        if (isset($elements['#concatenate_children']) && $elements['#concatenate_children'] == FALSE) {
+          $content[$key] = drupal_render($elements[$key]);
+        }
+        else {
+          $content .= drupal_render($elements[$key]);
+        }
       }
     }
   }
@@ -3339,7 +3417,7 @@ function drupal_common_theme() {
       'arguments' => array('text' => NULL)
     ),
     'page' => array(
-      'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE),
+      'arguments' => array('page' => NULL),
       'template' => 'page',
     ),
     'maintenance_page' => array(
@@ -3398,6 +3476,9 @@ function drupal_common_theme() {
     'item_list' => array(
       'arguments' => array('items' => array(), 'title' => NULL, 'type' => 'ul', 'attributes' => NULL),
     ),
+    'list' => array(
+      'arguments' => array('elements' => NULL),
+    ),
     'more_help_link' => array(
       'arguments' => array('url' => NULL),
     ),
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.458
diff -u -F^f -p -r1.458 theme.inc
--- includes/theme.inc	31 Dec 2008 12:02:21 -0000	1.458
+++ includes/theme.inc	5 Jan 2009 21:02:53 -0000
@@ -1571,6 +1571,29 @@ function theme_item_list($items = array(
 }
 
 /**
+ * Return a themed list of items from a drupal_render() style array.
+ *
+ * @param $elements
+ *   An array consisting of the following keys:
+ *     - #items: Mandatory - an array of items as expected by theme('item_list').
+ *     - #title: Optional - a title which prints above the list.
+ *     - #list_type: Optional - the type of list to return. Defaults to "ul".
+ *     - #attributes: Optional - an array of attributes as expected by theme('item_list').
+ * @return
+ *   A string containing the list output.
+ */
+function theme_list($elements) {
+  // Populate any missing array elements with their defaults.
+  $elements += array(
+    '#title' => '',
+    '#list_type' => 'ul',
+    '#attributes' => array(),
+    '#items' => array(),
+  );
+  return theme('item_list', $elements['#items'], $elements['#title'], $elements['#list_type'], $elements['#attributes']);
+}
+
+/**
  * Returns code that emits the 'more help'-link.
  */
 function theme_more_help_link($url) {
@@ -1633,30 +1656,6 @@ function theme_closure($main = 0) {
 }
 
 /**
- * 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 == <i>module</i>_<i>delta</i>
-      $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
@@ -1819,33 +1818,35 @@ function template_preprocess(&$variables
  * template_preprocess_maintenance_page() to keep all of them consistent.
  *
  * The $variables array contains the following arguments:
+ * - $page
  * - $content
- * - $show_blocks
  *
  * @see page.tpl.php
  */
 function template_preprocess_page(&$variables) {
+  // Move some variables to the top level for themer convenience and template cleanliness.
+  $variables['show_blocks'] = &$variables['page']['#show_blocks'];
+  $variables['show_messages'] = &$variables['page']['#show_messages'];
+  
+  // Import children of the page array as variables.
+  // This contains the main 'content' and the regions (with rendered blocks).
+  $content = $variables['page']['#children'];
+  foreach ($content as $key => $child) {
+    $variables[$key] = $child;
+  }
+  
+  // Ensure that we don't have empty regions. Avoids if (empty) checks in templates.
+  foreach (system_region_list($GLOBALS['theme']) as $region_key => $region_name) {
+    if (empty($variables[$region_key])) {
+      $variables[$region_key] = '';
+    }
+  }
+  
   // Add favicon
   if (theme_get_setting('toggle_favicon')) {
     drupal_set_html_head('<link rel="shortcut icon" href="' . check_url(theme_get_setting('favicon')) . '" type="image/x-icon" />');
   }
 
-  global $theme;
-  // Populate all block regions.
-  $regions = system_region_list($theme);
-  // Load all region content assigned via blocks.
-  foreach (array_keys($regions) as $region) {
-    // Prevent left and right regions from rendering blocks when 'show_blocks' == FALSE.
-    if ($variables['show_blocks'] || ($region != 'left' && $region != 'right')) {
-      $blocks = theme('blocks', $region);
-    }
-    else {
-      $blocks = '';
-    }
-    // Assign region to a region variable.
-    isset($variables[$region]) ? $variables[$region] .= $blocks : $variables[$region] = $blocks;
-  }
-
   // Set up layout variable.
   $variables['layout'] = 'none';
   if (!empty($variables['left'])) {
@@ -1883,8 +1884,8 @@ function template_preprocess_page(&$vari
   $variables['logo']              = theme_get_setting('logo');
   $variables['messages']          = $variables['show_messages'] ? theme('status_messages') : '';
   $variables['mission']           = isset($mission) ? $mission : '';
-  $variables['main_menu']     = theme_get_setting('toggle_main_menu') ? menu_main_menu() : array();
-  $variables['secondary_menu']   = theme_get_setting('toggle_secondary_menu') ? menu_secondary_menu() : array();
+  $variables['main_menu']         = theme_get_setting('toggle_main_menu') ? menu_main_menu() : array();
+  $variables['secondary_menu']    = theme_get_setting('toggle_secondary_menu') ? menu_secondary_menu() : array();
   $variables['search_box']        = (theme_get_setting('toggle_search') ? drupal_get_form('search_theme_form') : '');
   $variables['site_name']         = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : '');
   $variables['site_slogan']       = (theme_get_setting('toggle_slogan') ? variable_get('site_slogan', '') : '');
@@ -1977,6 +1978,8 @@ function template_preprocess_page(&$vari
  * @see node.tpl.php
  */
 function template_preprocess_node(&$variables) {
+  $variables['teaser'] = &$variables['elements']['teaser'];
+  $variables['node'] = &$variables['elements']['node'];
   $node = $variables['node'];
 
   $variables['date']      = format_date($node->created);
@@ -2042,6 +2045,7 @@ function template_preprocess_node(&$vari
  */
 function template_preprocess_block(&$variables) {
   static $block_counter = array();
+  $variables['block'] = $variables['block']['#block'];
   // All blocks get an independent counter for each region.
   if (!isset($block_counter[$variables['block']->region])) {
     $block_counter[$variables['block']->region] = 1;
Index: modules/blog/blog.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/blog/blog.pages.inc,v
retrieving revision 1.14
diff -u -F^f -p -r1.14 blog.pages.inc
--- modules/blog/blog.pages.inc	13 Oct 2008 00:33:01 -0000	1.14
+++ modules/blog/blog.pages.inc	5 Jan 2009 21:02:53 -0000
@@ -13,6 +13,7 @@ function blog_page_user($account) {
   global $user;
 
   drupal_set_title($title = t("@name's blog", array('@name' => $account->name)), PASS_THROUGH);
+  $page = drupal_get_page();
 
   $items = array();
 
@@ -23,18 +24,21 @@ function blog_page_user($account) {
     $items[] = t('You are not allowed to post a new blog entry.');
   }
 
-  $output = theme('item_list', $items);
-
-  $result = pager_query(db_rewrite_sql("SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.type = 'blog' AND n.uid = %d AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10), 0, NULL, $account->uid);
-  $has_posts = FALSE;
-
-  while ($node = db_fetch_object($result)) {
-    $output .= node_view(node_load($node->nid), 1);
-    $has_posts = TRUE;
-  }
-
-  if ($has_posts) {
-    $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
+  $page['content']['blog_actions'] = array(
+    '#items' => $items,
+    '#theme' => 'list',
+    '#weight' => -1,
+  );
+
+  $nids = pager_query(db_rewrite_sql("SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.type = 'blog' AND n.uid = %d AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10), 0, NULL, $account->uid)->fetchCol();
+  if (!empty($nids)) {
+    $nodes = node_load_multiple($nids);
+    $page['content'] += node_build_multiple($nodes);
+    $page['content']['pager'] = array(
+      '#markup' => theme('pager', NULL, variable_get('default_nodes_main', 10)),
+      '#weight' => count($nodes),
+    );
+    $page['content']['#sorted'] = TRUE;
   }
   else {
     if ($account->uid == $user->uid) {
@@ -46,7 +50,7 @@ function blog_page_user($account) {
   }
   drupal_add_feed(url('blog/' . $account->uid . '/feed'), t('RSS - !title', array('!title' => $title)));
 
-  return $output;
+  return $page;
 }
 
 /**
@@ -56,31 +60,34 @@ function blog_page_last() {
   global $user;
 
   $output = '';
-  $items = array();
+  $page = drupal_get_page();
 
   if (user_access('edit own blog')) {
     $items[] = l(t('Create new blog entry.'), "node/add/blog");
-  }
-
-  $output = theme('item_list', $items);
-
-  $result = pager_query(db_rewrite_sql("SELECT n.nid, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10));
-  $has_posts = FALSE;
-
-  while ($node = db_fetch_object($result)) {
-    $output .= node_view(node_load($node->nid), 1);
-    $has_posts = TRUE;
-  }
-
-  if ($has_posts) {
-    $output .= theme('pager', NULL, variable_get('default_nodes_main', 10));
+    $page['content']['blog_actions'] = array(
+      '#items' => $items,
+      '#theme' => 'list',
+      '#weight' => -1,
+    );
+  }
+
+  $nids = pager_query(db_rewrite_sql("SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.type = 'blog' AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC"), variable_get('default_nodes_main', 10))->fetchCol();
+
+  if (!empty($nids)) {
+    $nodes = node_load_multiple($nids);
+    $page['content'] += node_build_multiple($nodes);
+    $page['content']['pager'] = array(
+      '#markup' => theme('pager', NULL, variable_get('default_nodes_main', 10)),
+      '#weight' => count($nodes),
+    );
+    $page['content']['#sorted'] = TRUE;
   }
   else {
     drupal_set_message(t('No blog entries have been created.'));
   }
   drupal_add_feed(url('blog/feed'), t('RSS - blogs'));
 
-  return $output;
+  return $page;
 }
 
 /**
Index: modules/comment/comment.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v
retrieving revision 1.678
diff -u -F^f -p -r1.678 comment.module
--- modules/comment/comment.module	2 Jan 2009 21:17:35 -0000	1.678
+++ modules/comment/comment.module	5 Jan 2009 21:02:55 -0000
@@ -1628,7 +1628,7 @@ function comment_form_add_preview($form,
   }
   else {
     $suffix = empty($form['#suffix']) ? '' : $form['#suffix'];
-    $form['#suffix'] = $suffix . node_view($node);
+    $form['#suffix'] = $suffix . drupal_render(node_view($node));
     $edit['pid'] = 0;
   }
 
Index: modules/comment/comment.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.pages.inc,v
retrieving revision 1.11
diff -u -F^f -p -r1.11 comment.pages.inc
--- modules/comment/comment.pages.inc	31 Dec 2008 12:02:21 -0000	1.11
+++ modules/comment/comment.pages.inc	5 Jan 2009 21:02:55 -0000
@@ -92,7 +92,7 @@ function comment_reply($node, $pid = NUL
       }
       // This is the case where the comment is in response to a node. Display the node.
       elseif (user_access('access content')) {
-        $output .= node_view($node);
+        $output .= drupal_render(node_view($node));
       }
 
       // Should we show the reply box?
Index: modules/node/node.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.api.php,v
retrieving revision 1.6
diff -u -F^f -p -r1.6 node.api.php
--- modules/node/node.api.php	31 Dec 2008 12:02:22 -0000	1.6
+++ modules/node/node.api.php	5 Jan 2009 21:02:55 -0000
@@ -81,7 +81,7 @@ function hook_node_access_records($node)
     return;
   }
 
-  // We only care about the node if it's been marked private. If not, it is
+  // We only care about the node if it has been marked private. If not, it is
   // treated just like any other node and we completely ignore it.
   if ($node->private) {
     $grants = array();
@@ -169,7 +169,7 @@ function hook_node_operations() {
  * @return
  *   None.
  */
-function hook_nodeapi_alter($node, $teaser, $page) {
+function hook_nodeapi_alter($node, $teaser) {
 }
 
 /**
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1008
diff -u -F^f -p -r1.1008 node.module
--- modules/node/node.module	31 Dec 2008 12:02:22 -0000	1.1008
+++ modules/node/node.module	5 Jan 2009 21:02:56 -0000
@@ -96,7 +96,7 @@ function node_help($path, $arg) {
 function node_theme() {
   return array(
     'node' => array(
-      'arguments' => array('node' => NULL, 'teaser' => FALSE, 'page' => FALSE),
+      'arguments' => array('elements' => NULL),
       'template' => 'node',
     ),
     'node_list' => array(
@@ -1123,24 +1123,28 @@ function node_delete($nid) {
 }
 
 /**
- * Generate a display of the given node.
+ * Generate an array for rendering the given node.
  *
  * @param $node
  *   A node array or node object.
  * @param $teaser
  *   Whether to display the teaser only or the full form.
- * @param $links
- *   Whether or not to display node links. Links are omitted for node previews.
  *
  * @return
- *   An HTML representation of the themed node.
+ *   An array as expected by drupal_render().
  */
 function node_view($node, $teaser = FALSE) {
   $node = (object)$node;
 
   $node = node_build_content($node, $teaser);
-
-  return theme('node', $node, $teaser);
+  
+  $build = $node->content;
+  $build += array(
+    '#theme' => 'node',
+    'node' => $node,
+    'teaser' => $teaser,
+  );
+  return $build;
 }
 
 /**
@@ -1208,19 +1212,27 @@ function node_build_content($node, $teas
 }
 
 /**
- * Generate a page displaying a single node.
+ * Generate an array which displays a node detail page.
+ * 
+ * @param $node
+ *   A node object.
+ * @param $message
+ *   A flag which sets a page title relevant to the revision being viewed.
+ * @return
+ *   A $page element suitable for use by drupal_page_render().
+ *   
  */
 function node_show($node, $message = FALSE) {
   if ($message) {
     drupal_set_title(t('Revision of %title from %date', array('%title' => $node->title, '%date' => format_date($node->revision_timestamp))), PASS_THROUGH);
   }
 
-  $output = node_view($node, FALSE, TRUE);
-
   // Update the history table, stating that this user viewed this node.
   node_tag_new($node->nid);
-
-  return $output;
+  
+  $page = drupal_get_page();
+  $page['content'] = node_view($node, FALSE);
+  return $page;
 }
 
 /**
@@ -1872,20 +1884,46 @@ function node_feed($nids = FALSE, $chann
 }
 
 /**
+ * Construct a drupal_render() style array from an array of loaded nodes.
+ *
+ * @param $nodes
+ *   An array of nodes as returned by node_load_multiple().
+ * @param $teaser
+ *   Display nodes into teaser view or full view.
+ * @param $weight
+ *   An integer representing the weight of the first node in the list. 
+ * @return 
+ *   An array in the format expected by drupal_render().
+ */
+function node_build_multiple($nodes, $teaser = TRUE, $weight = 0) {
+  $build = array();
+  foreach ($nodes as $node) {
+    $build['nodes'][$node->nid] = node_view($node, $teaser);
+    $build['nodes'][$node->nid]['#weight'] = $weight;
+    $weight++;
+  }
+  $build['nodes']['#sorted'] = TRUE;
+  return $build;
+}
+
+/**
  * Menu callback; Generate a listing of promoted nodes.
  */
 function node_page_default() {
-  $nids = pager_query(db_rewrite_sql('SELECT n.nid 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))->fetchCol();
+  $page = drupal_get_page();
+  
+  $nids = 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))->fetchCol();
   if (!empty($nids)) {
     $nodes = node_load_multiple($nids);
-    $output = '';
-    foreach ($nodes as $node) {
-      $output .= node_view($node, TRUE);
-    }
+    $page['content'] = node_build_multiple($nodes);
 
     $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));
+    $page['content']['pager'] = array(
+      '#markup' => theme('pager', NULL, variable_get('default_nodes_main', 10)),
+      '#weight' => count($nodes),
+    );
+    $page['content']['#sorted'] = TRUE;
   }
   else {
     $default_message = '<h1 class="title">' . t('Welcome to your new Drupal website!') . '</h1>';
@@ -1898,11 +1936,11 @@ function node_page_default() {
     $default_message .= '</ol>';
     $default_message .= '<p>' . t('For more information, please refer to the <a href="@help">help section</a>, or the <a href="@handbook">online Drupal handbooks</a>. You may also post at the <a href="@forum">Drupal forum</a>, or view the wide range of <a href="@support">other support options</a> available.', array('@help' => url('admin/help'), '@handbook' => 'http://drupal.org/handbooks', '@forum' => 'http://drupal.org/forum', '@support' => 'http://drupal.org/support')) . '</p>';
 
-    $output = '<div id="first-time">' . $default_message . '</div>';
+    $page['content'] = array('#markup' => '<div id="first-time">' . $default_message . '</div>');
   }
   drupal_set_title('');
 
-  return $output;
+  return $page;
 }
 
 /**
@@ -2930,7 +2968,7 @@ function node_unpublish_by_keyword_actio
  */
 function node_unpublish_by_keyword_action($node, $context) {
   foreach ($context['keywords'] as $keyword) {
-    if (strstr(node_view(clone $node), $keyword) || strstr($node->title, $keyword)) {
+    if (strstr(drupal_render(node_view(clone $node)), $keyword) || strstr($node->title, $keyword)) {
       $node->status = 0;
       watchdog('action', 'Set @type %title to unpublished.', array('@type' => node_get_types('name', $node), '%title' => $node->title));
       break;
Index: modules/node/node.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.pages.inc,v
retrieving revision 1.48
diff -u -F^f -p -r1.48 node.pages.inc
--- modules/node/node.pages.inc	30 Dec 2008 16:43:18 -0000	1.48
+++ modules/node/node.pages.inc	5 Jan 2009 21:02:56 -0000
@@ -415,12 +415,12 @@ function theme_node_preview($node) {
   if ($preview_trimmed_version) {
     drupal_set_message(t('The trimmed version of your post shows what your post looks like when promoted to the main page or when exported for syndication.<span class="no-js"> You can insert the delimiter "&lt;!--break--&gt;" (without the quotes) to fine-tune where your post gets split.</span>'));
     $output .= '<h3>' . t('Preview trimmed version') . '</h3>';
-    $output .= node_view(clone $node, 1, FALSE);
+    $output .= drupal_render(node_view(clone $node, TRUE));
     $output .= '<h3>' . t('Preview full version') . '</h3>';
-    $output .= node_view($node, 0, FALSE);
+    $output .= drupal_render(node_view($node, FALSE));
   }
   else {
-    $output .= node_view($node, 0, FALSE);
+    $output .= drupal_render(node_view($node, FALSE));
   }
   $output .= "</div>\n";
 
Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.111
diff -u -F^f -p -r1.111 system.admin.inc
--- modules/system/system.admin.inc	30 Dec 2008 16:43:19 -0000	1.111
+++ modules/system/system.admin.inc	5 Jan 2009 21:02:58 -0000
@@ -1900,7 +1900,7 @@ function system_batch_page() {
   elseif (isset($output)) {
     // Force a page without blocks or messages to
     // display a list of collected messages later.
-    print theme('page', $output, FALSE, FALSE);
+    return $output;
   }
 }
 
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.8
diff -u -F^f -p -r1.8 system.api.php
--- modules/system/system.api.php	31 Dec 2008 11:08:47 -0000	1.8
+++ modules/system/system.api.php	5 Jan 2009 21:02:58 -0000
@@ -180,6 +180,28 @@ function hook_js_alter(&$javascript) {
 }
 
 /**
+ * Perform alterations before a page is rendered.
+ *
+ * Use this hook when you want to add/remove elements at the page level. If are 
+ * making changes to forms or nodes or users, use their hook_OBJECT_alter() hook instead.
+ *
+ * @param $page
+ *   Nested array of render elements that comprise the page.
+ * @return
+ *   None.
+ */
+function hook_page_alter($page) {
+  if (menu_get_object()) {
+    // We are on a node detail page. Append a standard disclaimer.
+    $page['disclaimer'] = array(
+      '#markup' => t("Opinions expressed on this page belong to its authors, and not to Acme, Inc."),
+      '#weight' => 25,
+    );
+  }
+}
+
+
+/**
  * Perform alterations before a form is rendered.
  *
  * One popular use of this hook is to add form elements to the node form. When
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.655
diff -u -F^f -p -r1.655 system.module
--- modules/system/system.module	2 Jan 2009 20:28:09 -0000	1.655
+++ modules/system/system.module	5 Jan 2009 21:02:59 -0000
@@ -236,6 +236,14 @@ function system_elements() {
     '#method' => 'post',
     '#action' => request_uri(),
   );
+  
+  // @see drupal_render() for documentation about #concatenate_children. 
+  $type['page'] = array(
+    '#show_messages' => TRUE,
+    '#show_blocks' => TRUE,
+    '#value' => NULL,
+    '#concatenate_children' => FALSE, 
+  );
 
   /**
    * Input elements.
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.451
diff -u -F^f -p -r1.451 taxonomy.module
--- modules/taxonomy/taxonomy.module	30 Dec 2008 16:43:19 -0000	1.451
+++ modules/taxonomy/taxonomy.module	5 Jan 2009 21:03:00 -0000
@@ -26,9 +26,6 @@ function taxonomy_theme() {
     'taxonomy_term_select' => array(
       'arguments' => array('element' => NULL),
     ),
-    'taxonomy_term_page' => array(
-      'arguments' => array('tids' => array(), 'result' => NULL),
-    ),
     'taxonomy_overview_vocabularies' => array(
       'arguments' => array('form' => array()),
     ),
@@ -1223,7 +1220,7 @@ function theme_taxonomy_term_select($ele
  * @param $order
  *   The order clause for the query that retrieve the nodes.
  * @return
- *   A resource identifier pointing to the query results.
+ *   An array node IDs.
  */
 function taxonomy_select_nodes($tids = array(), $operator = 'or', $depth = 0, $pager = TRUE, $order = 'n.sticky DESC, n.created DESC') {
   if (count($tids) > 0) {
@@ -1241,7 +1238,7 @@ function taxonomy_select_nodes($tids = a
     if ($operator == 'or') {
       $args = call_user_func_array('array_merge', $descendant_tids);
       $placeholders = db_placeholders($args, 'int');
-      $sql = 'SELECT DISTINCT(n.nid), n.sticky, n.title, n.created FROM {node} n INNER JOIN {term_node} tn ON n.vid = tn.vid WHERE tn.tid IN (' . $placeholders . ') AND n.status = 1 ORDER BY ' . $order;
+      $sql = 'SELECT DISTINCT(n.nid) AS nid, n.sticky, n.created FROM {node} n INNER JOIN {term_node} tn ON n.vid = tn.vid WHERE tn.tid IN (' . $placeholders . ') AND n.status = 1 ORDER BY ' . $order;
       $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n INNER JOIN {term_node} tn ON n.vid = tn.vid WHERE tn.tid IN (' . $placeholders . ') AND n.status = 1';
     }
     else {
@@ -1253,44 +1250,20 @@ function taxonomy_select_nodes($tids = a
         $wheres .= ' AND tn' . $index . '.tid IN (' . db_placeholders($tids, 'int') . ')';
         $args = array_merge($args, $tids);
       }
-      $sql = 'SELECT DISTINCT(n.nid), n.sticky, n.title, n.created FROM {node} n ' . $joins . ' WHERE n.status = 1 ' . $wheres . ' ORDER BY ' . $order;
+      $sql = 'SELECT DISTINCT(n.nid) AS nid, n.sticky, n.created FROM {node} n ' . $joins . ' WHERE n.status = 1 ' . $wheres . ' ORDER BY ' . $order;
       $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n ' . $joins . ' WHERE n.status = 1 ' . $wheres;
     }
     $sql = db_rewrite_sql($sql);
     $sql_count = db_rewrite_sql($sql_count);
     if ($pager) {
-      $result = pager_query($sql, variable_get('default_nodes_main', 10), 0, $sql_count, $args);
+      $nids = pager_query($sql, variable_get('default_nodes_main', 10), 0, $sql_count, $args)->fetchCol();
     }
     else {
-      $result = db_query_range($sql, $args, 0, variable_get('feed_default_items', 10));
+      $nids = db_query_range($sql, $args, 0, variable_get('feed_default_items', 10))->fetchCol();
     }
   }
 
-  return $result;
-}
-
-/**
- * Accepts the result of a pager_query() call, such as that performed by
- * taxonomy_select_nodes(), and formats each node along with a pager.
- */
-function taxonomy_render_nodes($result) {
-  $output = '';
-  $nids = array();
-  foreach ($result as $record) {
-    $nids[] = $record->nid;
-  }
-  if (!empty($nids)) {
-    $nodes = node_load_multiple($nids);
-
-    foreach ($nodes as $node) {
-      $output .= node_view($node, 1);
-    }
-    $output .= theme('pager', NULL, variable_get('default_nodes_main', 10), 0);
-  }
-  else {
-    $output .= '<p>' . t('There are currently no posts in this category.') . '</p>';
-  }
-  return $output;
+  return $nids;
 }
 
 /**
Index: modules/taxonomy/taxonomy.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.pages.inc,v
retrieving revision 1.19
diff -u -F^f -p -r1.19 taxonomy.pages.inc
--- modules/taxonomy/taxonomy.pages.inc	19 Dec 2008 03:55:23 -0000	1.19
+++ modules/taxonomy/taxonomy.pages.inc	5 Jan 2009 21:03:00 -0000
@@ -42,12 +42,41 @@ function taxonomy_term_page($terms, $dep
           $breadcrumb[] = l(t('Home'), NULL);
           $breadcrumb = array_reverse($breadcrumb);
           drupal_set_breadcrumb($breadcrumb);
-
-          $output = theme('taxonomy_term_page', $tids, taxonomy_select_nodes($tids, $terms['operator'], $depth, TRUE));
           drupal_add_feed(url('taxonomy/term/' . $str_tids . '/' . $depth . '/feed'), 'RSS - ' . $title);
-          return $output;
-          break;
+          
+          $page = drupal_get_page();
+          
+          // Only display the description if we have a single term, to avoid clutter and confusion.
+          if (count($tids) == 1) {
+            $term = taxonomy_term_load($tids[0]);
+            if (!empty($term->description)) {
+              $page['content']['term_description'] = array(
+                '#markup' => '<div class="taxonomy-term-description">' . filter_xss_admin($term->description) . '</div>',
+                '#weight' => -1,
+              );
+            }
+          }
+          
+          if ($nids = taxonomy_select_nodes($tids, $terms['operator'], $depth, TRUE)) {
+            drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css');
+
+            $nodes = node_load_multiple($nids);
+            $page['content'] += node_build_multiple($nodes);
+            $page['content']['pager'] = array(
+              '#markup' => theme('pager', NULL, variable_get('default_nodes_main', 10)),
+              '#weight' => count($nodes),
+            );
+            $page['content']['#sorted'] = TRUE;
+          }
+          else {
+            $page['content']['no_posts'] = array(
+              '#prefix' => '<p>',
+              '#markup' => t('There are currently no posts in this category.'),
+              '#suffix' => '</p>',
+            );
+          }
 
+          return $page;
         case 'feed':
           $channel['link'] = url('taxonomy/term/' . $str_tids . '/' . $depth, array('absolute' => TRUE));
           $channel['title'] = variable_get('site_name', 'Drupal') . ' - ' . $title;
@@ -58,13 +87,9 @@ function taxonomy_term_page($terms, $dep
             $channel['description'] = $term->description;
           }
 
-          $result = taxonomy_select_nodes($tids, $terms['operator'], $depth, FALSE);
-          $items = array();
-          while ($row = db_fetch_object($result)) {
-            $items[] = $row->nid;
-          }
+          $nids = taxonomy_select_nodes($tids, $terms['operator'], $depth, FALSE);
 
-          node_feed($items, $channel);
+          node_feed($nids, $channel);
           break;
 
         default:
@@ -78,38 +103,6 @@ function taxonomy_term_page($terms, $dep
 }
 
 /**
- * Render a taxonomy term page HTML output.
- *
- * @param $tids
- *   An array of term ids.
- * @param $result
- *   A pager_query() result, such as that performed by taxonomy_select_nodes().
- *
- * @ingroup themeable
- */
-function theme_taxonomy_term_page($tids, $result) {
-  drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css');
-  $output = '';
-
-  // Only display the description if we have a single term, to avoid clutter and confusion.
-  if (count($tids) == 1) {
-    $term = taxonomy_term_load($tids[0]);
-    $description = $term->description;
-
-    // Check that a description is set.
-    if (!empty($description)) {
-      $output .= '<div class="taxonomy-term-description">';
-      $output .= filter_xss_admin($description);
-      $output .= '</div>';
-    }
-  }
-
-  $output .= taxonomy_render_nodes($result);
-
-  return $output;
-}
-
-/**
  * Page to edit a vocabulary term.
  */
 function taxonomy_term_edit($term) {
Index: modules/upload/upload.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload/upload.test,v
retrieving revision 1.10
diff -u -F^f -p -r1.10 upload.test
--- modules/upload/upload.test	30 Dec 2008 16:43:19 -0000	1.10
+++ modules/upload/upload.test	5 Jan 2009 21:03:00 -0000
@@ -53,7 +53,7 @@ class UploadTestCase extends DrupalWebTe
     
     // Assure that the attachment link appears on teaser view and has correct count.
     $node = node_load($node->nid);
-    $teaser = node_view($node, TRUE);
+    $teaser = drupal_render(node_view($node, TRUE));
     $this->assertTrue(strpos($teaser, format_plural(2, '1 attachment', '@count attachments')), 'Attachments link found on node teaser.');
 
     // Fetch db record and use fid to rename and delete file.
