Permit the administrator to delete a single comment, without
also deleting all of its descendents.

http://drupal.org/node/11877

I still can't believe Drupal thinks it's a good idea to force an editor,
who wants to remove one little piece of content, to also remove other
content, sight unseen. This is Drupal's biggest flaw -- it's should be a
content-MANAGEMENT system, not a content-ARBITRARILY-DISCARDING system.


--- /Users/kevin/doc/dl/drupal-4.6.0/modules/comment.module	Fri Apr  8 10:00:02 2005
+++ comment.module	Thu Apr 14 11:57:37 2005
@@ -557,76 +557,7 @@ function comment_post($edit) {
 
         // Here we are building the thread field.  See the comment
         // in comment_render().
-        if ($edit['pid'] == 0) {
-          // This is a comment with no parent comment (depth 0): we start
-          // by retrieving the maximum thread level.
-          $max = db_result(db_query('SELECT MAX(thread) FROM {comments} WHERE nid = %d', $edit['nid']));
-
-          // Strip the "/" from the end of the thread.
-          $max = rtrim($max, '/');
-
-          // Next, we increase this value by one.  Note that we can't
-          // use 1, 2, 3, ... 9, 10, 11 because we order by string and
-          // 10 would be right after 1.  We use 1, 2, 3, ..., 9, 91,
-          // 92, 93, ... instead.  Ugly but fast.
-          $decimals = (string) substr($max, 0, strlen($max) - 1);
-          $units = substr($max, -1, 1);
-          if ($units) {
-            $units++;
-          }
-          else {
-            $units = 1;
-          }
-
-          if ($units == 10) {
-            $units = '90';
-          }
-
-          // Finally, build the thread field for this new comment.
-          $thread = $decimals . $units .'/';
-        }
-        else {
-          // This is comment with a parent comment: we increase
-          // the part of the thread value at the proper depth.
-
-          // Get the parent comment:
-          $parent = db_fetch_object(db_query('SELECT * FROM {comments} WHERE cid = %d', $edit['pid']));
-
-          // Strip the "/" from the end of the parent thread.
-          $parent->thread = (string) rtrim((string) $parent->thread, '/');
-
-          // Get the max value in _this_ thread.
-          $max = db_result(db_query("SELECT MAX(thread) FROM {comments} WHERE thread LIKE '%s.%%' AND nid = %d", $parent->thread, $edit['nid']));
-
-          if ($max == '') {
-            // First child of this parent.
-            $thread = $parent->thread .'.1/';
-          }
-          else {
-            // Strip the "/" at the end of the thread.
-            $max = rtrim($max, '/');
-
-            // We need to get the value at the correct depth.
-            $parts = explode('.', $max);
-            $parent_depth = count(explode('.', $parent->thread));
-            $last = $parts[$parent_depth];
-
-            // Next, we increase this value by one.  Note that we can't
-            // use 1, 2, 3, ... 9, 10, 11 because we order by string and
-            // 10 would be right after 1.  We use 1, 2, 3, ..., 9, 91,
-            // 92, 93, ... instead.  Ugly but fast.
-            $decimals = (string)substr($last, 0, strlen($last) - 1);
-            $units = substr($last, -1, 1);
-            $units++;
-            if ($units == 10) {
-              $units = '90';
-            }
-
-            // Finally, build the thread field for this new comment.
-            $thread = $parent->thread .'.'. $decimals . $units .'/';
-          }
-        }
-
+		$thread = comment_thread($edit['nid'], $edit['pid']);
 
         $edit['cid'] = db_next_id('{comments}_cid');
         $edit['timestamp'] = time();
@@ -669,6 +600,96 @@ function comment_post($edit) {
   }
 }
 
+function comment_thread($nid, $pid) {
+  if ($pid == 0) {
+    /*
+    ** This is a comment with no parent comment (depth 0): we start
+    ** by retrieving the maximum thread level.
+    */
+
+    $max = db_result(db_query('SELECT MAX(thread) FROM {comments} WHERE nid = %d', $nid));
+
+    // Strip the "/" from the end of the thread.
+    $max = rtrim($max, '/');
+    if (($dot = strpos($max, '.')) !== FALSE) {
+      $max = substr($max, 0, $dot);
+    }
+
+    /*
+    ** Next, we increase this value by one.  Note that we can't
+    ** use 1, 2, 3, ... 9, 10, 11 because we order by string and
+    ** 10 would be right after 1.  We use 1, 2, 3, ..., 9, 91,
+    ** 92, 93, ... instead.  Ugly but fast.
+    */
+
+    $decimals = (string)substr($max, 0, strlen($max) - 1);
+    $units = substr($max, -1, 1);
+    if ($units) {
+      $units++;
+    }
+    else {
+      $units = 1;
+    }
+
+    if ($units == 10) {
+      $units = '90';
+    }
+
+    // Finally, build the thread field for this new comment.
+    $thread = "$decimals$units/";
+  }
+  else {
+    /*
+    ** This is comment with a parent comment: we increase
+    ** the part of the thread value at the proper depth.
+    */
+
+    // Get the parent comment:
+    $parent = db_fetch_object(db_query('SELECT * FROM {comments} WHERE cid = %d', $pid));
+
+    // Strip the "/" from the end of the parent thread.
+    $parent->thread = (string)rtrim((string)$parent->thread, '/');
+
+    // Get the max value in _this_ thread.
+    $max = db_result(db_query("SELECT MAX(thread) FROM {comments} WHERE thread LIKE '%s.%%' AND nid = %d", $parent->thread, $nid));
+
+    if ($max == '') {
+      // First child of this parent.
+      $thread = "$parent->thread.1/";
+    }
+    else {
+      // Strip the "/" at the end of the thread.
+      $max = rtrim($max, '/');
+      // We need to get the value at the correct depth.
+      $parts = explode('.', $max);
+      $parent_depth = count(explode('.', $parent->thread));
+      $last = $parts[$parent_depth];
+      // We need to get the value at the correct depth.
+      $parts = explode('.', $max);
+      $parent_depth = count(explode('.', $parent->thread));
+      $last = $parts[$parent_depth];
+
+      /*
+      ** Next, we increase this value by one.  Note that we can't
+      ** use 1, 2, 3, ... 9, 10, 11 because we order by string and
+      ** 10 would be right after 1.  We use 1, 2, 3, ..., 9, 91,
+      ** 92, 93, ... instead.  Ugly but fast.
+      */
+
+      $decimals = (string)substr($last, 0, strlen($last) - 1);
+      $units = substr($last, -1, 1);
+      $units++;
+      if ($units == 10) {
+        $units = '90';
+      }
+
+      // Finally, build the thread field for this new comment.
+      $thread = "$parent->thread.". $decimals.$units .'/';
+    }
+  }
+  return $thread;
+}
+
 function comment_links($comment, $return = 1) {
   global $user;
 
@@ -961,10 +982,18 @@ function comment_delete($cid) {
   // We'll only delete if the user has confirmed the
   // deletion using the form in our else clause below.
   if ($comment->cid && $_POST['edit']['confirm']) {
-    drupal_set_message(t('The comment and all its replies have been deleted.'));
+		if ($_POST['edit']['thread']) {
+			drupal_set_message(t('The comment and all its replies have been deleted.'));
 
-    // Delete comment and its replies.
-    _comment_delete_thread($comment);
+			// Delete comment and its replies.
+			_comment_delete_thread($comment);
+		}
+		else {
+			drupal_set_message(t('The comment has been deleted.'));
+
+			// Delete comment.
+			_comment_delete_single($comment);
+		}
 
     _comment_update_node_statistics($comment->nid);
 
@@ -975,11 +1004,21 @@ function comment_delete($cid) {
 
   }
   else if ($comment->cid) {
+    $children = db_result(db_query('SELECT COUNT(cid) FROM {comments} WHERE pid = %d', $comment->cid));
+    if ($children) {
+      $extra = form_checkbox(t('Delete thread'), 'thread', 1, FALSE,
+        t('If selected, any replies to this comment will be lost.'));
+    }
+    else {
+      $extra = form_hidden('thread', 0);
+    }
     $output = theme('confirm',
-                    t('Are you sure you want to delete the comment %title?', array('%title' => theme('placeholder', $comment->subject))),
+                    t('Are you sure you want to delete the comment %title?', array('%title' => '<em>'. $comment->subject .'</em>')),
                     'node/'. $comment->nid,
-                    t('Any replies to this comment will be lost. This action cannot be undone.'),
-                    t('Delete'));
+                    NULL,
+                    t('Delete'),
+                    NULL,
+                    $extra);
     // Show comment that is being deleted
     $comment->comment = check_output($comment->comment, $comment->format);
     $output .= theme('comment', $comment);
@@ -1596,6 +1635,42 @@ function theme_comment_post_forbidden() 
     else {
       return t('<a href="%login">login</a> to post comments', array('%login' => url('user/login')));
     }
+  }
+}
+
+function _comment_delete_single($comment) {
+  $cid = $comment->cid;
+  $pid = $comment->pid;
+  // Delete the comment:
+  db_query('DELETE FROM {comments} WHERE cid = %d', $comment->cid);
+  watchdog('special', t('Comment: deleted %subject.', array('%subject' => "<em>$comment->subject</em>")));
+
+  module_invoke_all('comment', 'delete', $comment);
+
+  // Rethread this comment's descendents
+  $result = db_query('SELECT cid, nid, thread FROM {comments} WHERE pid = %d ORDER BY timestamp', $cid);
+  while ($child = db_fetch_object($result)) {
+    $oldthread = $child->thread;
+    $newthread = comment_thread($child->nid, $pid);
+    db_query("UPDATE {comments} SET thread = '%s', pid = %d WHERE cid = %d", $newthread, $pid, $child->cid);
+    _comment_rethread($child->cid, strlen($oldthread)-1, rtrim($newthread,'/'));
+  }
+}
+
+/**
+ * revise the thread field of descendents of a deleted comment
+ *
+ * @param $cid comment whose descendents are rethreaded
+ * @param $oldlength length of the former thread prefix
+ * @param $newprefix revised thread prefix
+ */
+function _comment_rethread($cid, $oldlength, $newprefix) {
+  $result = db_query('SELECT cid, thread FROM {comments} WHERE pid = %d', $cid);
+  while ($child = db_fetch_object($result)) {
+    $suffix = substr($child->thread, $oldlength);
+    $thread = $newprefix . $suffix;
+    db_query("UPDATE {comments} SET thread = '%s' WHERE cid = %d", $thread, $child->cid);
+    _comment_rethread($child->cid, $oldlength, $newprefix);
   }
 }
 
