diff --git a/modules/comment/comment-wrapper.tpl.php b/modules/comment/comment-wrapper.tpl.php
index 313510d06b3342e6223dbb28422d27295c78618e..aabf3850fa8ca4610b37f0cc824f7dd6d80924ad 100644
--- a/modules/comment/comment-wrapper.tpl.php
+++ b/modules/comment/comment-wrapper.tpl.php
@@ -6,8 +6,9 @@
  * Default theme implementation to wrap comments.
  *
  * Available variables:
- * - $content: All comments for a given page. Also contains comment form
- *   if enabled.
+ * - $content: The array of content-related elements for the node. Use
+ *   render($content) to print them all, or
+ *   print a subset such as render($content['comment_form']).
  * - $classes: String of classes that can be used to style contextually through
  *   CSS. It can be manipulated through the variable $classes_array from
  *   preprocess functions. The default value has the following:
@@ -30,5 +31,16 @@
  */
 ?>
 <div id="comments" class="<?php print $classes; ?>">
-  <?php print $content; ?>
+  <?php if ($node->type != 'forum'): ?>
+    <h2 class="comments"><?php print t('Comments'); ?></h2>
+  <?php endif; ?>
+
+  <?php print render($content['comments']); ?>
+
+  <?php if ($content['comment_form']): ?>
+    <h2 class="title"><?php print t('Post new comment'); ?></h2>
+    <div>
+      <?php print render($content['comment_form']); ?>
+    </div>
+  <?php endif; ?>
 </div>
diff --git a/modules/comment/comment.module b/modules/comment/comment.module
index 3fc8663482f6f2ea47222c28bcac886244ab51e9..da6d8b7fe9d7200e5f1e68e684a5c594047c3959 100644
--- a/modules/comment/comment.module
+++ b/modules/comment/comment.module
@@ -104,34 +104,18 @@ function comment_theme() {
       'arguments' => array(),
     ),
     'comment_preview' => array(
-      'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array(), 'visible' => 1),
-    ),
-    'comment_view' => array(
-      'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array(), 'visible' => 1),
+      'arguments' => array('comment' => NULL),
     ),
     'comment' => array(
       'template' => 'comment',
-      'arguments' => array('comment' => NULL, 'node' => NULL, 'links' => array()),
-    ),
-    'comment_form_box' => array(
-      'arguments' => array('edit' => NULL, 'title' => NULL),
-    ),
-    'comment_folded' => array(
-      'template' => 'comment-folded',
-      'arguments' => array('comment' => NULL, 'node' => NULL),
-    ),
-    'comment_flat_expanded' => array(
-      'arguments' => array('comment' => NULL, 'node' => NULL),
-    ),
-    'comment_thread_expanded' => array(
-      'arguments' => array('comment' => NULL, 'node' => NULL),
+      'arguments' => array('elements' => NULL),
     ),
     'comment_post_forbidden' => array(
       'arguments' => array('nid' => NULL),
     ),
     'comment_wrapper' => array(
       'template' => 'comment-wrapper',
-      'arguments' => array('content' => NULL, 'node' => NULL),
+      'arguments' => array('content' => NULL),
     ),
     'comment_submitted' => array(
       'arguments' => array('comment' => NULL),
@@ -168,10 +152,12 @@ function comment_menu() {
     'access arguments' => array('administer comments'),
     'type' => MENU_CALLBACK,
   );
-  $items['comment/edit'] = array(
+  $items['comment/edit/%comment'] = array(
     'title' => 'Edit comment',
     'page callback' => 'comment_edit',
-    'access arguments' => array('post comments'),
+    'access callback' => 'comment_access',
+    'page arguments' => array(2),
+    'access arguments' => array('edit', 2),
     'type' => MENU_CALLBACK,
   );
   $items['comment/reply/%node'] = array(
@@ -532,16 +518,311 @@ function comment_node_view($node, $build_mode) {
       '#attributes' => array('class' => 'links inline'),
     );
 
-    // Append the list of comments to $node->content for node detail pages.
-    if ($node->comment && (bool)menu_get_object() && empty($node->in_preview)) {
-      $node->content['comments'] = array(
-        '#markup' => comment_render($node),
-        '#sorted' => TRUE,
-      );
+    // Only append comments when we are building a node on its own node detail
+    // page. We compare $node and $page_node to ensure that comments are not
+    // appended to other nodes shown on the page, for example a node_reference
+    // displayed in 'full' build mode within another node.
+    $page_node = menu_get_object();
+    if ($node->comment && isset($page_node->nid) && $page_node->nid == $node->nid && empty($node->in_preview) && user_access('access comments')) {
+      $node->content['comments'] = comment_node_page_additions($node);
     }
   }
 }
 
+/**
+ * Build the comment-related elements for node detail pages.
+ *
+ * @param $node
+ *  A node object.
+ */
+function comment_node_page_additions($node) {
+  $additions = array();
+
+  // Only attempt to render comments if the node has visible comments.
+  // Unpublished comments are not included in $node->comment_count, so show
+  // comments unconditionally if the user is an administrator.
+  if ($node->comment_count || user_access('administer comments')) {
+    if ($cids = comment_get_thread($node)) {
+      $comments = comment_load_multiple($cids);
+      comment_prepare_thread($comments);
+      $build = comment_build_multiple($comments);
+      $build['#attached_css'][] = drupal_get_path('module', 'comment') . '/comment.css';
+      $build['pager']['#theme'] = 'pager';
+      $additions['comments'] = $build;
+    }
+  }
+
+  // Append comment form if needed.
+  if (user_access('post comments') && $node->comment == COMMENT_NODE_OPEN && (variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW) == COMMENT_FORM_BELOW)) {
+    $build = drupal_get_form('comment_form', array('nid' => $node->nid));
+    $additions['comment_form'] = $build;
+  }
+
+  if ($additions) {
+    $additions += array(
+      '#theme' => 'comment_wrapper',
+      '#node' => $node,
+      'comments' => array(),
+      'comment_form' => array(),
+    );
+  }
+
+  return $additions;
+}
+
+/**
+ * Retrieve comment(s) for a thread.
+ *
+ * @param $node
+ *   The node whose comment(s) needs rendering.
+ *
+ * To display threaded comments in the correct order we keep a 'thread' field
+ * and order by that value. This field keeps this data in
+ * a way which is easy to update and convenient to use.
+ *
+ * A "thread" value starts at "1". If we add a child (A) to this comment,
+ * we assign it a "thread" = "1.1". A child of (A) will have "1.1.1". Next
+ * brother of (A) will get "1.2". Next brother of the parent of (A) will get
+ * "2" and so on.
+ *
+ * First of all note that the thread field stores the depth of the comment:
+ * depth 0 will be "X", depth 1 "X.X", depth 2 "X.X.X", etc.
+ *
+ * Now to get the ordering right, consider this example:
+ *
+ * 1
+ * 1.1
+ * 1.1.1
+ * 1.2
+ * 2
+ *
+ * If we "ORDER BY thread ASC" we get the above result, and this is the
+ * natural order sorted by time. However, if we "ORDER BY thread DESC"
+ * we get:
+ *
+ * 2
+ * 1.2
+ * 1.1.1
+ * 1.1
+ * 1
+ *
+ * Clearly, this is not a natural way to see a thread, and users will get
+ * confused. The natural order to show a thread by time desc would be:
+ *
+ * 2
+ * 1
+ * 1.2
+ * 1.1
+ * 1.1.1
+ *
+ * which is what we already did before the standard pager patch. To achieve
+ * this we simply add a "/" at the end of each "thread" value. This way, the
+ * thread fields will look like this:
+ *
+ * 1/
+ * 1.1/
+ * 1.1.1/
+ * 1.2/
+ * 2/
+ *
+ * we add "/" since this char is, in ASCII, higher than every number, so if
+ * now we "ORDER BY thread DESC" we get the correct order. However this would
+ * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need
+ * to consider the trailing "/" so we use a substring only.
+ */
+ function comment_get_thread($node) {
+  $mode = _comment_get_display_setting('mode', $node);
+  $comments_per_page = _comment_get_display_setting('comments_per_page', $node);
+
+  $query = db_select('comment', 'c')->extend('PagerDefault');
+  $query->addField('c', 'cid');
+  $query
+    ->condition('c.nid', $node->nid)
+    ->addTag('node_access')
+    ->limit($comments_per_page);
+
+  $count_query = db_select('comment', 'c');
+  $count_query->addExpression('COUNT(*)');
+  $count_query
+    ->condition('c.nid', $node->nid)
+    ->addTag('node_access');
+
+  if (!user_access('administer comments')) {
+    $query->condition('c.status', COMMENT_PUBLISHED);
+    $count_query->condition('c.status', COMMENT_PUBLISHED);
+  }
+  if ($mode === COMMENT_MODE_FLAT) {
+    $query->orderBy('c.cid', 'ASC');
+  }
+  else {
+    // See comment above. Analysis reveals that this doesn't cost too
+    // much. It scales much much better than having the whole comment
+    // structure.
+    $query->orderBy('SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))', 'ASC');
+  }
+
+  $query->setCountQuery($count_query);
+  $cids = $query->execute()->fetchCol();
+
+  return $cids;
+}
+
+/**
+ * Loop over comment thread, noting indentation level.
+ *
+ * @param array $comments
+ *   An array of comment objects, keyed by cid.
+ * @return
+ *   The $comments argument is altered by reference with indentation information.
+ */
+function comment_prepare_thread(&$comments) {
+  // A flag stating if we are still searching for first new comment on the thread.
+  $first_new = TRUE;
+
+  // A counter that helps track how indented we are.
+  $divs = 0;
+
+  foreach ($comments as $key => $comment) {
+    if ($first_new && $comment->new != MARK_READ) {
+      // Assign the anchor only for the first new comment. This avoids duplicate
+      // id attributes on a page.
+      $first_new = FALSE;
+      $comment->first_new = TRUE;
+    }
+
+    // The $divs element instructs #prefix whether to add an indent div or
+    // close existing divs (a negative value).
+    $comment->depth = count(explode('.', $comment->thread)) - 1;
+    if ($comment->depth > $divs) {
+      $comment->divs = 1;
+      $divs++;
+    }
+    else {
+      $comment->divs = $comment->depth - $divs;
+      while ($comment->depth < $divs) {
+        $divs--;
+      }
+    }
+    $comments[$key] = $comment;
+  }
+
+  // The final comment must close up some hanging divs
+  $comments[$key]->divs_final = $divs;
+}
+
+/**
+ * Generate an array for rendering the given comment.
+ *
+ * @param $comment
+ *   A comment object.
+ * @param $build_mode
+ *   Build mode, e.g. 'full', 'teaser'...
+ *
+ * @return
+ *   An array as expected by drupal_render().
+ */
+function comment_build($comment, $build_mode = 'full') {
+  $node = node_load($comment->nid);
+  $comment = comment_build_content($comment, $build_mode);
+
+  $build = $comment->content;
+
+  $build += array(
+    '#theme' => 'comment',
+    '#comment' => $comment,
+    '#build_mode' => $build_mode,
+  );
+
+  $prefix = '';
+  $is_threaded = isset($comment->divs) && _comment_get_display_setting('mode', $node) == COMMENT_MODE_THREADED;
+
+  // Add 'new' anchor if needed.
+  if (!empty($comment->first_new)) {
+    $prefix .= "<a id=\"new\"></a>\n";
+  }
+
+  // Add indentation div or close open divs as needed.
+  if ($is_threaded) {
+    $prefix .= $comment->divs <= 0 ? str_repeat('</div>', abs($comment->divs)) : "\n" . '<div class="indented">';
+  }
+
+  // Add anchor for each comment.
+  $prefix .= "<a id=\"comment-$comment->cid\"></a>\n";
+  $build['#prefix'] = $prefix;
+
+  // Close all open divs.
+  if ($is_threaded && !empty($comment->divs_final)) {
+    $build['#suffix'] = str_repeat('</div>', $comment->divs_final);
+  }
+
+  return $build;
+}
+
+/**
+ * Builds a structured array representing the comment's content.
+ *
+ * The content built for the comment (field values, comments, file attachments or
+ * other comment components) will vary depending on the $build_mode parameter.
+ *
+ * @param $comment
+ *   A comment object.
+ * @param $build_mode
+ *   Build mode, e.g. 'full', 'teaser'...
+ * @return
+ *   A structured array containing the individual elements
+ *   of the comment's content.
+ */
+function comment_build_content($comment, $build_mode = 'full') {
+  if (empty($comment->content)) {
+    $comment->content = array();
+  }
+
+  // Build comment body.
+  $comment->content['comment_body'] = array(
+    '#markup' => check_markup($comment->comment, $comment->format, '', FALSE),
+  );
+
+  if (empty($comment->in_preview)) {
+    $comment->content['links']['comment'] = array(
+      '#theme' => 'links',
+      '#links' => comment_links($comment),
+      '#attributes' => array('class' => 'links inline'),
+    );
+  }
+
+  // Allow modules to make their own additions to the comment.
+  module_invoke_all('comment_view', $comment, $build_mode);
+
+  // Allow modules to modify the structured comment.
+  drupal_alter('comment_build', $comment, $build_mode);
+
+  return $comment;
+}
+
+/**
+ * Construct a drupal_render() style array from an array of loaded comments.
+ *
+ * @param $comments
+ *   An array of comments as returned by comment_load_multiple().
+ * @param $build_mode
+ *   Build mode, e.g. 'full', 'teaser'...
+ * @param $weight
+ *   An integer representing the weight of the first comment in the list.
+ * @return
+ *   An array in the format expected by drupal_render().
+ */
+function comment_build_multiple($comments, $build_mode = 'full', $weight = 0) {
+  $build = array(
+    '#sorted' => TRUE,
+  );
+  foreach ($comments as $comment) {
+    $build[$comment->cid] = comment_build($comment, $build_mode);
+    $build[$comment->cid]['#weight'] = $weight;
+    $weight++;
+  }
+  return $build;
+}
+
 /**
  * Implement hook_form_FORM_ID_alter().
  */
@@ -878,7 +1159,7 @@ function comment_save($comment) {
   }
   else {
     // Add the comment to database. This next section builds the thread field.
-    // Also see the documentation for comment_render().
+    // Also see the documentation for comment_build().
     if ($comment->pid == 0) {
       // This is a comment with no parent comment (depth 0): we start
       // by retrieving the maximum thread level.
@@ -977,24 +1258,13 @@ function comment_link($type, $object, $build_mode) {
  *
  * @param $comment
  *   The comment to which the links will be related.
- * @param $return
- *   Not used.
  * @return
  *   An associative array containing the links.
  */
-function comment_links($comment, $return = 1) {
+function comment_links(&$comment) {
   global $user;
   $links = array();
 
-  // If viewing just this comment, link back to the in-context view.
-  if ($return) {
-    $links['comment_parent'] = array(
-      'title' => t('parent'),
-      'href' => 'comment/' . $comment->cid,
-      'fragment' => "comment-$comment->cid"
-    );
-  }
-
   $node = node_load($comment->nid);
   if ($node->comment == COMMENT_NODE_OPEN) {
     if (user_access('administer comments') && user_access('post comments')) {
@@ -1042,181 +1312,9 @@ function comment_links($comment, $return = 1) {
     }
   }
 
-  return $links;
-}
-
-/**
- * Renders comment(s).
- *
- * @param $node
- *   The node which comment(s) needs rendering.
- * @param $cid
- *   Optional, if given, only one comment is rendered.
- *
- * To display threaded comments in the correct order we keep a 'thread' field
- * and order by that value. This field keeps this data in
- * a way which is easy to update and convenient to use.
- *
- * A "thread" value starts at "1". If we add a child (A) to this comment,
- * we assign it a "thread" = "1.1". A child of (A) will have "1.1.1". Next
- * brother of (A) will get "1.2". Next brother of the parent of (A) will get
- * "2" and so on.
- *
- * First of all note that the thread field stores the depth of the comment:
- * depth 0 will be "X", depth 1 "X.X", depth 2 "X.X.X", etc.
- *
- * Now to get the ordering right, consider this example:
- *
- * 1
- * 1.1
- * 1.1.1
- * 1.2
- * 2
- *
- * If we "ORDER BY thread ASC" we get the above result, and this is the
- * natural order sorted by time. However, if we "ORDER BY thread DESC"
- * we get:
- *
- * 2
- * 1.2
- * 1.1.1
- * 1.1
- * 1
- *
- * Clearly, this is not a natural way to see a thread, and users will get
- * confused. The natural order to show a thread by time desc would be:
- *
- * 2
- * 1
- * 1.2
- * 1.1
- * 1.1.1
- *
- * which is what we already did before the standard pager patch. To achieve
- * this we simply add a "/" at the end of each "thread" value. This way, the
- * thread fields will look like this:
- *
- * 1/
- * 1.1/
- * 1.1.1/
- * 1.2/
- * 2/
- *
- * we add "/" since this char is, in ASCII, higher than every number, so if
- * now we "ORDER BY thread DESC" we get the correct order. However this would
- * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need
- * to consider the trailing "/" so we use a substring only.
- */
-function comment_render($node, $cid = 0) {
-  global $user;
-  $output = '';
-
-  if (user_access('access comments')) {
-    // Pre-process variables.
-    $nid = $node->nid;
-    if (empty($nid)) {
-      $nid = 0;
-    }
-
-    $mode = _comment_get_display_setting('mode', $node);
-    $comments_per_page = _comment_get_display_setting('comments_per_page', $node);
 
-    if ($cid && is_numeric($cid)) {
-      $comment = current(comment_load_multiple(array('cid' => $cid, 'status' => COMMENT_PUBLISHED)));
-      // Single comment view.
-      if ($comment) {
-        $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
-        $links = module_invoke_all('link', 'comment', $comment, 1);
-        drupal_alter('link', $links, $node);
-
-        $output .= theme('comment_view', $comment, $node, $links);
-      }
-    }
-    // Only attempt to render comments if the node has visible comments.
-    // Unpublished comments are not included in $node->comment_count, so show
-    // comments unconditionally if the user is an administrator.
-    elseif ($node->comment_count || user_access('administer comments')) {
-
-      // Multiple comment view.
-      $query = db_select('comment', 'c')->extend('PagerDefault');
-      $query->addField('c', 'cid');
-      $query
-        ->condition('c.nid', $nid)
-        ->addTag('node_access')
-        ->limit($comments_per_page);
-
-      $count_query = db_select('comment', 'c');
-      $count_query->addExpression('COUNT(*)');
-      $count_query
-        ->condition('c.nid', $nid)
-        ->addTag('node_access');
 
-      if (!user_access('administer comments')) {
-        $query->condition('c.status', COMMENT_PUBLISHED);
-        $count_query->condition('c.status', COMMENT_PUBLISHED);
-      }
-      if ($mode === COMMENT_MODE_FLAT) {
-        $query->orderBy('c.cid', 'ASC');
-      }
-      else {
-        // See comment above. Analysis reveals that this doesn't cost too
-        // much. It scales much much better than having the whole comment
-        // structure.
-        $query->orderBy('SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))', 'ASC');
-      }
-
-      $query->setCountQuery($count_query);
-      $cids = $query->execute()->fetchCol();
-
-      $divs = 0;
-      $num_rows = FALSE;
-      $render = '';
-      $comments = comment_load_multiple($cids);
-      drupal_add_css(drupal_get_path('module', 'comment') . '/comment.css');
-      foreach ($comments as $comment) {
-        $comment = drupal_unpack($comment);
-        $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
-        $comment->depth = count(explode('.', $comment->thread)) - 1;
-
-        if ($mode == COMMENT_MODE_THREADED) {
-          if ($comment->depth > $divs) {
-            $divs++;
-            $render .= '<div class="indented">';
-          }
-          else {
-            while ($comment->depth < $divs) {
-              $divs--;
-              $render .= '</div>';
-            }
-          }
-        }
-
-        if ($mode == COMMENT_MODE_FLAT) {
-          $render .= theme('comment_flat_expanded', $comment, $node);
-        }
-        elseif ($mode == COMMENT_MODE_THREADED) {
-          $render .= theme('comment_thread_expanded', $comment, $node);
-        }
-        $num_rows = TRUE;
-      }
-      while ($divs-- > 0) {
-        $render .= '</div>';
-      }
-      $output .= $render;
-      $output .= theme('pager', NULL);
-    }
-
-    // If enabled, show new comment form if it's not already being displayed.
-    $reply = arg(0) == 'comment' && arg(1) == 'reply';
-    if (user_access('post comments') && $node->comment == COMMENT_NODE_OPEN && (variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW) == COMMENT_FORM_BELOW) && !$reply) {
-      $output .= theme('comment_form_box', array('nid' => $nid), t('Post new comment'));
-    }
-    if ($output) {
-      $output = theme('comment_wrapper', $output, $node);
-    }
-  }
-
-  return $output;
+  return $links;
 }
 
 /**
@@ -1287,6 +1385,14 @@ function comment_load_multiple($cids = array(), $conditions = array()) {
     $comments = $query->execute()->fetchAllAssoc('cid');
   }
 
+  // Setup standard comment properties.
+  foreach ($comments as $key => $comment) {
+    $comment = drupal_unpack($comment);
+    $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
+    $comment->new = node_mark($comment->nid, $comment->timestamp);
+    $comments[$key] = $comment;
+  }
+
   // Invoke hook_comment_load().
   if (!empty($comments)) {
     module_invoke_all('comment_load', $comments);
@@ -1396,7 +1502,7 @@ function comment_get_display_ordinal($cid, $node_type) {
   else {
     // For threaded comments, the c.thread column is used for ordering. We can
     // use the vancode for comparison, but must remove the trailing slash.
-    // @see comment_render().
+    // @see comment_build_multiple().
     $query->where('SUBSTRING(c1.thread, 1, (LENGTH(c1.thread) -1)) < SUBSTRING(c2.thread, 1, (LENGTH(c2.thread) -1))');
   }
 
@@ -1425,20 +1531,18 @@ function comment_get_display_page($cid, $node_type) {
 /**
  * Generate the basic commenting form, for appending to a node or display on a separate page.
  *
- * @param $title
- *   Not used.
  * @ingroup forms
  * @see comment_form_validate()
  * @see comment_form_submit()
  */
-function comment_form(&$form_state, $edit, $title = NULL) {
+function comment_form(&$form_state, $edit = array()) {
   global $user;
 
   $op = isset($_POST['op']) ? $_POST['op'] : '';
   $node = node_load($edit['nid']);
 
   if (!$user->uid && variable_get('comment_anonymous_' . $node->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT) != COMMENT_ANONYMOUS_MAYNOT_CONTACT) {
-    drupal_add_js(drupal_get_path('module', 'comment') . '/comment.js');
+    $form_state['#attached_js'][] = drupal_get_path('module', 'comment') . '/comment.js';
   }
 
   // Take into account multi-step rebuilding.
@@ -1675,22 +1779,6 @@ function comment_form(&$form_state, $edit, $title = NULL) {
   return $form;
 }
 
-/**
- * Theme the comment form box.
- *
- * @param $edit
- *   The form structure.
- * @param $title
- *   The form title.
- * @return
- *   A string containing the box output.
- */
-function theme_comment_form_box($edit, $title = NULL) {
-  $content = drupal_render(drupal_get_form('comment_form', $edit, $title));
-  $output = '<h2 class="title">' . $title . '</h2><div>' . $content . '</div>';
-  return $output;
-}
-
 /**
  * Build a preview from submitted form values.
  */
@@ -1729,38 +1817,31 @@ function comment_preview($comment) {
     }
 
     $comment->timestamp = !empty($edit['timestamp']) ? $edit['timestamp'] : REQUEST_TIME;
-    $output = theme('comment_view', $comment, $node);
-  }
-  else {
-    $output = '';
-  }
+    $comment->in_preview = TRUE;
+    $comment_build = comment_build($comment);
+    $comment_build += array(
+      '#weight' => -100,
+      '#prefix' => '<div class="preview">',
+      '#suffix' => '</div>',
+    );
 
-  $form['comment_preview'] = array(
-    '#markup' => $output,
-    '#weight' => -100,
-    '#prefix' => '<div class="preview">',
-    '#suffix' => '</div>',
-  );
+    $form['comment_preview'] = $comment_build;
+  }
 
   if ($comment->pid) {
     $parent_comment = db_query('SELECT c.*, u.uid, u.name AS registered_name, u.signature, u.picture, u.data FROM {comment} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = :cid AND c.status = :status', array(
       ':cid' => $comment->pid,
       ':status' => COMMENT_PUBLISHED,
       ))->fetchObject();
-    $parent_comment = drupal_unpack($parent_comment);
-    $parent_comment->name = $parent_comment->uid ? $parent_comment->registered_name : $parent_comment->name;
-    $output_below = theme('comment_view', $parent_comment, $node);
+
+    $build = comment_build($parent_comment);
   }
   else {
-    $suffix = empty($form['#suffix']) ? '' : $form['#suffix'];
-    $form['#suffix'] = $suffix . drupal_render(node_build($node));
-    $output_below = '';
+    $build = node_build($node);
   }
 
-  $form['comment_preview_below'] = array(
-    '#markup' => $output_below,
-    '#weight' => 100,
-  );
+  $form['comment_output_below'] = $build;
+  $form['comment_output_below']['#weight'] = 100;
 
   return $form;
 }
@@ -1904,68 +1985,26 @@ function comment_form_submit($form, &$form_state) {
   $form_state['redirect'] = $redirect;
 }
 
-/**
- * Theme a single comment block.
- *
- * @param $comment
- *   The comment object.
- * @param $node
- *   The comment node.
- * @param $links
- *   An associative array containing control links.
- * @param $visible
- *   Switches between folded/unfolded view.
- * @ingroup themeable
- */
-function theme_comment_view($comment, $node, $links = array(), $visible = TRUE) {
-  $first_new = &drupal_static(__FUNCTION__, TRUE);
-  $comment->new = node_mark($comment->nid, $comment->timestamp);
-  $output = '';
-
-  if ($first_new && $comment->new != MARK_READ) {
-    // Assign the anchor only for the first new comment. This avoids duplicate
-    // id attributes on a page.
-    $first_new = FALSE;
-    $output .= "<a id=\"new\"></a>\n";
-  }
-
-  $output .= "<a id=\"comment-$comment->cid\"></a>\n";
-
-  // Switch to folded/unfolded view of the comment.
-  if ($visible) {
-    $comment->comment = check_markup($comment->comment, $comment->format, '', FALSE);
-    // Comment API hook.
-    module_invoke_all('comment_view', $comment);
-    $output .= theme('comment', $comment, $node, $links);
-  }
-  else {
-    $output .= theme('comment_folded', $comment, $node);
-  }
-
-  return $output;
-}
-
 /**
  * Process variables for comment.tpl.php.
  *
  * @see comment.tpl.php
- * @see theme_comment()
  */
 function template_preprocess_comment(&$variables) {
-  $comment = $variables['comment'];
-  $node = $variables['node'];
+  $comment = $variables['elements']['#comment'];
+  $variables['comment']   = $comment;
+  $variables['node']      = node_load($comment->nid);
   $variables['author']    = theme('username', $comment);
-  $variables['content']   = $comment->comment;
+  $variables['content']   = $comment->content;
   $variables['date']      = format_date($comment->timestamp);
-  $variables['links']     = isset($variables['links']) ? theme('links', $variables['links']) : '';
-  $variables['new']       = $comment->new ? t('new') : '';
+  $variables['new']       = !empty($comment->new) ? t('new') : '';
   $variables['picture']   = theme_get_setting('toggle_comment_user_picture') ? theme('user_picture', $comment) : '';
   $variables['signature'] = $comment->signature;
   $variables['submitted'] = theme('comment_submitted', $comment);
   $variables['title']     = l($comment->subject, 'comment/' . $comment->cid, array('fragment' => "comment-$comment->cid"));
-  $variables['template_files'][] = 'comment-' . $node->type;
+  $variables['template_files'][] = 'comment-' . $variables['node']->type;
   // Set status to a string representation of comment->status.
-  if (isset($comment->preview)) {
+  if (isset($comment->in_preview)) {
     $variables['status']  = 'comment-preview';
   }
   else {
@@ -1986,71 +2025,12 @@ function template_preprocess_comment(&$variables) {
     if ($comment->uid === $variables['user']->uid) {
       $variables['classes_array'][] = 'comment-by-viewer';
     }
-    if ($comment->new) {
+    if ($variables['new']) {
       $variables['classes_array'][] = 'comment-new';
     }
   }
 }
 
-/**
- * Process variables for comment-folded.tpl.php.
- *
- * @see comment-folded.tpl.php
- * @see theme_comment_folded()
- */
-function template_preprocess_comment_folded(&$variables) {
-  $comment = $variables['comment'];
-  $variables['author'] = theme('username', $comment);
-  $variables['date']   = format_date($comment->timestamp);
-  $variables['new']    = $comment->new ? t('new') : '';
-  $variables['title']  = l($comment->subject, 'comment/' . $comment->cid, array('fragment' => "comment-$comment->cid"));
-  // Gather comment classes.
-  if ($comment->uid === 0) {
-    $variables['classes_array'][] = 'comment-by-anonymous';
-  }
-  else {
-    if ($comment->status == COMMENT_NOT_PUBLISHED) {
-      $variables['classes_array'][] = 'comment-unpublished';
-    }
-    if ($comment->uid === $variables['node']->uid) {
-      $variables['classes_array'][] = 'comment-by-node-author';
-    }
-    if ($comment->uid === $variables['user']->uid) {
-      $variables['classes_array'][] = 'comment-by-viewer';
-    }
-    if ($comment->new) {
-      $variables['classes_array'][] = 'comment-new';
-    }
-  }
-
-}
-
-/**
- * Theme comment flat expanded view.
- *
- * @param $comment
- *   The comment to be themed.
- * @param $node
- *   The comment node.
- * @ingroup themeable
- */
-function theme_comment_flat_expanded($comment, $node) {
-  return theme('comment_view', $comment, $node, module_invoke_all('link', 'comment', $comment, 0));
-}
-
-/**
- * Theme comment thread expanded view.
- *
- * @param $comment
- *   The comment to be themed.
- * @param $node
- *   The comment node.
- * @ingroup themeable
- */
-function theme_comment_thread_expanded($comment, $node) {
-  return theme('comment_view', $comment, $node, module_invoke_all('link', 'comment', $comment, 0));
-}
-
 /**
  * Theme a "you can't post comments" notice.
  *
@@ -2096,7 +2076,8 @@ function theme_comment_post_forbidden($node) {
  */
 function template_preprocess_comment_wrapper(&$variables) {
   // Provide contextual information.
-  $variables['display_mode']  = _comment_get_display_setting('mode', $variables['node']);
+  $variables['node'] = $variables['content']['#node'];
+  $variables['display_mode'] = _comment_get_display_setting('mode', $variables['node']);
   $variables['template_files'][] = 'comment-wrapper-' . $variables['node']->type;
 }
 
diff --git a/modules/comment/comment.pages.inc b/modules/comment/comment.pages.inc
index 5748c4f46f1c9a1ecc71e17cf81fce7d2ef56380..dc18f803516f39a96a60921009cb31068522e9d4 100644
--- a/modules/comment/comment.pages.inc
+++ b/modules/comment/comment.pages.inc
@@ -7,24 +7,14 @@
  */
 
 /**
- * Form builder; generate a comment editing form.
+ * A menu callback; build a comment editing form.
  *
- * @param $cid
- *   ID of the comment to be edited.
+ * @param $comment
+ *   The comment to be edited.
  * @ingroup forms
  */
-function comment_edit($cid) {
-  global $user;
-  $comment = db_query('SELECT c.*, u.uid, u.name AS registered_name, u.data FROM {comment} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = :cid', array(':cid' => $cid))->fetchObject();
-  $comment = drupal_unpack($comment);
-  $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
-
-  if (comment_access('edit', $comment)) {
-    return theme('comment_form_box', (array)$comment);
-  }
-  else {
-    drupal_access_denied();
-  }
+function comment_edit($comment) {
+  return drupal_get_form('comment_form', (array)$comment);
 }
 
 /**
@@ -52,13 +42,13 @@ function comment_reply($node, $pid = NULL) {
   // Set the breadcrumb trail.
   drupal_set_breadcrumb(array(l(t('Home'), NULL), l($node->title, 'node/' . $node->nid)));
   $op = isset($_POST['op']) ? $_POST['op'] : '';
-  $output = '';
+  $build = array();
 
   if (user_access('access comments')) {
     // The user is previewing a comment prior to submitting it.
     if ($op == t('Preview')) {
       if (user_access('post comments')) {
-        $output .= theme('comment_form_box', array('pid' => $pid, 'nid' => $node->nid), NULL);
+        $build['comment_form'] = drupal_get_form('comment_form', array('pid' => $pid, 'nid' => $node->nid));
       }
       else {
         drupal_set_message(t('You are not authorized to post comments.'), 'error');
@@ -84,7 +74,7 @@ function comment_reply($node, $pid = NULL) {
           // Display the parent comment
           $comment = drupal_unpack($comment);
           $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
-          $output .= theme('comment_view', $comment, $node);
+          $build['comment_parent'] = comment_build($comment);
         }
         else {
           drupal_set_message(t('The comment you are replying to does not exist.'), 'error');
@@ -93,7 +83,7 @@ function comment_reply($node, $pid = NULL) {
       }
       // This is the case where the comment is in response to a node. Display the node.
       elseif (user_access('access content')) {
-        $output .= drupal_render(node_build($node));
+        $build['comment_node'] = node_build($node);
       }
 
       // Should we show the reply box?
@@ -102,7 +92,8 @@ function comment_reply($node, $pid = NULL) {
         drupal_goto("node/$node->nid");
       }
       elseif (user_access('post comments')) {
-        $output .= theme('comment_form_box', array('pid' => $pid, 'nid' => $node->nid), t('Add new comment'));
+        $edit = array('nid' => $node->nid, 'pid' => $pid);
+        $build['comment_form'] = drupal_get_form('comment_form', $edit);
       }
       else {
         drupal_set_message(t('You are not authorized to post comments.'), 'error');
@@ -115,11 +106,11 @@ function comment_reply($node, $pid = NULL) {
     drupal_goto("node/$node->nid");
   }
 
-  return $output;
+  return $build;
 }
 
 /**
- * Publish specified comment.
+ * Menu callback; publish specified comment.
  *
  * @param $cid ID of comment to be published.
  */
diff --git a/modules/comment/comment.test b/modules/comment/comment.test
index 146a8538cd7b52f66223f5df052e067ac2711f4f..082d8ed228070f5bc1c50aad7f7eb6e3ea651804 100644
--- a/modules/comment/comment.test
+++ b/modules/comment/comment.test
@@ -76,7 +76,7 @@ class CommentHelperCase extends DrupalWebTestCase {
       $regex .= '<div(.*?)'; // Begin in comment div.
       $regex .= $comment->subject . '(.*?)'; // Match subject.
       $regex .= $comment->comment . '(.*?)'; // Match comment.
-      $regex .= '<\/div>/s'; // Dot matches newlines and ensure that match doesn't bleed outside comment div.
+      $regex .= '/s';
 
       return (boolean)preg_match($regex, $this->drupalGetContent());
     }
@@ -446,7 +446,7 @@ class CommentAnonymous extends CommentHelperCase {
     // "Login or register to post comments" type link may be shown.
     $this->drupalLogout();
     $this->drupalGet('node/' . $this->node->nid);
-    $this->assertNoRaw('<div id="comments">', t('Comments were not displayed.'));
+    $this->assertNoPattern('/<div ([^>]*?)id="comments"([^>]*?)>/', t('Comments were not displayed.'));
     $this->assertNoLink('Add new comment', t('Link to add comment was found.'));
 
     // Attempt to view node-comment form while disallowed.
@@ -459,7 +459,7 @@ class CommentAnonymous extends CommentHelperCase {
     $this->setAnonymousUserComment(TRUE, FALSE, FALSE);
     $this->drupalLogout();
     $this->drupalGet('node/' . $this->node->nid);
-    $this->assertRaw('<div id="comments">', t('Comments were displayed.'));
+    $this->assertPattern('/<div ([^>]*?)id="comments"([^>]*?)>/', t('Comments were displayed.'));
     $this->assertLink('Login', 1, t('Link to login was found.'));
     $this->assertLink('register', 1, t('Link to register was found.'));
   }
diff --git a/modules/comment/comment.tpl.php b/modules/comment/comment.tpl.php
index 07360a04436deb8b875ddcddc39761b07c7e236d..abcca937803dbf7c95857a51df5d6208dbf9b3ec 100644
--- a/modules/comment/comment.tpl.php
+++ b/modules/comment/comment.tpl.php
@@ -7,9 +7,11 @@
  *
  * Available variables:
  * - $author: Comment author. Can be link or plain text.
- * - $content: Body of the post.
+ * - $content: An array of comment items. Use render($content) to print them all, or
+ *   print a subset such as render($content['field_example']). Use
+ *   hide($content['field_example']) to temporarily suppress the printing of a
+ *   given element.
  * - $date: Date and time of posting.
- * - $links: Various operational links.
  * - $new: New comment marker.
  * - $picture: Authors picture.
  * - $signature: Authors signature.
@@ -46,7 +48,7 @@
 <div class="<?php print $classes; ?> clearfix">
   <?php print $picture ?>
 
-  <?php if ($comment->new): ?>
+  <?php if ($new): ?>
     <span class="new"><?php print $new ?></span>
   <?php endif; ?>
 
@@ -57,7 +59,11 @@
   </div>
 
   <div class="content">
-    <?php print $content ?>
+    <?php
+      // We hide the comments and links now so that we can render them later.
+      hide($content['links']);
+      print render($content);
+    ?>
     <?php if ($signature): ?>
     <div class="user-signature clearfix">
       <?php print $signature ?>
@@ -65,5 +71,5 @@
     <?php endif; ?>
   </div>
 
-  <?php print $links ?>
+  <?php print render($content['links']) ?>
 </div>
diff --git a/themes/garland/comment.tpl.php b/themes/garland/comment.tpl.php
index e2edf0dc0c94fdbebb843341d4535b2362271dc4..c9daefc003c1a408d61827568479301e34bd52d4 100644
--- a/themes/garland/comment.tpl.php
+++ b/themes/garland/comment.tpl.php
@@ -8,7 +8,7 @@
     <span class="submitted"><?php print $submitted; ?></span>
   <?php endif; ?>
 
-  <?php if ($comment->new) : ?>
+  <?php if ($new) : ?>
     <span class="new"><?php print drupal_ucfirst($new) ?></span>
   <?php endif; ?>
 
@@ -17,7 +17,7 @@
     <h3><?php print $title ?></h3>
 
     <div class="content">
-      <?php print $content ?>
+      <?php hide($content['links']); print render($content); ?>
       <?php if ($signature): ?>
       <div class="clearfix">
         <div>—</div>
@@ -27,7 +27,5 @@
     </div>
   </div>
 
-  <?php if ($links): ?>
-    <div class="links"><?php print $links ?></div>
-  <?php endif; ?>
+  <?php print render($content['links']) ?>
 </div>
diff --git a/themes/garland/template.php b/themes/garland/template.php
index 24d1e64e334e33a3d946556fe4d149bc22a429ef..e1be8efc075e4c89f5b9b77e5284addd052ec981 100644
--- a/themes/garland/template.php
+++ b/themes/garland/template.php
@@ -14,18 +14,6 @@ function garland_breadcrumb($breadcrumb) {
   }
 }
 
-/**
- * Allow themable wrapping of all comments.
- */
-function garland_comment_wrapper($content, $node) {
-  if (!$content || $node->type == 'forum') {
-    return '<div id="comments">' . $content . '</div>';
-  }
-  else {
-    return '<div id="comments"><h2 class="comments">' . t('Comments') . '</h2>' . $content . '</div>';
-  }
-}
-
 /**
  * Override or insert variables into the page template.
  */