PHP jQuery AJAX Contact Form CAPTCHA Code Sample (BotDetect v3.0; deprecated)

This code sample demonstrates the use of BotDetect Captcha in a scenario using jQuery and AJAX to validate individual form fields against a PHP server backend. The approach in this scenario is useful in situations where duplication of server-side validation routines on the client side is impractical.

First Time Here?

Check the BotDetect PHP Captcha Quickstart for key integration steps.

To make the sample brief and concrete, it is implemented as an AJAX contact form with Captcha protection.

The sample shows proper steps to be taken when validating the Captcha in various ways this scenario provides: be it partial validation or complete form validation and submission using AJAX; or server side validation only when Javascript is disabled.

index.php

<?php
  // PHP v5.2.0+ required
  session_start();

  // include BotDetect Captcha library files
  require("botdetect.php");

  // create & configure the Captcha object
  $ContactCaptcha = new Captcha("ContactCaptcha");
  $ContactCaptcha->UserInputID = "CaptchaCode";
  $ContactCaptcha->CodeLength = 3;
  $ContactCaptcha->ImageWidth = 150;
  $ContactCaptcha->ImageStyle = ImageStyle::Graffiti2;
  
  require("process_form.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>PHP jQuery AJAX Contact Form CAPTCHA Sample</title>
  <link type="text/css" rel="Stylesheet" href="StyleSheet.css" />
  <!-- include the captcha stylesheet -->
  <link type="text/css" rel="Stylesheet" href="<?php echo CaptchaUrls::
      LayoutStylesheetUrl() ?>" />

  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <script type="text/javascript" src="js/jquery-1.7.2.min.js"></script>
  <script type="text/javascript" src="js/validation.js"></script>
</head>
<body>
  <form method="post" action="index.php" id="contactForm" name="contactForm">

    <h1>PHP jQuery AJAX Contact Form CAPTCHA Sample</h1>

    <fieldset>
      <legend>Contact Form</legend>
      
      <?PHP echo showValidationMessage("Form"); ?>

      <div class="input">
        <label for="Name">Name:</label>
        <input type="text" name="Name" id="Name" class="textbox" value="<?php 
            echo getValue('Name');?>" />
        <?php // name validation failed, show error message
        echo showValidationMessage("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 // email validation failed, show error message
        echo showValidationMessage("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>
        <br />
        <?php // message validation failed, show error message
        echo showValidationMessage("Message");
        ?>
        
      </div>


      <div class="input">
      <?php
       
      // only show the Captcha if it hasn't been already solved for the 
      current message
      if(!$ContactCaptcha->IsSolved) { ?>
        <label for="CaptchaCode">Retype the characters from the picture:
        </label>

        <?php echo $ContactCaptcha->Html(); ?>
        <input type="text" name="CaptchaCode" id="CaptchaCode" class="textbox"/>

        <?php // Captcha validation failed, show error message
        echo showValidationMessage("CaptchaCode");
      }?>
      </div>

      <input type="submit" name="SubmitButton" id="SubmitButton" 
          value="Submit" />

    </fieldset>
  </form>
</body>
</html>

The form and Captcha loading code is mostly basic and standard way of loading and configuring the BotDetect Captcha. In addition to the form elements, the showValidationMessage() php method outputs per field validation messages. Also note that the Captcha is not shown if it has already been solved by the user.

Main server side functionality is provided in the process_form.php file, which is included after creating and configuring the Captcha object.

process_form.php

<?php 
  // An array of validation responses
  $validationResult = array();
  
  // Type of request we are interested in. $_POST, $_GET or both - $_REQUEST
  $request = $_REQUEST;
  foreach($request as $formItem => $value) {
    $validationMethod = "Validate" . ucfirst($formItem);
    if (is_callable($validationMethod)) {
      // This is an array of validation result arrays for each field
      // see setElementValidationResponse() for more details.
      $validationResult[$formItem] = call_user_func($validationMethod, $value);
    }
  }

  // Total form validation result
  $isFormValid = true;
  foreach($validationResult as $formItem){
    $isFormValid = $isFormValid && $formItem['isValid']; 
  }

  // Respond according to results and request type
  
  // Regular response to POST form submission
  if (key_exists("SubmitButton", $request)) {
    // We want to make sure our Captcha is solved before continuing
    if (!$isFormValid || !$ContactCaptcha->IsSolved) { 
      // Form validation failed, show our error message
      $validationResult['Form'] = setElementValidationResult(false, null, "Please 
      review your input.");
    } else {
      // We send the message with content from the validator
      // Additional sanitization should be implemented along with the validation 
      $isSent = sendMessage($validationResult['Name']['validContent'], 
      $validationResult['Email']['validContent'], $validationResult['Message'][
      'validContent']);
      if ($isSent === true) {
        // each message requires a new Captcha challenge
        $ContactCaptcha->Reset();
        $validationResult['Form'] = setElementValidationResult(true, null, 'Your 
        message was sent.');
      } else {
        $validationResult['Form'] = setElementValidationResult(false, null, "The 
        server had problems sending your message.");   
      }
    }
  }
  
  // JSON response when AJAX parameter is passed
  if (isset($_GET['AJAX']) && $_GET['AJAX'] == 1) {
    header('Cache-Control: no-cache, must-revalidate');
    header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
    header('Content-type: application/json');
    
    echo json_encode($validationResult);
    // Terminate further output after JSON
    exit;
  }

  // we are processing a valid submited form
  function sendMessage($name, $email, $message) {
    return true;

    // TODO: send email only after configuring your email server settings
    /*
    $to = "YOUR_EMAIL_HERE";    
    $subject = "Message from your website"; 
        
    $headers = 'From: ' . $email;
    $headers .= '\r\n\'Reply-To: ' . $email;
    $headers .= '\r\n\'X-Mailer: PHP/' . phpversion();
    
    $body = $name . " (" . $email . ") sent the following message:\n" . $message;
    $body = wordwrap($body, 70);
    
    return mail($to, $subject, $body, $headers);
    */
  }
  
  // form a proper validation response to be assigned to a element
  // validation response to the field is an array of following:
  // "isValid" - status
  // "validContent" - optionaly filtered content
  // "validationMessage" - message for the user
  // Note that both valid and non-valid messages can be supplied.
  // This array format is a convention between both serverside methods
  // and clientside JS methods.
  function setElementValidationResult($status, $validContent = null, 
  $validationMessage = null) {
    $result['isValid'] = $status;
    if ($status && $validContent) $result['validContent'] = $validContent;
    if ($validationMessage) $result['validationMessage'] = $validationMessage;

    return $result;
  }

  // validate the Captcha
  function ValidateCaptchaCode($code) {
    global $ContactCaptcha;
    // We want to check if the user already solved the Captcha for this message
    $isHuman = $ContactCaptcha->IsSolved;
    if (!$isHuman) {
      // Validate the captcha
      // Both the user entered $code and $instanceId are used.
      
      global $request;
      if (array_key_exists('CaptchaInstanceId', $request)) { // ajax validation of 
      Captcha input only
        $instanceId = $request["CaptchaInstanceId"];
        $isHuman = $ContactCaptcha->AjaxValidate($code, $instanceId);
      } else { // regular full form post validation of all fields
        $isHuman = $ContactCaptcha->Validate($code);
      }
    }
    if ($isHuman === true) {
      return setElementValidationResult(true, null, "Solved!");
    } else {
      return setElementValidationResult(false, null, "Please retype the code.");  
    }
  }
  
  // name validation
  function ValidateName($name) {
    $name = stripcslashes($name);
    if (strlen($name) > 2 && strlen($name) < 30) {
      return setElementValidationResult(true, $name, "Ok!");
    } else {
      return setElementValidationResult(false, $name, "Please enter your name.");
    };
  }
  
  // email validaton
  function ValidateEmail($email) {
    if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
      return setElementValidationResult(true, $email, "Ok!");
    } else {
      return setElementValidationResult(false, $email, "This email is not valid.");
    }
  }
  
  // message validation
  function ValidateMessage($message) {
    $message = stripcslashes($message);
    $headerInjection = preg_match("/(bcc:|cc:|content\-type:)/i", $message);
    if (strlen($message) > 2 && strlen($message) < 255 && !$headerInjection) {
      return setElementValidationResult(true, $message, "Ok!");
    } else {
      return setElementValidationResult(false, $message, "Please enter your message.");
    }
  }
  
  
  // remember user input if validation fails
  function getValue($fieldName) {
    $value = '';
    if (isset($_REQUEST[$fieldName])) { 
      $value = $_REQUEST[$fieldName];
    }
    return $value;
  }
  

  // Validation message helper function used in HTML
  // the validation result is stored in a global array
  function showValidationMessage($element) {
    global $validationResult;

    $message = "";
    $messageClass[] = "validatorMessage";
    
    if (is_array($validationResult) && array_key_exists($element, $validationResult)) {
      $elementStatus = $validationResult[$element];
      
      if ($elementStatus['isValid'] == false) {
        $messageClass[] = "incorrect";
      } elseif ($elementStatus['isValid'] == true) {
        $messageClass[] = "correct";
      }
    
      if (array_key_exists('validationMessage', $elementStatus) && $elementStatus[
      'validationMessage'] != null) {
        $message = $elementStatus['validationMessage'];
      }
    }
        
    $validator = $element . "ValidatorMessage";
    $messageClass = implode(" ", $messageClass);
    
    $messageHtml = '<span class="' . $messageClass . '" id="' . $validator . '">' . 
    $message . '</span>';
    
    return $messageHtml;
  }
?>

There are a few parts to the server side functionality: the form processing and response part, validation methods and some methods to output validation methods.

The form processing is made dynamically, it provides a some general infrastructure for forms to lessen the amount of code redundancy. The result is that additional or different form fields only require adding a validation method in a correct naming scheme: Validate[Fieldname](). Also, this code handles differentiating POST and AJAX requests with form data.

Validation methods are straightforward, with the returning type being an array of validation state and response messages, supplied by the setElementValidationResult().

The captcha validation method, ValidateCaptchaCode(), operates on the field CaptchaCode and uses the existing $ContactCaptcha object created in index.php. The validation is performed by supplying the user entered data and an instance ID to the Validate() method on that object.

Printing errors is handled by the function showValidationMessage() which is used throughout index.php where specific field errors should be shown. This method works with the array arrangement that setElementValidationResult() provides.

Finally, the contact form data is sent via sendMessage() method.

js/validation.js

// Define the name of the Captcha field.
// It serves to access BotDetect Captcha client-side API later.
// https://captcha.com/doc/php/api/captcha-client-side-reference.html
var captchaUserInputId = "CaptchaCode";

$(document).ready(function() {
  // AJAX argument is added to differentiate from regular POST.
  var validationUrl = "index.php?AJAX=1"
  
  // Collect form elements we want to handle.
  var formElements = $('#contactForm input, #contactForm textarea');
  var form = $('#contactForm');

  formElements.blur(
    function(){
      var postData = {};
      // Additional check to skip over empty fields
      // This igores non-relevant triggering of onBlur
      if (this.value != ''){
        postData[this.id] = this.value;
      }

      if(this.id == captchaUserInputId){
        // In case of our Captcha field, we also send the InstanceId
        captchaUserInputField = $('#' + captchaUserInputId).get(0);
        postData["CaptchaInstanceId"] = captchaUserInputField.Captcha.InstanceId;
      }
      
      if(this.id == "SubmitButton"){
        return false;
      }

      $.post(validationUrl, postData, postValidation);
    }
  );
  
  form.submit(
    function(){
      var postData = {}
      formElements.each(
        function(){
        postData[this.id] = this.value;
        }
      );
      $.post(validationUrl, postData, postValidation);
      return false;
    }
   );
  
});

function postValidation(data){

  if (data[captchaUserInputId]){

    // Get the Captcha instance, as per client side API
    captcha = $('#' + captchaUserInputId).get(0).Captcha;

    if(!data[captchaUserInputId]["isValid"]){
      // We want to get another image if the Captcha validation failed.
      // User gets one try per image.
      captcha.ReloadImage();
    }
  }

  if (data["Form"] && data["Form"]["isValid"]){
    $("#SubmitButton").attr("disabled", "disabled");
  }
  updateValidatorMessages(data);
}

// Handling the display of validation messages
function updateValidatorMessages(data){
  for(var elementKey in data){
    validatedElement = data[elementKey];

    var elementValidatorMessage = $("#" + elementKey + "ValidatorMessage");
    
    if(validatedElement.hasOwnProperty("validationMessage")){
      elementValidatorMessage.text(validatedElement["validationMessage"]);
    }else{
      elementValidatorMessage.empty();
    }
    
    if(validatedElement["isValid"]){
      elementValidatorMessage.toggleClass("correct", true);
      elementValidatorMessage.toggleClass("incorrect", false);
    }else{
      elementValidatorMessage.toggleClass("correct", false);
      elementValidatorMessage.toggleClass("incorrect", true); 
    }
  }
}

User entered data for each field is sent to the server on field blur (lost focus) and on form submit. The server processes each field with the corresponding validate method and returns an array of results.

When sending the Captcha field, an exception is made. Along with the user entered code, Captcha instance Id needs to be sent along, so the server-side validation can properly validate.


Please Note

The information on this page is out of date and applies to a deprecated version of BotDetect™ CAPTCHA (v3.0).

An up-to-date equivalent page for the latest BotDetect Captcha release (v4) is BotDetect v4 Captcha documentation index.

General information about the major improvements in the current BotDetect release can be found at the What's New in BotDetect v4.0 page.