WP True Typed — a WordPress Plugin to Fight Comment Spam

This is the official plugin page for WP True Typed, an anti-spam plugin to protect your site from comment spam.

As any author/site admin knows, fighting comment spam is one of the biggest responsibilities of maintaining a website. You want interactivity with your reader base and site visitors, but comment spam wastes your valuable time and resources. There are plenty of front-line and “back of the house” tools available though, to help combat comment spam.

Front-line tools are those that are placed directly in the comment form and require the comment author to answer some sort of challenge. Captcha-based challenges use images–of either words or objects–and require the comment author to type in what they see, while math-based challenges require the comment author to solve an equation. Back of the house tools work silently behind the scenes and either employ the use of javascripts, cookies, css, etc., or web-based services such as Akismet.

WordPress has quite a few internal spam checks, from comment moderation to blacklists to link checks. Akismet is installed (though not activated) by default in all new installations as well. The Codex has an article outlining these internal spam checks and is a must-read. You can check it out here: Combating Comment Spam.

But like the article points out:

There is no “one size fits all” method that will protect your comments; spammers use many tactics. Consider using multiple defenses. Remember spammers change the way they attack so you must keep your choices updated.

The challenge for site admins lies in preventing comment spam without inconveniencing legitimate commenters. And for some reason, many people consider answering a challenge question of any sort–captcha-based or otherwise–an inconvenience. Granted, a poorly-conceived challenge can be an inconvenience, but a well developed one shouldn’t be. It just becomes another field to fill in on the form. After all, you’re already required to enter name, email, comment text, etc.

The challenge for anti-spam developers lies in creating a tool that is challenging enough to catch spam, but one that is still accessible, easy to understand, and doesn’t require too much from the commenter. That was the thought behind developing WP True Typed.

WP True Typed dynamically creates an anti-spam challenge question based on the post or page the comment form is on, and prevents the comment from being processed if answered incorrectly. It works as a first line of defense in battling comment spam.

Great, but what does it actually do?

WP True Typed scans the page the comment form is on, grabs the title of that page, and asks the commenter to enter information about the title. Specifically, the first word of the title.

For the developers and tech geeks among you, keep reading to see how it works.

If you just want to install the plugin and move on, click here to skip this section.

The Code Explained

We’re going to break the plugin down into its various functions and examine how each works. WP True Typed uses a lot of WordPress’ internal comment functions and author-validation functions. We build upon these and hook onto various action/filter hooks throughout the commenting process; starting with the comment author.

Do You Validate?

The first function of WP True Typed is to check whether or not the commenter is a registered user and signed in, or if he’s already had a comment approved. If so, the validation process isn’t even loaded, and the commenter will be able to avoid dealing with validation at all. This doesn’t affect the site’s discussion settings. If you still have “An administrator must always approve the comment” set in your Dashboard, each comment will still have to be approved. All this setting does is determine whether or not the commenter has to do the validation.

function ttype_author_can_comment() {
    if ( is_user_logged_in() ) {
        return true;    
    } else {
        global $wpdb;
        $author = '';
        $email = '';
        $author .= ( isset($_COOKIE['comment_author_'.COOKIEHASH]) ) ? stripslashes($_COOKIE['comment_author_' . COOKIEHASH]) : '';
        $email .= ( isset($_COOKIE['comment_author_email_'.COOKIEHASH]) ) ? stripslashes($_COOKIE['comment_author_email_' . COOKIEHASH]) : '';
        if ( '' != $author && '' != $email ) {
            $ok_to_comment = $wpdb->get_var("SELECT comment_approved FROM $wpdb->comments WHERE comment_author = '$author' AND comment_author_email = '$email' and comment_approved = '1' LIMIT 1");
            if ( (1 == $ok_to_comment) ) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
}

Create the Gate

The next function creates the actual validation field for your comment form. Since the default comment form changed in WordPress 3.0, this function first checks what version you’re running and outputs the validation field accordingly. Version 3.0 saw numerous changes to the form, one of which was adding a class attribute on each enclosing paragraph. WP True Typed follows the naming convention and markup structure of the default form and adds a class of “comment-form-validation” to the paragraph enclosing the validation input field and label. This class is also used to apply some styling via the plugin’s included style sheet; style_truetyped.css.

function ttype_add_validation_field() {
    $wp_version = absint(get_bloginfo('version'));
    if ( $wp_version < 3 ) { ?>
        <p class="comment-form-validation">
            <label for="validate"><small>Validation (Enter <b>first word</b> of <b> post title</b>, no characters or numbers.)(required)</small></label>
            <input id="validate" type="text" size="22" value="" name="validate" aria-required="true" tabindex="6" />
        </p>
    <?php } else { ?>
        <p class="comment-form-validation">
            <label for="validate">Validation (Enter the <b>first word</b> of the <b> post title</b>, no characters or numbers.)</label><span class="required">*</span>
            <input id="validate" type="text" size="30" value="" name="validate" aria-required="true" />
        </p>
    <?php };
};

Here’s what it looks like in the WP 3.0 default installation:

And in versions prior to WP 3.0 (again, default installation):

“C” is For “Cookie” As In, Comment Cookie

With the next function we’re modifying the comment form textarea. This will only work with WP 3.0 and higher since it uses a filter hook introduced in this version; (comment_form_field_comment). What we’re doing is storing the value of the comment text in a cookie and then displaying it in the textarea if the commenter doesn’t pass validation. This is one way WP True Typed addresses the whole “inconvenience” argument; if a legitimate commenter accidentally enters the wrong validation, this plugin remembers his comment and re-displays it when he’s redirected back to the form.

function ttype_comment_comment_field() {
    $comment_field = '<p class="comment-form-comment"><label for="comment">' . _x( 'Comment', 'noun' ) . '</label><textarea id="comment" name="comment" cols="45" rows="8" aria-required="true">'. esc_attr($_COOKIE['comment_author_comment_' . COOKIEHASH]) .'</textarea></p>';
    return $comment_field;
}

You can replicate this same functionality in older versions of WordPress by replacing this section in the default form:

<p><textarea name="comment" id="comment" cols="58" rows="10" tabindex="4"></textarea></p>

with the following:

<p><textarea name="comment" id="comment" cols="58" rows="10" tabindex="4"><?php echo stripslashes($_COOKIE['comment_author_comment_' . COOKIEHASH]);  ?></textarea></p>

Failure to Launch

The next function generates the actual error message displayed if the commenter fails the validation. This error message will be inserted into the comment form above the validation field when the commenter is redirected.

function ttype_comment_err_message() {
    echo '<p id="comment-form-error-msg" class="comment-form-error">Error: the validation you entered is incorrect.</p>';
};

Here’s what it looks like in the WP 3.0 default installation:

And in versions prior to WP 3.0 (again, default installation):

Dress it Up or Down

The next function calls the plugin’s CSS style sheet. The default styles make sure the paragraph enclosing the validation field clears any floats, and style the error message with a light red background and a darker border. The included style sheet is lightweight (1kb) and is only included in pages or posts that have comments open. By including a style sheet vs. writing directly to the <head> section of the site, you can style these elements directly in the style sheet vs. having to dig into the plugin source code.

function ttype_css() {
    $css_file = WP_PLUGIN_URL . '/'. basename(dirname(__FILE__)) . '/style_truetyped.css';
    echo '<link rel="stylesheet" href="'.$css_file.'" type="text/css" media="screen" />'."\n";
}

The Gatekeeper

Since we’ve already built the gate, we now have to have something watch it. This is what the next function does; the actual data generation and validation. It accomplishes the following:

  1. Checks if the commenter is pre-approved (via the ttype_author_can_comment() function). If so, we skip the validation.
  2. Unsets the comment text cookie set by the plugin. This is in case the commenter changed his comment before re-submitting the form.
  3. Retrieves the post title, strips all special characters and numbers, and returns the first word.
  4. Retrieves the word entered by the commenter in the validation field of the comment form.Compares the values from steps 3 and 4. If they don’t match:
    1. Generate cookies for commenter’s name, email, url, and comment text.
    2. Check for a comment parent (if commenter clicked on reply link in nested comments).
    3. Add query variables for the error message and comment parent (if applicable).
    4. Redirect the commenter back to the comment form.
function ttype_validate() {

    if( ttype_author_can_comment() )
    return;
        
    if ( isset($_COOKIE['comment_author_comment_'.COOKIEHASH]) ) {
        setcookie('comment_author_comment_' . COOKIEHASH, '', time() - 60, COOKIEPATH, COOKIE_DOMAIN);
    };
    
    $comment_post_ID = isset( $_POST['comment_post_ID'] ) ? (int) $_POST['comment_post_ID'] : 0;
        
    $post_title = get_the_title($comment_post_ID);
    $post_title = strip_tags($post_title);
    $post_title = preg_replace('#[^A-Za-z\s\s+]#','',$post_title);
    $post_title = explode(' ', $post_title);
    foreach ($post_title as $key => $value) {
      if ( is_null($value) || '' == $value ) {
        unset($post_title[$key]);
      }
    } 
    $post_title = array_values($post_title);
    $post_title_first_word = $post_title[0];
    $post_title_first_word = preg_replace('#[^A-Za-z]#', '', $post_title_first_word);
    $post_title_first_word = strtolower($post_title_first_word);
    $post_title_first_word = md5($post_title_first_word);                    

    $comment_validation = strip_tags($_POST['validate']);
    $comment_validation = trim($comment_validation);
    $comment_validation = preg_replace('#[^A-Za-z]#', '', $comment_validation);
    $comment_validation = ( '' != $comment_validation ) ? $comment_validation: 'validation-fail';   
    $comment_validation = strtolower($comment_validation);                                       
    $comment_validation = md5($comment_validation);                                                
    
    if ( $comment_validation != $post_title_first_word ) {
        
        $comment_cookie_lifetime = apply_filters('comment_cookie_lifetime', 30000000);
        setcookie('comment_author_' . COOKIEHASH, $_POST['author'], time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN);
        setcookie('comment_author_email_' . COOKIEHASH, $_POST['email'], time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN);
        setcookie('comment_author_url_' . COOKIEHASH, esc_url($_POST['url']), time() + $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN);
        setcookie('comment_author_comment_' . COOKIEHASH, stripslashes(htmlentities($_POST['comment'], ENT_QUOTES, 'utf-8' )), time() + 60, COOKIEPATH, COOKIE_DOMAIN);
      
        $comment_parent = isset($_POST['comment_parent']) ? absint($_POST['comment_parent']) : 0;

        $ref = remove_query_arg( array('m', 'replytocom'), wp_get_referer() );
        $ref = $ref.'#comment-form-error-msg';
        $ref = ( $comment_parent > 0 ) ? add_query_arg (array('replytocom' => $comment_parent , 'm' => 'comm-err'), $ref) : add_query_arg ('m', 'comm-err', $ref) ;
        wp_redirect($ref);
        exit;
    }
};
add_action('pre_comment_on_post', 'ttype_validate');

As previously mentioned, this function does the bulk of the processing for the plugin. We hook it onto the pre_comment_on_post action hook so that it executes before WordPress does anything with the comment. Current default WordPress action is to only redirect the commenter back to the comment area if he passes validation and WordPress runs all its checks. If anything is missing, you get that dreaded error screen:

This abrupt stop is a huge inconvenience and disrupts your commenters’ experience. There’s no indication of what to do next; no link back to the post, no link to your home page, etc. Your user is just stopped. It’s the equivalent of running into a brick wall.

WP True Typed doesn’t address this default WordPress action (although it probably will in a future update), but by hooking the plugin onto the pre_comment_on_post action hook , it allows us to redirect the commenter back to the post if he fails the validation. And since it saves all the information he entered in the same cookies WordPress sets, we save the commenter any headaches.

If the commenter passes the validation test, the comment is passed along to the default WordPress comment processing.

Making it all Work

The last function of the plugin brings everything together and does the actual execution of the various functions that make up the plugin:

function ttype_init() {
    global $post;

    if( !comments_open($post->ID) || ttype_author_can_comment() )
    return;

    ttype_css();

    $fail = '';
    $fail .= $_GET['m'];
    
    $wp_version = absint(get_bloginfo('version'));
    if ( $wp_version < 3 ) {
        if( 'comm-err' == $fail ) { add_action('comment_form', 'ttype_comment_err_message', 1); }
        add_action('comment_form', 'ttype_add_validation_field', 1);
    } else {
        if( 'comm-err' == $fail ) { add_action('comment_form_before_fields', 'ttype_comment_err_message', 1); }
        add_action('comment_form_after_fields', 'ttype_add_validation_field', 2);
        add_filter('comment_form_field_comment', 'ttype_comment_comment_field', 1);
    }
}
add_action('wp_head','ttype_init');

The first thing we’re doing is checking to see if comments are closed or if the commenter is a pre-approved commenter. If so, the plugin will stop loading.

global $post;

if( !comments_open($post->ID) || ttype_author_can_comment() )
return;

Next we’re loading the style sheet:

ttype_css();

Next we’re checking for an error message:

$fail = '';
$fail .= $_GET['m'];

Next we’re checking what version of WordPress the site is using, and hooking onto the appropriate action/filter hook. In versions lower than WP 3.0 we’re hooking on the comment_form action hook, in all other versions we’re hooking on to various action/filter hooks provided by the new comment_form(); function provided in WordPress 3.0.

$wp_version = absint(get_bloginfo('version'));
if ( $wp_version < 3 ) {
    if( 'comm-err' == $fail ) { add_action('comment_form', 'ttype_comment_err_message', 1); }
    add_action('comment_form', 'ttype_add_validation_field', 1);
} else {
    if( 'comm-err' == $fail ) { add_action('comment_form_before_fields', 'ttype_comment_err_message', 1); }
    add_action('comment_form_after_fields', 'ttype_add_validation_field', 2);
    add_filter('comment_form_field_comment', 'ttype_comment_comment_field', 1);
}

And finally, we hook this function to the wp_head action hook:

add_action('wp_head','ttype_init');

Installing the Script

  1. Download the WP True Typed plugin from the WordPress Plugin Repository.
  2. Unzip the folder and upload to your plugins directory (..wp-content/plugins)
  3. Activate in the Plugins section of your Admin Dashboard

Using the Script

Using the script is as easy as following the installation steps outlined above. Once done, WP True Typed will just work. In its default styling, it’s set to fit in with WordPress’ default comment form (any version–see Notes & Resources). If you’d like to style it to fit your current comment form, there’s an included style sheet called “style_truetyped.css”. The paragraph enclosing the validation input field and label has a class of “comment-form-validation” applied, and the error message paragraph has an id of “comment-form-error-msg”.

As you can see from the comment form on this site, it’s easy to customize the form and still work the validation field in.

Notes & Resources

WP True Typed is intended to work with the default comment forms included in all versions of WordPress. If you use a custom comment form and have a version of WordPress older than 3.0, make sure your comment form has the following line (usually located just above the </form> element):

<?php do_action('comment_form', $post->ID); ?>

(My personal preference is to move the comment_form action hook above the submit button, just below the textarea. This way, the validation field will show before the submit button, not after.)

For 3.0 and higher, the plugin uses the following hooks:

  • comment_form_before_fields
  • comment_form_after_fields
  • comment_form_field_comment

So if you’re using a custom comment form, make sure you’re including these hooks.

Features

  • Checks for registered users or pre-approved comment authors
  • No cookies needed
  • No javascript needed
  • No images needed
  • Dynamic, changes based on post or page
  • Section 508 and WAI accessible
  • Valid HTML
  • Works with any comment form
  • Included CSS style sheet for customization

First the Article,
Now the Comments

The individual views expressed within are those of the respective commenter and are not reflective of my own or my clients’. Skip to Comment Form →

I just installed WP True Typed.

I saw in the WP Directory that True Typed will work with any comment form as long as the form “has the comment action hook: do_action(‘comment_form’, $post->ID);

How do I tell if the comment forms on my site have this? Thank you. Gina

@Gina: If you’re using the default form in any version of WordPress, it should have the action hook. If you’re using a version of WP lower than WP 3.0, open your comments.php file and look for this line inside the comment form <form> tags.

<?php do_action('comment_form', $post->ID); ?>

Make Yourself Read, Leave a Comment

Comment moderation is enabled. If you don’t see your comment, either it’s your first time commenting, you’re spam, or there was a glitch. If it’s two of the three, no worries, it’ll show up soon. (* Indicates a required field.)

If you want a commenter avatar, check them out here.

Allowed XHTML: <a> <abbr> <blockquote> <em> <strong>
Enclose code in backticks (`); no need to encode, the filter will do that for you.