PHP Built-In Ajax CAPTCHA Validation Code Sample (BotDetect v3.0; deprecated)

The PHP BotDetect built-in Ajax Captcha validation sample shows how to properly perform Ajax CAPTCHA validation using built-in BotDetect CAPTCHA client-side functionality, which doesn't require any 3rd party Ajax frameworks.

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 validation of all form fields.

Ajax CAPTCHA validation improves the user experience by reducing CAPTCHA validation response time, giving users much faster feedback about the validation result.

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 as well.

In case of any Ajax errors or timeouts, the sample simply falls back to full form posting and server-side CAPTCHA validation.

Downloaded Location

The PHP built-in Ajax Captcha validation code sample is included in the samples/captcha_ajax_validation_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 Ajax 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() ?>" />
</head>
<body>
  <form method="post" action="process_form.php" id="form1">

    <h1>BotDetect PHP CAPTCHA Ajax 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("NameValidator"); ?>
      </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("EmailValidator"); ?>
      </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("MessageValidator"); ?>
      </div>


      <div class="input">
      <?php // Adding BotDetect Captcha to the page
      $AjaxValidationCaptcha = new Captcha("AjaxValidationCaptcha");
      $AjaxValidationCaptcha->UserInputID = "CaptchaCode";
      $AjaxValidationCaptcha->CodeLength = 3;
      $AjaxValidationCaptcha->ImageWidth = 150;
      $AjaxValidationCaptcha->ImageStyle = ImageStyle::Graffiti2;

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

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

        <?php echo getValidationStatus("CaptchaValidator");
      } ?>
      </div>

      <input type="submit" name="SubmitButton" id="SubmitButton" 
      value="Submit" onclick="return validateForm();"/>

    </fieldset>
    
    <?php
      // remember user input if validation fails
      function getValue($fieldName) {
        $value = '';
        if (isset($_REQUEST[$fieldName])) { 
          $value = $_REQUEST[$fieldName];
        }
        return $value;
      }

      // server-side validation message helper function
      function getValidationStatus($validator) {
        $requestParam = substr($validator, 0, -4);
        $messageHtml = '<span class="incorrect" id="' . $validator . '" 
          style="display:';

        if ((isset($_REQUEST[$requestParam]) && $_REQUEST[$requestParam] == 0))
        {
          // server-side field validation failed, show validator
          $messageHtml .= 'inline';
        } else {
          // server-side field validation passed, hide validator
          $messageHtml .= 'none';
        }

        $messageHtml .= ';">*</span>';

        return $messageHtml;
      }
    ?>

  <script type="text/javascript" src="validation.js"></script>

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

The input form code is quite similar as in the the script containing our client-side validation code.

validation.js

function validateForm() {
  var isNameValid = validateName();
  var isEmailValid = validateEmail();
  var isMessageValid = validateMessage();
  var isCaptchaValid = validateCaptcha();
  
  return isNameValid && isEmailValid && isMessageValid && isCaptchaValid;
}

function validateName() {
  var result = false;
  
  var input = document.getElementById("Name");
  if (input && 'text' == input.type) {
    var value = input.value.replace(/^\s+|\s+$/g,""); // trim leading and 
    trailing whitespace
    if (value && value.length > 2 && value.length < 30) {
      result = true;
    }
  }
  
  var validator = document.getElementById("NameValidator");
  updateValidatorDisplay(validator, result);
  
  return result;
}

function validateEmail() {
  var result = false;
  
  var input = document.getElementById("Email");
  if (input && 'text' == input.type) {
    var value = input.value.replace(/^\s+|\s+$/g,""); // trim leading and 
    trailing whitespace
    if (value && value.length > 5 && value.length < 100 && value.match(
    /^(.+)@(.+)\.(.+)$/)) {
      result = true;
    }
  }
  
  var validator = document.getElementById("EmailValidator");
  updateValidatorDisplay(validator, result);
  
  return result;
}

function validateMessage() {
  var result = false;
  
  var input = document.getElementById("Message");
  if (input && 'textarea' == input.type) {
    var value = input.value.replace(/^\s+|\s+$/g,""); // trim leading and 
    trailing whitespace
    if (value && value.length > 2 && value.length < 255) {
      result = true;
    }
  }
  
  var validator = document.getElementById("MessageValidator");
  updateValidatorDisplay(validator, result);
  
  return result;
}

function validateCaptcha() {
  var result = false;
  
  var input = document.getElementById("CaptchaCode");
  if (input && 'text' == input.type) {
    var value = input.value.replace(/^\s+|\s+$/g,""); // trim leading and 
    // trailing whitespace
    if (value && value.length > 0 && value.length < 10) {
      result = input.Captcha.Validate(); // call the BotDetect built-in 
      // client-side validation function
      // this function must be called after the Captcha is displayed on the 
      // page, otherwise the client-side object won't be initialized
    }
  }
  
  var validator = document.getElementById("CaptchaValidator");
  updateValidatorDisplay(validator, result);
  
  return result;
}


// register handlers for the four steps of the BotDetect Ajax validation 
// workflow
BotDetect.RegisterCustomHandler('PreAjaxValidate', OnCaptchaValidate);
BotDetect.RegisterCustomHandler('AjaxValidationFailed', OnCaptchaIncorrect);
BotDetect.RegisterCustomHandler('AjaxValidationPassed', OnCaptchaCorrect);
BotDetect.RegisterCustomHandler('AjaxValidationError', OnAjaxError);

function OnCaptchaValidate() {
  // hide validator
  var captchaValidator = document.getElementById("CaptchaValidator");
  updateValidatorDisplay(captchaValidator, true);
  
  // disable multiple clicks while waiting for server response
  var submitButton = document.getElementById("SubmitButton");
  submitButton.disabled = true;
  submitButton.value = 'Validating...';
}

function OnCaptchaCorrect() {
  // hide validator
  var captchaValidator = document.getElementById("CaptchaValidator");
  updateValidatorDisplay(captchaValidator, true);
  
  // automatically proceed to server-side validation
  var submitButton = document.getElementById("SubmitButton");
  submitButton.disabled = false;
  submitButton.value = 'Submit';
  submitButton.focus();
  submitButton.click();
}

function OnCaptchaIncorrect() {
  // show validator
  var captchaValidator = document.getElementById("CaptchaValidator");
  updateValidatorDisplay(captchaValidator, false);
  
  // allow the users to re-try the new Captcha 
  var submitButton = document.getElementById("SubmitButton");
  submitButton.disabled = false;
  submitButton.value = 'Submit';
}
      
function OnAjaxError() {
  // fall back to server-side validation
  var submitButton = document.getElementById("SubmitButton");
  submitButton.disabled = false;
  submitButton.value = 'Submit';
  submitButton.focus();
  submitButton.click();
}

function updateValidatorDisplay(validator, result) {
  if (validator) {
    if (result) { 
      validator.style.display = 'none'; 
    } else {
      validator.style.display = 'inline'; 
    }
  }
}

The first part of the script defines validation methods for other form fields and is not relevant for Ajax Captcha validation implementation.

Handlers for the four Ajax-related client-side events will be automatically called by the Validate() function call at appropriate stages of the Ajax Captcha validation workflow.

Main concerns of these handlers are displaying the validation result, disabling multiple consecutive button clicks while Ajax validation is in progress, and handling Ajax errors and timeouts.

Details of the Ajax implementation are encapsulated in BotDetect source for easier use. They involve making an Ajax request at a special endpoint in the Captcha handler, and processing the returned JSON result. You do not need to concern yourself with such details, but can use the supplied Validate() function as is.

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;
  }

  // sumbitted 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 
  $AjaxValidationCaptcha = new Captcha("AjaxValidationCaptcha");
  $AjaxValidationCaptcha->UserInputID = "CaptchaCode";
  if (!$AjaxValidationCaptcha->IsSolved) {
    $isHuman = $AjaxValidationCaptcha->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);
  $AjaxValidationCaptcha->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);
  }
  
?>

Server-side Captcha validation is a necessary fallback for all Ajax uses, as explained in the form Captcha sample.

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 PHP CAPTCHA Ajax Validation 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 PHP CAPTCHA Ajax Validation 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

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.