I use the Contact Form 7 plugin for my site's contact form. The plugin's developer, Takayuki Miyoshi, created a complimentary plugin, Really Simple CAPTCHA, that incorporates with Contact Form 7 to provide a CAPTCHA for forms. In a nice example of forward thinking, Miyoshi created Really Simple CAPTCHA as a stand-alone plugin, complete with API, to facilitate other plugins' incorporation of CAPTCHA functionality. I had been using the excellent Math Comment Spam Protection plugin for my comments form.
However, continued development of the plugin has been dormant (the last update was almost three years ago), and the developer hasn't kept it updated with newer WordPress releases (I had to hack it to ensure compatibility with WordPress 3.0). Not wanting to take on maintaining a fork of Math Comment Spam Protection, realizing that I was actually using two different CAPTCHA solutions on my site, and wanting to take on the challenge of figuring out how to use Really Simple CAPTCHA's API, I decided to try to implement it for my comment form, directly into my Theme via functions.php.
As you can see: I managed to figure it out - through a combination of parsing the plugin files for Really Simple CAPTCHA, Contact Form 7, and Math Comment Spam Protection. Since I assume others might find this technique useful, I will try to explain the process. Integrating Really Simple CAPTCHA consists of two steps:
- Creating the CAPTCHA image, and adding associated fields to the comment form.
- Validating the CAPTCHA response when the comment form is submitted.
Creating the CAPTCHA image, and adding associated fields to the comment form
I will assume that the Theme uses a call to comment_form()
rather than a hard-coded comment form, and thus cannot hard-code CAPTCHA fields into the comment form. Fortunately, WordPress has several built-in, comment form-related hooks. For our purposes, the comment_form_after_fields
action hook works perfectly. This hook will allow us to add our form fields immediately after the Name/Email/URL fields, and before the comment text field. So, let's add the hook 1:
if ( ( ! $user_ID ) && ( class_exists('ReallySimpleCaptcha') ) ) { add_action( 'comment_form_after_fields' , 'mytheme_comment_captcha' ); }
Next, we need the function called by the hook:
function mytheme_comment_captcha() { }
Now to build the function. The first step is to instantiate the ReallySimpleCaptcha() class:
$comment_captcha = new ReallySimpleCaptcha();
The instantiated class uses several variables, the defaults for many of which can be modified if you choose to do so. Here are the defaults for several useful ones:
// Characters to use in CAPTCHA image. $comment_captcha->chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Number of characters in CAPTCHA image. $comment_captcha->char_length = '4'; // Width/Height dimensions of CAPTCHA image. $comment_captcha->img_size = array( '72', '24' ); // Font color of CAPTCHA characters, in RGB (0 - 255). $comment_captcha->fg = array( '0', '0', '0' ); // Background color of CAPTCHA image, in RGB (0 - 255). $comment_captcha->bg = array( '255', '255', '255' ); // Font Size of CAPTCHA characters. $comment_captcha->font_size = '16'; // Width between CAPTCHA characters. $comment_captcha->font_char_width = '15'; // CAPTCHA image type. Can be 'png', 'jpeg', or 'gif' $comment_captcha->img_type = 'png';
Once you've (optionally) set variables, the next step is to generate a random prefix, which will be used to generate the CAPTCHA image:
// Generate random word and image prefix $comment_captcha_word = $comment_captcha->generate_random_word(); $comment_captcha_prefix = mt_rand(); // Generate CAPTCHA image $comment_captcha_image_name = $comment_captcha->generate_image($comment_captcha_prefix, $comment_captcha_word);
Next, define some variables for use in the comment form CAPTCHA fields:
$comment_captcha_image_url = get_bloginfo('wpurl') . '/wp-content/plugins/really-simple-captcha/tmp/'; $comment_captcha_image_src = $comment_captcha_image_url . $comment_captcha_image_name; $comment_captcha_image_width = $comment_captcha->img_size[0]; $comment_captcha_image_height = $comment_captcha->img_size[1]; $comment_captcha_field_size = $comment_captcha->char_length; $comment_captcha_form_label = 'Anti-Spam:';
And finally, output the fields for the comment form:
?> <p class="comment-form-captcha"> <img src="<?php echo $comment_captcha_image_src; ?>"alt="captcha"width="<?php echo $comment_captcha_image_width; ?>"height="<?php echo $comment_captcha_image_height; ?>" /> <label for="captcha_code"><?php echo $comment_captcha_form_label; ?></label> <input id="comment_captcha_code" name="comment_captcha_code" size="<?php echo $comment_captcha_field_size; ?>" type="text" /> <input id="comment_captcha_prefix" name="comment_captcha_prefix" type="hidden"value="<?php echo $comment_captcha_prefix; ?>" /> </p> <?php
Here it is, all put together:
// Instantiate the ReallySimpleCaptcha class, which will handle all of the heavy lifting $comment_captcha = new ReallySimpleCaptcha(); // ReallySimpleCaptcha class option defaults. // Changing these values will hav no impact. For now, these are here merely for reference. // If you want to configure these options, see "Set Really Simple CAPTCHA Options", below // TODO: Add admin page to allow configuration of options. $comment_captcha_defaults = array( 'chars' => 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789', 'char_length' => '4', 'img_size' => array( '72', '24' ), 'fg' => array( '0', '0', '0' ), 'bg' => array( '255', '255', '255' ), 'font_size' => '16', 'font_char_width' => '15', 'img_type' => 'png', 'base' => array( '6', '18'), ); /************************************** * All configurable options are below * ***************************************/ // Set Really Simple CAPTCHA Options $comment_captcha->chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; $comment_captcha->char_length = '4'; $comment_captcha->img_size = array( '72', '24' ); $comment_captcha->fg = array( '0', '0', '0' ); $comment_captcha->bg = array( '255', '255', '255' ); $comment_captcha->font_size = '16'; $comment_captcha->font_char_width = '15'; $comment_captcha->img_type = 'png'; $comment_captcha->base = array( '6', '18' ); // Set Comment Form Options $comment_captcha_form_label = 'Anti-Spam:'; /******************************************************************** * Nothing else to edit. No configurable options below this point. * *********************************************************************/ // Generate random word and image prefix $comment_captcha_word = $comment_captcha->generate_random_word(); $comment_captcha_prefix = mt_rand(); // Generate CAPTCHA image $comment_captcha_image_name = $comment_captcha->generate_image($comment_captcha_prefix, $comment_captcha_word); // Define values for comment form CAPTCHA fields $comment_captcha_image_url = get_bloginfo('wpurl') . '/wp-content/plugins/really-simple-captcha/tmp/'; $comment_captcha_image_src = $comment_captcha_image_url . $comment_captcha_image_name; $comment_captcha_image_width = $comment_captcha->img_size[0]; $comment_captcha_image_height = $comment_captcha->img_size[1]; $comment_captcha_field_size = $comment_captcha->char_length; // Output the comment form CAPTCHA fields ?> <p class="comment-form-captcha"> <img src="<?php echo $comment_captcha_image_src; ?>" alt="captcha" width="<?php echo $comment_captcha_image_width; ?>" height="<?php echo $comment_captcha_image_height; ?>" /> <label for="captcha_code"><?php echo $comment_captcha_form_label; ?></label> <input id="comment_captcha_code" name="comment_captcha_code" size="<?php echo $comment_captcha_field_size; ?>" type="text" /> <input id="comment_captcha_prefix" name="comment_captcha_prefix" type="hidden" value="<?php echo $comment_captcha_prefix; ?>" /> </p> <?php } if ( ( ! $user_ID ) && ( class_exists('ReallySimpleCaptcha') ) ) { add_action( 'comment_form_after_fields' , 'mytheme_comment_captcha' ); }
At this point, your comment form should have a (non-functional) CAPTCHA field and image. Now we need to validate the CAPTCHA when the form is submitted.
Validating the CAPTCHA response when the comment form is submitted
For this part, we will use the preprocess_comment
filter hook 2:
if ( ( ! $user_ID ) && ( $comment_data['comment_type'] == '' ) && ( class_exists('ReallySimpleCaptcha') ) ) { add_filter('preprocess_comment', 'mytheme_check_comment_captcha', 0); }
And as before, we define the function added by the filter:
function mytheme_check_comment_captcha( $comment_data ) { }
Once again, our first step is to instantiate the ReallySimpleCaptcha() class.
$comment_captcha = new ReallySimpleCaptcha();
Then, declare a variable to hold the validation result, which we will default to false
:
$comment_captcha_correct = false;
Next, we will use the "check" function built into the ReallySimpleCaptcha class to validate the CAPTCHA response from the comment form:
$comment_captcha_prefix = $_POST['comment_captcha_prefix']; $comment_captcha_code = $_POST['comment_captcha_code']; $comment_captcha_check = $comment_captcha->check( $comment_captcha_prefix, $comment_captcha_code ); $comment_captcha_correct = $comment_captcha_check;
Now that CAPTCHA validation is complete, we don't need the files hanging around the temp directory. So, we'll clean them up. The first function removes the files we just used. The second will remove any files older than 60 minutes (in case any are hanging around for some reason):
$comment_captcha->remove($_POST['comment_captcha_prefix']); $comment_captcha->cleanup();
And finally, if the CAPTCHA response is incorrect, return an error; otherwise, process the comment as per normal:
if ( ! $comment_captcha_correct ) { wp_die('You have entered an incorrect CAPTCHA value. Click the BACK button on your browser, and try again.'); break; } return $comment_data;
Here's the comment-processing function, all put together:
function mytheme_check_comment_captcha( $comment_data ) { $comment_captcha = new ReallySimpleCaptcha(); // This variable holds the CAPTCHA image prefix, which corresponds to the correct answer $comment_captcha_prefix = $_POST['comment_captcha_prefix']; // This variable holds the CAPTCHA response, entered by the user $comment_captcha_code = $_POST['comment_captcha_code']; // This variable will hold the result of the CAPTCHA validation. Set to 'false' until CAPTCHA validation passes $comment_captcha_correct = false; // Validate the CAPTCHA response $comment_captcha_check = $comment_captcha->check( $comment_captcha_prefix, $comment_captcha_code ); // Set to 'true' if validation passes, and 'false' if validation fails $comment_captcha_correct = $comment_captcha_check; // clean up the tmp directory $comment_captcha->remove($comment_captcha_prefix); $comment_captcha->cleanup(); // If CAPTCHA validation fails (incorrect value entered in CAPTCHA field) don't process the comment. if ( ! $comment_captcha_correct ) { wp_die('You have entered an incorrect CAPTCHA value. Click the BACK button on your browser, and try again.'); break; } // if CAPTCHA validation passes (correct value entered in CAPTCHA field), process the comment as per normal return $comment_data; } if ( ( ! $user_ID ) && ( $comment_data['comment_type'] == '' ) && ( class_exists('ReallySimpleCaptcha') ) ) { add_filter('preprocess_comment', 'mytheme_check_comment_captcha', 0); }
That's it! Now you should have a fully functional CAPTCHA for your comment form. To use the same technique, just drop the above two functions and corresponding hooks into your Theme's functions.php file, then install and activate the Really Simple CAPTCHA plugin.
In case you would rather not mess with your Theme's functions.php file, I have also created a plugin to accomplish the same thing: cbnet Really Simple CAPTCHA Comments. I've added inline documentation, and cleaned up the code a bit. In a subsequent version, I will include an admin options page for setting the configurable options. For now, options can be set in the PHP file.
Notes:
- I've wrapped the hook in two conditionals:
( ! $user_ID )
- if$user_ID
is set, then the user is logged in, so we don't need to add a CAPTCHA.( class_exists('ReallySimpleCaptcha') )
- theReallySimpleCaptcha()
class corresponds to the Really Simple CAPTCHA plugin, upon which our code depends. If it's not installed/activated, we don't want to try to use it.
- For this hook, in addition to the same two conditionals, we add a third:
( $comment_data['comment_type'] == '' )
- only validate the CAPTCHA for comments, and not for Trackbacks or Pingbacks