Author: Chip Bennett

  • Using Really Simple CAPTCHA Plugin for Comments

    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:

    1. Creating the CAPTCHA image, and adding associated fields to the comment form.
    2. 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[ref] I’ve wrapped the hook in two conditionals:

    1. ( ! $user_ID ) – if $user_ID is set, then the user is logged in, so we don’t need to add a CAPTCHA.
    2. ( class_exists('ReallySimpleCaptcha') )– the ReallySimpleCaptcha() 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.

    [/ref]:

    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[ref] 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

    [/ref]:

    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.

  • Twitter Updates for 2010-07-28

  • Daily Digest for July 27th

    twitter (feed #7)
    Chip Bennett Local police & animal control just corralled a stray, starving pit bull that had been roaming the neighborhood for food. It’s safe now. [chip_bennett].
  • Daily Digest for July 26th

    twitpic (feed #9)
    Chip Bennett shared 2 photos.
    chip_bennett: http://twitpic.com/28o1ne @themelab notice how the Read More button is cut off at the bottom of the Featured Post container chip_bennett: http://twitpic.com/28o16k @themelab Chrome adds the yellow border highlight. Notice how off-center it is.
    twitter (feed #7)
    Chip Bennett Does anyone know how to import a SQL dump into a specific WordPress Multisite site, since each one is virtual & doesn’t have own database? [chip_bennett].
  • Daily Digest for July 25th

    twitter (feed #7)
    Chip Bennett Mexico has invaded the United States, taking over two ranches in Laredo, TX http://is.gd/dERTI via @ryancduff @technosailor [chip_bennett].
    twitter (feed #7)
    Chip Bennett Did Twitter just throw up a pop-up ad at me? Seriously? Twitter: 1998 called; they want their crappy internet advertising strategy back. [chip_bennett].
  • Daily Digest for July 24th

    twitter (feed #7)
    Chip Bennett Hey #wordpress folks: anybody know of any current issues with the WXR exporter? It’s throwing "cannot modify headers" errors for me. [chip_bennett].
  • Daily Digest for July 23rd

    twitter (feed #7)
    Chip Bennett Fire at the paper plant next to our site. Fire Dept. has only exit road blocked. Stuck at the office until it’s cleared. 🙁 [chip_bennett].
    twitter (feed #7)
    Chip Bennett Well that’ll take the wind out of some sails RT @pearsonified Friends and lovers: Thesis now sports a split GPL license. Huzzah for harmony! [chip_bennett].
  • Daily Digest for July 22nd

    twitter (feed #7)
    Chip Bennett RT @flashingcursor Inspired by @photomatt : http://bit.ly/aeBMff : WordPress themes and plugins DO NOT inherit the GPL v2 [chip_bennett].
    twitter (feed #7)
    Chip Bennett Now on the Huffington Post: this http://huff.to/dyHRhQ blatant plagiarism of this @copyblogger blog post: http://bit.ly/9vSsaZ [chip_bennett].
  • Daily Digest for July 21st

    twitter (feed #7)
    Chip Bennett WordPress Themes, GPL, and Copyright Case Law – http://bit.ly/cj689B [chip_bennett].
  • WordPress Themes, GPL, and Copyright Case Law

    Within the WordPress community, the question of GPL inheritance of WordPress themes erupts into contentious debate with the reliability – if not the frequency – of Old Faithful. While I understand that, according to the GPL interpretation of Matt Mullenweg, the Free Software Foundation (FSF), and the Software Freedom Law Center (SFLC), WordPress themes are derivative of WordPress and therefore must necessarily inherit WordPress’ GPL, I would like to investigate the issue not in light of their interpretation but rather in light of copyright law and precedent case law.

    Before I begin, let me add an important caveat: I have no qualms with the GPL. I have always released – and will continue to release – under GPL anything I develop related to WordPress. I do so because I choose to do so, as a means of making even a minor contribution to a project from which I believe I have personally benefited. I do have issues with how the GPL-inheritance question has been handled – but those issues are out-of-scope for this post.

    Having (hopefully) made that point clear, let’s begin!

    What US Copyright Law Says

    US Copyright law defines a “derivative work” as such:

    A “derivative work” is a work based upon one or more preexisting works, such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or any other form in which a work may be recast, transformed, or adapted. A work consisting of editorial revisions, annotations, elaborations, or other modifications, which, as a whole, represent an original work of authorship, is a “derivative work”.

    Note the key adjectives: recast, transformed, and adapted.

    Consider also Section 102(b), which states:

    In no case does copyright protection for an original work of authorship extend to any idea, procedure, process, system, method of operation, concept, principle, or discovery, regardless of the form in which it is described, explained, illustrated, or embodied in such work.

    This clause establishes the boundary around copyright between copyrightable expression, and non-copyrightable ideas.

    Summarizing GPL Inheritance Requirements

    To summarize GPL requirements regarding license inheritance for derivative works[ref]

    WordPress is released under GPL version 2.0. I’ll try to summarize below the parts of the license germane to derivative works.

    First, from the Preamble:

    The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such.

    The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a “work based on the library” and a “work that uses the library”. The former contains code derived from the library, while the latter only works together with the library.

    Terms and Conditions, Clause 0:

    The “Library”, below, refers to any such software library or work which has been distributed under these terms. A “work based on the Library” means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term “modification”.)

    Terms and Conditions, Clause 2:

    These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.

    Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library.

    Terms and Conditions, Clause 5:

    A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a “work that uses the Library”. Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License.

    However, linking a “work that uses the Library” with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a “work that uses the library”. The executable is therefore covered by this License. Section 6 states terms for distribution of such executables.

    [/ref]:

    1. The GPL only applies to distribution of a (modified or unmodified) GPL-licensed work, or a derivative work. Any activity involving use, modification, or creation of derivative works that does not involve distribution is outside of the scope of the GPL.
    2. Distribution of a (modified or unmodified) GPL-licensed work, or a derivative work, requires that such distribution be licensed under GPL.

    The GPL is what is now referred to as a “copyleft” license: a modified public-domain license that takes advantage of the exclusive rights granted by copyright law to prevent derivative works from being restrictively licensed. Since the copyright owner has exclusive right to produce and to distribute derivative works based on the copyrighted work, the GPL intends to grant unlimited usage rights (to use, study, modify, etc.) to the end-user, while forcing follow-on developers of derivative works to release those works under the same license.

    It is important to understand that, because the GPL explicitly defines any activity not involving distribution to be out of the scope of the license, and since right of distribution is solely derived from copyright law, that GPL derives its legal basis from copyright law alone. This distinction separates the GPL from most other traditional software licenses, which derive their basis for usage and modification restrictions not from copyright law, but from contract law.