Drupal Overrides - Comment Approval Page

The standard Drupal comment approval admin page has always been an annoyance to me. The main reason I will toss a comment is that it's spam. You know, like "I love this blog. For a great 3-day cruise..." and the like. The problem with the approval page is that it shows everything BUT the comment.

There are contributed solutions for this, an example being the Better Comments Admin module. However, I didn't want to add the overhead of panels to my site just to have a better comment page. That decided, I saw the opportunity for a blog post.

In this post I'll show you how to create a custom module that overrides a callback. The goal is to replace the default implementation of the comment approval admin form with a custom one...without hacking core! If you've never created a custom module in Drupal, this will be a good first experience for you, too.

With most things in Drupal, there is more than one way to approach our goal. We could alter the form via hook_form_alter(), which would give us the opportunity to make dynamic alterations to the existing form right before it gets rendered. However, it's much easier to simply provide our own form. But how do we do that, given that the path for the approval form, admin/content/comment/approval, leads to a callback in the comment module? The answer is hook_menu_alter(), which allows us to override the callback provided for a path.

In Drupal, page requests reach their destination in code via the menu router. Whether a menu item is entered via the admin screen, or is created in a module, the entry must end up being registered with the menu router so that when it is requested, the request can be sent to the right place. We're going to change 'the right place' to the one we want.

To start, we need to create the bare minimum needed for our module. The requirement is only 2 files. We'll call the module comment_approval, so let's create a directory by that name in the customary location:

cd sites/all/modules
mkdir custom
mkdir custom/comment_approval

That done, we'll create the two files that we need:

cd custom/comment_approval
touch comment_approval.module
touch comment_approval.info

The contents of the .info file will provide what Drupal needs to recognize our module:

name = Comment Approval
description = "Provides an improved comment admin page."
package = Administration
core = 7.x

As for our .module file, the first thing we'll add is the hijacked page callback. We need to find out where the page request leads. I know that the comment module provides the comment forms, so that's the place to look. Menu entries are provided to the menu router via hook_menu(), which will almost always be found in the module's .module file. If we take a look at modules/comment/comment.module, we will find the following in the function comment_menu(), around line 235:

$items['admin/content/comment/approval'] = array(
    'title' => 'Unapproved comments',
    'title callback' => 'comment_count_unpublished',
    'page arguments' => array('approval'),
    'access arguments' => array('administer comments'),
    'type' => MENU_LOCAL_TASK,
  );

The index for $items is the path that is being routed, so admin/content/comment/approval, the path to the approval form. The first thing you might notice is that there is not a page callback. Huh? Well, it turns out that hook_menu() provides some inheritance. In this case, since no page callback is specified, the page callback for the parent path, admin/content/comment, if present, will be inherited. Looking at that entry, around line 221, we find the callback:

 'page callback' => 'comment_admin',

Okay, we find that function in comment.admin.inc around line 11. This function is actually the callback for two different panes of the comment admin form, the one that lists the published comments, and the one that lists those awaiting approval. We're only interested in seeing a preview of the unpublished comments, so we'll copy and paste the function and only change the line in this function that handles that request, as well as changing the function name to match our module rather than the comment module:

function comment_approval_admin($type = 'new') {
  $edit = $_POST;
  module_load_include('inc', 'comment', 'comment.admin');

  if (isset($edit['operation']) && ($edit['operation'] == 'delete') && isset($edit['comments']) && $edit['comments']) {
    return drupal_get_form('comment_multiple_delete_confirm');
  }
  else {
    return drupal_get_form('comment_approval_admin_overview', $type);
  }
}

One other change was the addition of the call to module_load_include(). This is because the original form comment_multiple_delete_confirm will still be needed, but it appears in a module .inc file for the comment module, which would not be loaded if we didn't explicitly ask for it to be. What we need, next, is the call that will tell Drupal that instead of calling comment_admin() when the page is requested, we want it to call comment_approval_admin(). That will be done by implementing hook_menu_alter(). This tells Drupal to replace the page callback for the path admin/content/comment/approval with our callback function. 

function comment_approval_menu_alter(&$items) {
  if (isset($items['admin/content/comment/approval'])) {
    $items['admin/content/comment/approval']['page callback'] = 'comment_approval_admin';
  }
}

At this point, we've done what is necessary to have Drupal invoke our callback, which then invokes our form function. We'll copy and paste the comment module form function, comment_admin_overview(), which can be found around line 34 in comment.admin.inc, and make changes to it:

function comment_approval_admin_overview($form, &$form_state, $arg) {

Now the name of the function matches the call in the callback function.

 

 $header = array(
    'subject' => array('data' => t('Subject'), 'field' => 'subject'),
    'comment' => array('data' => t('Preview')),
    'link' => array('data' => t('Link?')),
    'author' => array('data' => t('Author'), 'field' => 'name'),
    'posted_in' => array('data' => t('Posted in'), 'field' => 'node_title'),
    'changed' => array('data' => t('Updated'), 'field' => 'c.changed', 'sort' => 'desc'),
    'operations' => array('data' => t('Operations')),
  );

 

This is the table header for the comment list. We're adding two columns to the table: comment, which will be the comment preview, and link, which will let us know whether the commentator snuck a link into the comment body. The following code will be added right after the header we just modified:

 

if ($arg != 'approval') {
    unset($header['comment']);
    unset($header['link']);
}

The form function is used for both the comment admin pane and the comment approval pane. We only want the preview and link columns in the approval pane, so with the code, above, we'll remove those two columns from the header array when we're not in the approval pane.

  foreach ($comments as $comment) {
    // Remove the first node title from the node_titles array and attach to
    // the comment.
    $comment->node_title = array_shift($node_titles);
    $comment_body = field_get_items('comment', $comment, 'comment_body');
    $options[$comment->cid] = array(
      'subject' => array(
        'data' => array(
          '#type' => 'link',
          '#title' => $comment->subject,
          '#href' => 'comment/' . $comment->cid,
          '#options' => array('attributes' => array('title' => truncate_utf8($comment_body[0]['value'], 128)), 'fragment' => 'comment-' . $comment->cid),
        ),
      ),
      'comment' => substr($comment_body[0]['value'], 0, 30),
      'link' => strpos($comment_body[0]['value'], 'href') !== FALSE ? 'Y' : '',
      'author' => theme('username', array('account' => $comment)),

This code loops through each comment and adds it to the table. We've embedded the two additional columns in the table. The first gives a 30-character preview of the comment body (note that $comment_body is an array, and we use $comment_body[0]['value']), and the second checks the comment body for an occurrence of 'href,' and returns 'Y' if it is found.

That completes our module. There are two steps remaining, enabling the module, and clearing cache so that the menu router will be rebuilt. Both of these can be done with drush, as shown, below, or via the respective admin pages, admin/modules, and admin/config/development/performance.

drush en comment_approval -y
drush cc all

Now, when we request the comment form, admin/content/comment/approval, we see the two new columns included:

 

SUBJECT PREVIEW LINK? AUTHOR POSTED IN UPDATEDsort ascending OPERATIONS

blah I think this is a great column Y admin (not verified) My test content 2015-01-09 21:04 edit

 

Add new comment

Markdown

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd>
  • Missing filter. All text is removed