Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.132
diff -u -F^f -r1.132 form.inc
--- includes/form.inc	5 Aug 2006 00:26:35 -0000	1.132
+++ includes/form.inc	7 Aug 2006 01:18:28 -0000
@@ -648,7 +648,15 @@ function form_render(&$elements) {
   if (isset($content) && $content !== '') {
     $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
     $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
-    return $prefix . $content . $suffix;
+    $content = $prefix . $content . $suffix;
+    
+    if (isset($elements['#after_render'])) {
+      foreach ($elements['#after_render'] as $function) {
+        $function($elements, $content);
+      }
+    }
+
+    return $content;
   }
 }
 
Index: modules/blog/blog.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/blog/blog.module,v
retrieving revision 1.254
diff -u -F^f -r1.254 blog.module
--- modules/blog/blog.module	6 Aug 2006 23:00:41 -0000	1.254
+++ modules/blog/blog.module	7 Aug 2006 01:18:28 -0000
@@ -235,7 +235,7 @@ function blog_form(&$node) {
 /**
  * Implementation of hook_view().
  */
-function blog_view(&$node, $teaser = FALSE, $page = FALSE) {
+function blog_view($node, $teaser = FALSE, $page = FALSE) {
   if ($page) {
     // Breadcrumb navigation
     $breadcrumb[] = array('path' => 'blog', 'title' => t('blogs'));
@@ -243,7 +243,7 @@ function blog_view(&$node, $teaser = FAL
     $breadcrumb[] = array('path' => 'node/'. $node->nid);
     menu_set_location($breadcrumb);
   }
-  $node = node_prepare($node, $teaser);
+  return node_prepare($node, $teaser);
 }
 
 /**
Index: modules/book/book.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/book/book.module,v
retrieving revision 1.378
diff -u -F^f -r1.378 book.module
--- modules/book/book.module	6 Aug 2006 23:00:41 -0000	1.378
+++ modules/book/book.module	7 Aug 2006 01:18:28 -0000
@@ -440,16 +440,6 @@ function book_content($node, $teaser = F
 }
 
 /**
- * Implementation of hook_view().
- *
- * If not displayed on the main page, we render the node as a page in the
- * book with extra links to the previous and next pages.
- */
-function book_view(&$node, $teaser = FALSE, $page = FALSE) {
-  $node = node_prepare($node, $teaser);
-}
-
-/**
  * Implementation of hook_nodeapi().
  *
  * Appends book navigation to all nodes in the book.
@@ -476,7 +466,10 @@ function book_nodeapi(&$node, $op, $teas
           }
           $node->breadcrumb[] = array('path' => 'node/'. $node->nid);
 
-          $node->body .= theme('book_navigation', $node);
+          $node->content['book_navigation'] = array(
+            '#value' => theme('book_navigation', $node),
+            '#weight' => 100,
+          );
 
           if ($page) {
             menu_set_location($node->breadcrumb);
@@ -811,7 +804,7 @@ function book_node_visitor_html_pre($nod
   $output .= "<h1 class=\"book-heading\">". check_plain($node->title) ."</h1>\n";
 
   if ($node->body) {
-    $output .= $node->body;
+    $output .= form_render($node->content);
   }
   return $output;
 }
Index: modules/forum/forum.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/forum/forum.module,v
retrieving revision 1.344
diff -u -F^f -r1.344 forum.module
--- modules/forum/forum.module	6 Aug 2006 23:00:42 -0000	1.344
+++ modules/forum/forum.module	7 Aug 2006 01:18:28 -0000
@@ -313,8 +313,11 @@ function forum_view(&$node, $teaser = FA
   }
 
   $node = node_prepare($node, $teaser);
-
-  $node->body .= theme('forum_topic_navigation', $node);
+  $node->content['forum_navigation'] = array(
+    '#value' => theme('forum_topic_navigation', $node),
+    '#weight' => 100,
+  );
+  return $node;
 }
 
 /**
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.667
diff -u -F^f -r1.667 node.module
--- modules/node/node.module	6 Aug 2006 23:00:42 -0000	1.667
+++ modules/node/node.module	7 Aug 2006 01:18:28 -0000
@@ -636,24 +636,6 @@ function node_save(&$node) {
 function node_view($node, $teaser = FALSE, $page = FALSE, $links = TRUE) {
   $node = (object)$node;
 
-  // Remove the delimiter (if any) that separates the teaser from the body.
-  // TODO: this strips legitimate uses of '<!--break-->' also.
-  $node->body = str_replace('<!--break-->', '', $node->body);
-
-  if ($node->log != '' && !$teaser) {
-    $node->body .= '<div class="log"><div class="title">'. t('Log') .':</div>'. filter_xss($node->log) .'</div>';
-  }
-
-  // The 'view' hook can be implemented to overwrite the default function
-  // to display nodes.
-  if (node_hook($node, 'view')) {
-    node_invoke($node, 'view', $teaser, $page);
-  }
-  else {
-    $node = node_prepare($node, $teaser);
-  }
-  // Allow modules to change $node->body before viewing.
-  node_invoke_nodeapi($node, 'view', $teaser, $page);
   if ($links) {
     $node->links = module_invoke_all('link', 'node', $node, !$page);
 
@@ -662,11 +644,18 @@ function node_view($node, $teaser = FALS
       $function($node, $node->links);
     }
   }
-  // unset unused $node part so that a bad theme can not open a security hole
+
+  $node = node_build_content($node, $teaser, $page);
+
+  // Set the proper node part, then unset unused $node part so that a bad
+  // theme can not open a security hole.
+  $content = form_render($node->content);
   if ($teaser) {
+    $node->teaser = $content;
     unset($node->body);
   }
   else {
+    $node->body = $content;
     unset($node->teaser);
   }
 
@@ -674,16 +663,62 @@ function node_view($node, $teaser = FALS
 }
 
 /**
- * Apply filters to a node in preparation for theming.
+ * Apply filters and build the node's standard elements.
  */
 function node_prepare($node, $teaser = FALSE) {
-  $node->readmore = (strlen($node->teaser) < strlen($node->body));
-  if ($teaser == FALSE) {
-    $node->body = check_markup($node->body, $node->format, FALSE);
+  $node->content['body'] = array(
+    '#value' => check_markup($teaser ? $node->teaser : $node->body, $node->format, FALSE),
+    '#weight' => 0,
+  );
+
+  if ($node->log != '' && !$teaser) {
+    $node->content['log_message'] = array(
+      '#value' => theme('node_log_message', filter_xss($node->log)),
+      '#weight' => 20,
+    );
+  }
+
+  if (strlen($node->teaser) < strlen($node->body)) {
+    $node->readmore = TRUE;
+  }
+
+  return $node;
+}
+
+/**
+ * Builds a structured array representing the node's content.
+ *
+ * @param $node
+ *   A node object.
+ * @param $teaser
+ *   Whether to display the teaser only, as on the main page.
+ * @param $page
+ *   Whether the node is being displayed by itself as a page.
+ *
+ * @return
+ *   An structured array containing the individual elements
+ *   of the node's body.
+ */
+function node_build_content($node, $teaser = FALSE, $page = FALSE) {
+  // Remove the delimiter (if any) that separates the teaser from the body.
+  // TODO: this strips legitimate uses of '<!--break-->' also.
+  $node->body = str_replace('<!--break-->', '', $node->body);
+
+  // The 'view' hook can be implemented to overwrite the default function
+  // to display nodes.
+  if (node_hook($node, 'view')) {
+    $node = node_invoke($node, 'view', $teaser, $page);
   }
   else {
-    $node->teaser = check_markup($node->teaser, $node->format, FALSE);
+    $node = node_prepare($node, $teaser);
   }
+
+  // Allow modules to make their own additions to the node.
+  node_invoke_nodeapi($node, 'view', $teaser, $page);
+
+  // Allow modules to modify the fully-built node.
+  node_invoke_nodeapi($node, 'alter', $teaser, $page);
+
   return $node;
 }
 
@@ -838,17 +873,10 @@ function node_search($op = 'search', $ke
       // Load results
       $results = array();
       foreach ($find as $item) {
+        // Build the node body.
         $node = node_load($item->sid);
-
-        // Get node output (filtered and with module-specific fields).
-        if (node_hook($node, 'view')) {
-          node_invoke($node, 'view', FALSE, FALSE);
-        }
-        else {
-          $node = node_prepare($node, FALSE);
-        }
-        // Allow modules to change $node->body before viewing.
-        node_invoke_nodeapi($node, 'view', FALSE, FALSE);
+        $node = node_build_content($node, FALSE, FALSE);
+        $node->body = form_render($node->content);
 
         // Fetch comments for snippet
         $node->body .= module_invoke('comment', 'nodeapi', $node, 'update index');
@@ -2049,6 +2077,10 @@ function theme_node_preview($node) {
   return $output;
 }
 
+function theme_node_log_message($log) {
+  return '<div class="log"><div class="title">'. t('Log') .':</div>'. $log .'</div>';
+}
+
 function node_form_submit($form_id, $edit) {
   global $user;
 
@@ -2328,15 +2360,10 @@ function node_update_index() {
     $last_nid = $node->nid;
     $node = node_load($node->nid);
 
-    // Get node output (filtered and with module-specific fields).
-    if (node_hook($node, 'view')) {
-      node_invoke($node, 'view', FALSE, FALSE);
-    }
-    else {
-      $node = node_prepare($node, FALSE);
-    }
-    // Allow modules to change $node->body before viewing.
-    node_invoke_nodeapi($node, 'view', FALSE, FALSE);
+    // Build the node body.
+    $node = node_load($item->sid);
+    $node = node_build_content($node, FALSE, FALSE);
+    $node->body = form_render($node->content);
 
     $text = '<h1>'. check_plain($node->title) .'</h1>'. $node->body;
 
Index: modules/poll/poll.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/poll/poll.module,v
retrieving revision 1.205
diff -u -F^f -r1.205 poll.module
--- modules/poll/poll.module	6 Aug 2006 23:00:42 -0000	1.205
+++ modules/poll/poll.module	7 Aug 2006 01:18:28 -0000
@@ -56,12 +56,11 @@ function poll_block($op = 'list', $delta
         $poll = node_load(array('type' => 'poll', 'created' => $timestamp, 'status' => 1));
 
         if ($poll->nid) {
-          // poll_view() dumps the output into $poll->body.
-          poll_view($poll, 1, 0, 1);
+          $poll = poll_view($poll, TRUE, FALSE, TRUE);
         }
       }
       $block['subject'] = t('Poll');
-      $block['content'] = $poll->body;
+      $block['content'] = form_render($poll->content);
       return $block;
     }
   }
@@ -541,14 +540,14 @@ function poll_cancel(&$node) {
  *   An extra parameter that adapts the hook to display a block-ready
  *   rendering of the poll.
  */
-function poll_view(&$node, $teaser = FALSE, $page = FALSE, $block = FALSE) {
+function poll_view($node, $teaser = FALSE, $page = FALSE, $block = FALSE) {
   global $user;
   $output = '';
 
   // Special display for side-block
   if ($block) {
     // No 'read more' link
-    $node->body = $node->teaser = '';
+    $node->readmore = FALSE;
 
     $links = module_invoke_all('link', 'node', $node, 1);
     $links[] = array('title' => t('older polls'), 'href' => 'poll', 'attributes' => array('title' => t('View the list of polls on this site.')));
@@ -560,13 +559,16 @@ function poll_view(&$node, $teaser = FAL
   }
 
   if ($node->allowvotes && ($block || arg(2) != 'results')) {
-    $output .= poll_view_voting($node, $teaser, $page, $block);
+    $node->content['body'] = array(
+      '#value' => poll_view_voting($node, $teaser, $page, $block),
+    );
   }
   else {
-    $output .= poll_view_results($node, $teaser, $page, $block);
+    $node->content['body'] = array(
+      '#value' => poll_view_results($node, $teaser, $page, $block),
+    );
   }
-
-  $node->body = $node->teaser = $output;
+  return $node;
 }
 
 /**
Index: modules/upload/upload.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload/upload.module,v
retrieving revision 1.116
diff -u -F^f -r1.116 upload.module
--- modules/upload/upload.module	6 Aug 2006 23:00:42 -0000	1.116
+++ modules/upload/upload.module	7 Aug 2006 01:18:28 -0000
@@ -484,32 +484,36 @@ function upload_nodeapi(&$node, $op, $te
 
     case 'view':
       if (isset($node->files) && user_access('view uploaded files')) {
-        // Manipulate so that inline references work in preview
-        if (!variable_get('clean_url', 0)) {
-          $previews = array();
-          foreach ($node->files as $file) {
-            if (strpos($file->fid, 'upload') !== FALSE) {
-              $previews[] = $file;
-            }
+        // Add the attachments list to node body with a heavy
+        // weight to ensure they're below other elements
+        if (count($node->files)) {
+          if (!$teaser && user_access('view uploaded files')) {
+            $node->content['files'] = array(
+              '#value' => theme('upload_attachments', $node->files),
+              '#weight' => 50,
+            );
           }
-
-          // URLs to files being previewed are actually Drupal paths. When Clean
-          // URLs are disabled, the two do not match. We perform an automatic
-          // replacement from temporary to permanent URLs. That way, the author
-          // can use the final URL in the body before having actually saved (to
-          // place inline images for example).
-          foreach ($previews as $file) {
-            $old = file_create_filename($file->filename, file_create_path());
-            $new = url($old);
-            $node->body = str_replace($old, $new, $node->body);
-            $node->teaser = str_replace($old, $new, $node->teaser);
+          // Manipulate so that inline references work in preview
+          if (!variable_get('clean_url', 0)) {
+            $previews = array();
+            foreach ($node->files as $file) {
+              $file = (object)$file;
+              if (strpos($file->fid, 'upload') !== FALSE) {
+                $previews[] = $file;
+              }
+            }
+            // URLs to files being previewed are actually Drupal paths. When Clean
+            // URLs are disabled, the two do not match. We perform an automatic
+            // replacement from temporary to permanent URLs. That way, the author
+            // can use the final URL in the body before having actually saved (to
+            // place inline images for example).
+            foreach ($previews as $file) {
+              $old = file_create_filename($file->filename, file_create_path());
+              $node->content['#upload_urls'][$old] = url($old);
+            }
+            $node->content['#after_render'][] = 'upload_fix_preview_urls';
           }
         }
-
-        // Add the attachments list to node body
-        if (count($node->files) && !$teaser) {
-          $node->body .= theme('upload_attachments', $node->files);
-        }
       }
       break;
 
@@ -558,6 +562,18 @@ function upload_nodeapi(&$node, $op, $te
   }
 }
 
+function upload_fix_preview_urls($elements, &$content) {
+  if (is_array($elements['#upload_urls'])) {
+    $old_list = array();
+    $new_list = array();
+    foreach ($elements['#upload_urls'] as $old => $new) {
+      $old_list[] = $old;
+      $new_list[] = $new;
+    }
+    $content = str_replace($old_list, $new_list, $content);
+  }
+}
+
 /**
  * Displays file attachments in table
  */
