Rearranging comments in Drupal
Moving comments around in Drupal is not (yet) straightforward. There is an experimental module that aims at providing this type of funcitonality, but the maintainers strongly discourage you in using it on live sites. If you're a daredevil, just go ahead and play with the
comments table. You might ruin your comment structure though… unless you want to understand how Drupal manages comments. After reading this post you should be able to move comments without fear.
Comments under the hood
Drupal stores comments to nodes in the
comments table. In Drupal 6, this table contains the following fields:
The comment identifier. This is the unique identifier for a comment.
The parent comment identifier. If the comment is a reply to a 'parent' comment, this value is set to thee comment identifier of the parent comment. Otherwise it is set to 0, which means that the comment is a direct comment on the node.
The node identifier. This is the reference to the node to which this comment belongs to. The node identifier (nid) is the unique identifier for a given content node in Drupal. It is also an integer.
The user identifier. This is the reference to the Drupal user that posted the comment. The user identifier (uid) is the unique identifier tor a given registered user in Drupal. It is also an integer.
Anonymous users are characterized by
The subject of the comment.
The actual comment.
The IP address of the author of the comment.
The UNIX timestamp of the comment.
The status of this comment (it is 0 if the comment is published).
The input format of this comment. The format identifier maps to the value column of the filter_formats table.
This field stores the position of the comment in a thread. This information is represented as a directory structure like text string, such as:
The reasoning behind this format is given in the Drupal API documentation. Basically, you must remember this:
Name of the author of the comment.
Email address of the author of the comment.
Home page of the author of the comment.
This probably is the most complicated thing relating to comments in Drupal. Basically, a thread represents a conversation, where information about replies is preserved. As I explained in the table above, Drupal keeps information about which thread a comment belongs to, in the the
thread comumn of the
If Drupal only maintained the cid and pid fields, we would still be able to order comments in threads, but the order of the replies at a certain hierarchical level would be lost. You could argue that the post date (the timestamp field) should suffice to address this. True, if we can guarantee that the post date does not get overwritten by an update. However, there is only one timestamp field preserved in the
comments table. Whenever somebody edits the comment, the timestamp will change as well. This means that we cannot rely on the timestamp field for ordering the comment replies at a certain depth level in a thread: comments would jump around because of edits to them.
The comments module in Drupal core eventually opted for a solution without an updated timestamp field. There probably are several reasons for this decision. One reason is performance of database queries for reconstructing the thread structure. Another is that the directory-like scheme is able to avoid recursion. Indeed, the position in the thread is easily inferred from the dotted decimal identifier, but in addition this dotted decimal identifier can be used as a search pattern in queries!
The numbering scheme used starts counting from 1 for top-level comments (i.e., direct replies to the node), and from 0 for replies to existing comments. For a given node, an example comment tree might look like:
|Comment hierarchy||cid||pid||depth||Drupal thread identifier||Comment|
|1||1/||Comment 1 is the first comment to the node. Hence it has no parent comment identifier (n/a). In the Drupal database this is represented as pid = 0.
|Comment 2||2||1||2||1.0/||Comment 2 is the first reply (hence: ".0/") to comment 1. Its parent comment is comment 1, hence pid = 1.
|Comment 5||5||1||2||1.1/||Comment 5 is the second reply (hence: ".1/") to comment 1. Like comment 2, its parent comment is also comment 1. However, it comes after comment 1 in the thread. This is expressed in the thread identifier, where at level 2, we see 1 instead of 0.
|Comment 16||16||5||3||1.1.0/||Comment 16 is the first reply (hence: ".0/") to comment 5, which is the second reply to comment 1. Hence comment 16 has comment 5 as parent comment.
|1||2/||Comment 3 is the second top level comment to the node.
|Comment 14||14||3||2||2.1/||Comment 14 is the second reply (hence: ".1/") to comment 3, hence its parent comment is 3, but the thread identifier at level 2 is 1 instead of 0 for comment 7.
|Comment 10||10||4||2||3.2/||Comment 10 is the third reply (hence: ".2/") to comment 4 (hence pid = 4).|
|Comment 13||13||10||3||3.2.1/||Comment 13 is the second reply (hence: ".1/") to comment 10 (hence pid = 10), which in its turn is the third reply to comment 4.
Comment 17 is the fourth reply (hence: ".3/") to comment 4 (hence pid = 4).
|1||4/||Comment 12 is the fourth direct comment to the node.
Moving a comment in a thread
To move a comment in the thread, we must execute the following steps:
- Identify the new
pidof the comment that we want to move. If the thread moves at top level, set
pid= 0, otherwise set
pidto the new parent's cid.
- Update the thread identifier to define the new position of the moved comment in the thread. Thread identifiers that are affected, are at the same level of the place where the comment will be moved, and below. This means that we need to get the thread identifier of the moved comment's new parent node, and replace the trailing slash with a period: every comment whose thread identifier starts with this sequence falls in the scope of the next steps:
- Save the original thread identifier of the comment we want to move.
- The thread identifier of all comments (at the same level) before the moved comment remain unchanged
- The moved comment takes the next thread identifier (at the same level)
- Its child comments inherit the new thread identifier from the parent comment. This means that the moved comment's original thread identifier is a prefix that has to be replaced with the moved comment's new thread identifier. Everything after the original thread identifier remains the same (we aren't changing the order of the comments below the comment we want to move).
- If other comments exist at the same level, their thread identifier must also be updated, as well as its child comments (same procedure as in previous step).
A very simple example
Imagine comment 15 should have been a reply at the node level. To preserve the timeline, we want to move comment 15 below comment 12. Stepwise, we need to proceed as follows:
- The new
pidof comment 15 will be 0, since we want to move it at top level.
- Updating the thread identifier for comment 15 will only affect comment 15: there are no comments under comment 12, and comment 15 has no child comments.
- The original thread identifier for comment 15 is "2.0.0/"
- As already said, in this example only comment 15 will have a changed thread identifier.
- The new thread identifier for comment 15 will be 1 after comment 12 at level 1, hence 1 more at level 1 than "4/", hence "5/"
- Comment 15 has no child comments, hence we can skip this step.
- There are no more comments at level 1 after comment 15, hence we are done.
Summarizing, in this example we only needed to make 2 edits:
- Change the
pidof comment 15 from 7 to 0.
- Change the thread identifier of comment 15 from "2.0.0/" to "5/".
A more elaborate example
Imagine we want to move comment 12 between comments 11 and 13. Stepwise, we need to proceed as follows:
- The new
pidof comment 12 will be 10, since comments 11 and 13 have comment 10 as parent. Hence we set the new
pidof comment 12 to 10.
- Now we must update the thread identifier of comment 12 (and its descendants), hence all comments that have a thread identifier starting with "3.2." (note the period at the end of this text string). The original thread identifier is
- From the table we read that the original thread identifier of comment 12 is "4/".
- All comments up to and including comment 11 remain unchanged.
- Comment 12 will take the place of comment 13, hence we need to update the thread identifiers from here onward (at the level where comment 12 will sit). In our case, this boils down to comments 12 (moved) and 13. The new thread identifier for comment 12 is the current thread identifier of comment 13: "3.2.1/".
- Since comment 12 has no children, nothing needs to be done here.
- Remember we move comment 2 to level 3. Hence we only need to consider comment 13 now, not comment 17 (it's at level 2, so it does not see the 3rd level we are editing in the move). Comment 13 will now receive the next thread identifier at its level (level 3). Hence it goes from "3.2.1/" to "3.2.2/".
Summarizing, in this example we only needed to make 3 edits:
- Change the
pidof comment 12 from 0 to 10.
- Change the thread identifier of comment 12 from "4/" to "3.2.1/".
- Change the thread identifier of comment 13 from "3.2.1/" to "3.2.2/".
A more elaborate example
Imagine we want to make comment 10 (and its children) the first child of comment 7. In other words, we want comment 10 to appear below comment 7 but above comment 15, at the same level as comment 15. The steps to follow are:
- The new
pidof comment 10 is 7.
- The scope of thread identifier change is everything below comment 7, hence all comment identifiers that start with comment 7's thread identifier(minus the slash) are affected. The matching prefix is "2.0." (note the period).
- The original thread identifier of comment 10 we want to move, is "3.2/". The children of comment 10 have a thread identifier starting with "3.2." (note the period instead of the slash). This is easy to read from the table above.
- Since comment 10 will be the first child of comment 7, skip this step.
- The first identifier at this level, is "2.0.0/", and this will be the new thread identifier for comment 10.
- Now we must move the children of comment 10. This is easy: we only need to replace the thread identifier. More precisely, we must replace the prefix "3.2." with "2.0.0.". Hence comment 11 will have "18.104.22.168/" as thread identifier, and likewise comment 13 will have "22.214.171.124/" as new thread identifier.
- The only comment that remains to be processed now, is comment 14. It receives the the next thread identifier after the previous one that was assigned to comment 10. Hence, "2.0.1/".
Summarizing, in this example the following 5 edits have to be made:
- Change the
pidof comment 10 from 4 to 7.
- Change the thread identifier of comment 10 from "3.2/" to "2.0.0/".
- Change the thread identifier of comment 11 from "3.2.0/" to "126.96.36.199/".
- Change the thread identifier of comment 13 from "3.2.1/" to "188.8.131.52/".
- Change the thread identifier of comment 14 from "2.0.0/" to "2.0.1/".
For sake of completeness
I deliberately kept things a bit too simplified:
- In principle we also need to rebuild the thread identifiers in the comment branch where the comment originated from. However, leaving 'holes' in the identifiers (e.g. going from "2.0.0/" to "2.0.2/") will not affect the order, and no information will be lost. Hence it is not really necessary to clean up the 'pruned' branch.
- In reality Drupal constructs the thread identifier with two-digit sequence numbers at each level. Hence the thread identifier example "3.2.0/" would in fact be stored as "03.02.00/".
From the procedure given above it is clear that the simplest way of deleting a comment will also have to delete its children. Otherwise we will end up with orphaned comments.
If we want to preserve child comments from a comment we want to delete, then we must first assign a new parent comment to the soon-to-be-orphaned comments. This basically boils down to moving all children of the comment we want to delete. This has already been described above.
Moving comments to another node
In this scenario, we must change the
nid of the comment we want to move, as well as for all child comments of this comment. Then the regular comment move steps must be performed, as described above.