PHP jQuery Validation CAPTCHA Code Sample

The PHP jQuery Validation Captcha code sample shows how to integrate BotDetect PHP CAPTCHA validation with jQuery Validation client-side form validation.

First Time Here?

Check the BotDetect PHP Captcha Quickstart for key integration steps.

It uses the Captcha Form Sample as a starting point, and adds client-side jQuery Validation rules for all form fields.

Client-side validation is not secure by itself (it can be bypassed trivially), so the sample also shows how the protected form action must always be secured by server-side CAPTCHA validation first, and use client-side validation only to improve the user experience.

Download the BotDetect PHP CAPTCHA library and run this sample

Downloaded Location

The PHP Captcha jQuery validation code sample is included in the samples/php_jquery_validation_captcha_sample folder of the download package.

index.php

<?php session_start(); ?>
<?php require("botdetect.php"); ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
  <title>BotDetect PHP CAPTCHA jQuery Validation Sample</title>
  <link type="text/css" rel="Stylesheet" href="StyleSheet.css" />
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <link type="text/css" rel="Stylesheet" 
    href="<?php echo CaptchaUrls::LayoutStylesheetUrl() ?>" />
  <script type="text/javascript" src="js/jquery-1.7.2.min.js"></script>
  <script type="text/javascript" src="js/jquery.validate.min.js"></script>
</head>
<body>
  <form method="post" action="process_form.php" id="form1">

    <h1>BotDetect PHP CAPTCHA jQuery Validation Sample</h1>
    
    <fieldset>
      <legend>CAPTCHA included in PHP form validation</legend>
      
      <div class="input">
        <label for="Name">Name:</label>
        <input type="text" name="Name" id="Name" class="textbox" 
          value="<?php echo getValue('Name');?>" />
        <?php echo getValidationStatus('Name'); ?>
      </div>
      
      <div class="input">
        <label for="Email">Email:</label>
        <input type="text" name="Email" id="Email" class="textbox" 
          value="<?php echo getValue('Email');?>" />
        <?php echo getValidationStatus('Email'); ?>
      </div>
      
      <div class="input">
        <label for="Message">Short message:</label>
        <textarea class="inputbox" id="Message" name="Message" rows="5" 
          cols="40"><?php echo getValue('Message');?></textarea>
        <?php echo getValidationStatus('Message'); ?>
      </div>
      
      
      <div class="input">
        <?php // Adding BotDetect Captcha to the page 
          $jQueryValidatedCaptcha = new Captcha("jQueryValidatedCaptcha");
          $jQueryValidatedCaptcha->UserInputID = "CaptchaCode";
          $jQueryValidatedCaptcha->CodeLength = 3;
          $jQueryValidatedCaptcha->ImageWidth = 150;
          $jQueryValidatedCaptcha->ImageStyle = ImageStyle::Graffiti2;
          
          // only show the Captcha if it hasn't been already solved 
          for the current message
          if(!$jQueryValidatedCaptcha->IsSolved) { ?>
            <label for="CaptchaCode">Retype the characters from the 
              picture:</label>
            <?php echo $jQueryValidatedCaptcha->Html(); ?>
            <input type="text" name="CaptchaCode" id="CaptchaCode" 
              class="textbox" />
            <?php echo getValidationStatus('CaptchaCode');
          }
        ?>
      </div>
      
      <input type="submit" name="SubmitButton" id="SubmitButton" 
        value="Submit"  />
        
    </fieldset>
    
    <script type="text/javascript" src="js/validation_rules.js"></script>
    
  <?php 
    // remember user input if validation fails
    function getValue($fieldName) {
      $value = '';
      if (isset($_REQUEST[$fieldName])) { 
        $value = $_REQUEST[$fieldName];
      }
      return $value;
    }
    
    // server-side validation status helper function
    function getValidationStatus($fieldName) {
      // validation status param, e.g. "NameValid" from "Name"
      $requestParam = $fieldName . 'Valid';
      if ((isset($_REQUEST[$requestParam]) && $_REQUEST[$requestParam] == 0)) {
        // server-side field validation failed, show error indicator
        $messageHtml = "<label class='incorrect' for='{$fieldName}'>*</label>";
      } else {
        // server-side field validation passed, no message shown
        $messageHtml = '';
      }
      return $messageHtml;
    }
  ?>
    
  </form>
</body>
</html>

The input form code is almost exactly the same as in the form Captcha sample. The only difference is that we must include jQuery, the jQuery.validate plugin, and the script containing our custom validation rules. To make the sample work in IE 6, we have to load the jQuery includes before Captcha markup.

js\validation_rules.js

$(document).ready(function() {
  $("#form1").validate({
    rules: {
      Name: { required: true, minlength: 3 },
      Email: { required: true, email: true },
      Message: { required: true, minlength: 10 },
      // Captcha code is a required input, and its value is validated remotely
      // the remote validation Url is exposed through the BotDetect client-side
      // API
      CaptchaCode: {
        required: true,
        remote: $("#CaptchaCode").get(0).Captcha.ValidationUrl 
      }
    },
    messages: {
      Name: {
        required: "A name is required",
        minlength: jQuery.format("Enter at least {0} characters")
      },
      Email: {
        required: "An email address is required",
        email: "Please enter a valid email address"
      },
      Message: {
        required: "A message is required",
        minlength: jQuery.format("Enter at least {0} characters")
      },
      // error messages for Captcha code validation 
      CaptchaCode: {
        required: "The Captcha code is required",
        remote: "The Captcha code must be retyped correctly"
      }
    },
    // the Captcha input must only be validated when the whole code string is
    // typed in, not after each individual character (onkeyup must be false)
    onkeyup: false,
    // validate user input when the element loses focus
    onfocusout: function(element) { $(element).valid(); },
    // reload the Captcha image if remote validation failed
    showErrors: function(errorMap, errorList) {
      this.defaultShowErrors();
      if (typeof(errorMap.CaptchaCode) != "undefined" &&
          errorMap.CaptchaCode === this.settings.messages.CaptchaCode.remote) {
        $("#CaptchaCode").get(0).Captcha.ReloadImage();
      }
    },
    success: function(label) {
      label.text("Ok!");
      label.addClass(this.validClass);
    },
    errorClass: "incorrect",
    validClass: "correct",
    errorElement: "label"
  });
});

To set up jQuery validation of a textbox taking Captcha code input, we do the following:

  • Add validation rules specifying it as a required field, that also needs to be validated remotely when a value is entered; define error messages shown when these validation rules are triggered
  • Disable onkeyup validation of the Captcha code, since we must validate the whole code and not the individual characters
  • Reload the Captcha image whenever remote Captcha validation fails

CAPTCHA jQuery Validation Rules and Error Messages

We add the needed declarations in the rules and messages properties of the validation specification, tied to the Id of the Captcha code input element.

The expression for the remote Captcha validation Url can easily be read looking at dot-separated segments from right to left:

  • The ValidationUrl property is part of the BotDetect client-side API, and is unique for each Captcha instance.
  • The client-side BotDetect object can always be accessed through the Captcha property of the Captcha code textbox. For this custom property to be assigned, the textbox must be registered during server-side Captcha object initialization, through the UserInputID property (as has been done in index.php code above).
  • We use the standard jQuery selector to access the textbox by Id, and the .get(0) function call to get the underlying DOM element. This is needed because BotDetect adds the custom Captcha property to the DOM element directly, and the jQuery wrapper element returned by the jQuery selector doesn't include it.

Disabling Remote CAPTCHA Validation on Individual Character Input

Since BotDetect deletes the Captcha code stored on the server after failed validation of a particular Captcha instance (as explained in the BotDetect FAQ), we must avoid validating the user input before the user finished typing in the whole Captcha code. The simplest way to achieve this is to disable onkeyup validation completely (onkeyup: false).

Reloading the CAPTCHA Image When Remote Validation Fails

Due to the above (failed validation invalidates the stored code for security purposes), we must also always reload the Captcha image when remote validation fails. Otherwise the user would be trying to correct his input based on an expired image, and couldn't pass Captcha validation at all.

We do this by customizing the jQuery.validate showErrors callback: beside the regular functionality (this.defaultShowErrors), we also check that there is a Captcha validation error (typeof(errorMap.CaptchaCode) != "undefined") and that the error message indicates a remote validation failure (errorMap.CaptchaCode === this.settings.messages.CaptchaCode.remote). If that is case, we call the ReloadImage() function on the client-side Captcha object (accessed as explained above).

process_form.php

<?php 

  session_start();
  require("botdetect.php");


  $form_page = "index.php";
  $view_page = "messages.php";
  
  // directly accessing this script is an error
  if (!$_SERVER['REQUEST_METHOD'] == "POST") {
    header("Location: ${form_page}");
    exit;
  }

  // submitted data
  $name = $_REQUEST['Name'];
  $email = $_REQUEST['Email'];
  $message = $_REQUEST['Message'];
  $form_page = $form_page . "?Name=" . urlencode($name) . "&Email=" . 
    urlencode($email) . "&Message=" . urlencode($message);

  // total form validation result
  $isPageValid = true;

  
  // Captcha validation 
  $jQueryValidatedCaptcha = new Captcha("jQueryValidatedCaptcha");
  $jQueryValidatedCaptcha->UserInputID = "CaptchaCode";
  if (!$jQueryValidatedCaptcha->IsSolved) {
    $isHuman = $jQueryValidatedCaptcha->Validate();
    $isPageValid = $isPageValid && $isHuman;
    $form_page = $form_page . "&captchaCodeValid=" . $isHuman;
  }
  
  // name validation
  $isNameValid = ValidateName($name);
  $isPageValid = $isPageValid && $isNameValid;
  $form_page = $form_page . "&nameValid=" . $isNameValid;
  
  // email validation
  $isEmailValid = ValidateEmail($email);
  $isPageValid = $isPageValid && $isEmailValid;
  $form_page = $form_page . "&emailValid=" . $isEmailValid;
  
  // message validation
  $isMessageValid = ValidateMessage($message);
  $isPageValid = $isPageValid && $isMessageValid;
  $form_page = $form_page . "&messageValid=" . $isMessageValid;
  
  if (!$isPageValid) { 
    // form validation failed, show error message
    header("Location: ${form_page}");
    exit;
  }
  
  // keep a collection of submitted valid messages in Session state
  SaveMessage($name, $email, $message);
  $jQueryValidatedCaptcha->Reset(); // each message requires a new Captcha 
  // challenge
  header("Location: ${view_page}");
  exit;
  
  
  // name validation
  function ValidateName($name) {
    $result = false;
    if (strlen($name) > 2 && strlen($name) < 30) {
      $result = true;
    }
    return $result;
  }
  
  // email validaton
  function ValidateEmail($email) {
    $result = false;
    if (strlen($email) < 5 || strlen($email) > 100) {
      $result = false;
    } else {
      $result = (1 == preg_match('/^(.+)@(.+)\.(.+)$/', $email));
    }
    return $result;
  }
  
  // message validation
  function ValidateMessage($message) {
    $result = false;
    if (strlen($message) > 2 && strlen($message) < 255) {
      $result = true;
    }
    return $result;
  }
  
  // data storage
  function SaveMessage($name, $email, $message) {
    // we want to keep the sample code simple, so we'll store the 
    // messages in Session state despite it being unfit for real-world 
    // use in such scenarios;
    // using a database or another appropriate persistence medium 
    // would complicate the sample code
    $_SESSION['Message_' . strtolower(md5(uniqid(mt_rand(), true)))] = 
      htmlentities($name) . ' (' . htmlentities($email) . ') says: ' . 
      htmlentities($message);
  }
  
?>

Form submission validation is performed in this file, which checks all required fields and redirects the user back to the form if validation fails. Captcha validation is treated no different than other field validation. Server-side validation code is kept exactly the same as in the form Captcha sample (we just changed the CaptchaId to match form code).

We left the server-side Captcha validation (implemented in the form Captcha sample) in place, ensuring that any bots or users with JavaScript disabled have their input checked. For users with JavaScript enabled, errors in Captcha code input will be shown on the client without full form being POSTed to the server.

messages.php

<?php session_start(); ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
  <title>BotDetect CAPTCHA PHP Form Sample</title>
  <link type="text/css" rel="Stylesheet" href="StyleSheet.css" />
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>

  <h1>BotDetect CAPTCHA PHP Form Sample</h1>
  
  <h2>View Messages</h2>

  <?php
    $count = 0;
    foreach($_SESSION as $key => $val) {
      if (false !== strpos($key, "Message_") && isset($val)) {
        echo "<p class='message'>${val}</p>";
        $count++;
      }
    }
    
    if ($count == 0) {
      echo '<p class="message">No messages yet.</p>';
    }
  ?>
  
  <br />
  
  <p class="navigation"><a href="index.php">Add Message</a></p>
    
</body>
</html>

This page displays all successfully submitted messages, and the user is automatically redirected here after validation of all form fields (including Captcha) is passed.


Please Note

BotDetect 3.0 PHP Captcha Library Beta2 is an in-progress port of BotDetect 3.0 Captcha, and we need you to guide our efforts towards a polished product. Please let us know if you encounter any bugs, implementation issues, or a usage scenario you would like to discuss.

BotDetect PHP Captcha v3.0.Beta2 now supports localized Captcha generation, using various Unicode character sets and downloadable multi-language captcha pronunciations.