Dealing with threaded comments in panels

We just finished pushing a change to a client site that gives them threaded comments in node displayed in a panel. No big deal, just click a few radio buttons in the node config screen, right? Not so fast there clicker.

When a user clicks the "reply" link on a comment they get sent to sitename/comments/reply/node_id/parent_id. If your theme is based upon the use of panels for everything then you are suddenly faced with having to theme that one screwy path to make it look like all your panels. So of course you first you just try and set up a panel at /comments/reply/%nid/%pid. It almost works, since there is a Ctools "content type" plugin for the comment form, but it does not accept the comment parent id (pid) as a context. This means that although you can post the new comment back to the node it is always a new thread, instead of being a reply to the comment the user originally clicked on. Additionally the Ctools plugin is designed to keep the user on the node page, so when they submit they end up staying on /comment/reply/foo/bar and everything gets really confusing really fast.

Looking at the code for the existing comment form plugin it seemed pretty straight forward to hack into what I needed. That being a way to pass in the comment parent ID so new comments would get posted as threaded. In the original code the parent ID was being forced to null. The key was to require an additional String context which would pick up the %pid from the reply URL.

I made a dummy module called threaded_comment_plugin (catchy name, no) which serves to hold the plugin.

The new plugin array in the /plugins/content_types include looks like:

 

$plugin = array(
 'single' => TRUE,
 'title' => t('Threaded Comment form'),
 'icon' => 'icon_node.png',
 'description' => t('A form to add a new threaded comment.'),
 'required context' => array(
     new ctools_context_required(t('Node'),'node'),
     new ctools_context_required(t('String'), 'string'),
 ),
 'category' => t('Node'),
 'defaults' => array('anon_links' => FALSE),
 'render callback' => 'threaded_comment_plugin_content_type_render',
 'edit form' => 'threaded_comment_plugin_content_type_edit_form',
 );

 

This will pass two required contexts, one for the node being commented on, and the other for the parent ID of the comment.

Then the render function splits the contexts into an array and passes them forward into the form:

function threaded_comment_plugin_content_type_render($subtype, $conf, $panel_args, $context) {
    // Seperating the context to two different variables
  list($node_context, $string_context) = $context;
  // Make sure that context variable arent empty.
  if (empty($string_context) || empty($string_context->data)) {
    return;
  }
  if (empty($node_context) || empty($node_context->data)) {
    return;
  }
    $node = clone($node_context->data);
    //the parent comment comment ID
    $pid = $string_context->data;
    $block = new stdClass();
    $block->module = 'comments';
    $block->delta = $node->nid;

    $block->title = t('Add comment');

    if (empty($node)) {
        $block->content = t('Comment form here.');
    } else {
        if (user_access('post comments') && $node->comment == COMMENT_NODE_OPEN) {
            $comment = new stdClass();
            $comment->nid = $node->nid;
            $comment->pid = $pid;
            $form_state = array(
                'node' => $node,
                'build_info' => array(
                    'args' => array(
                        $comment,
                    ),
                ),
            );
            $block->content = drupal_build_form('comment_node_' . $node->type . '_form', $form_state);
        } else if (!empty($conf['anon_links'])) {
            $block->content = theme('comment_post_forbidden', array('node' => $node));
        }
    }

    return $block;
}

The original code also did a form_alter on the comment form to set the destination. We don't want it to do that in our case, so we just get rid of it (I love getting rid of code!).

To recap:

  1. make a panel page at /comments/reply/%nid/%pid
  2. on the arguments tab set the %nid argument as a content:id context, and the %pid as a String context
  3. create a new variant and add the new plugin "threaded comment form" as content
  4. enjoy

I am working on a few other comment related plugins and will probably release them as a little module shortly!