CakePHP Form Model Validation BotDetect CAPTCHA Sample

This code sample shows how to integrate BotDetect PHP Captcha validation and CakePHP data validation functionality. It uses Cake's FormHelper and Model validation, which provide a lot of out-the-box functionality when used together.

First Time Here?

Check the BotDetect CakePHP Captcha Quickstart for key integration steps.

The sample is based around a contact form which sends email if the user input is considered valid – a likely real world scenario for Captcha protection integration.

To keep the sample brief, it doesn't use a database backend. Still, this type of Model integration is ideal for protecting database-driven forms that take advantage of the CakePHP Model infrastructure.

BotDetect CakePHP CAPTCHA Form Model Captcha validation screenshot

Files for this sample:

The files are available as part of the BotDetect Captcha CakePHP integration package.

Model – /app/Model/Contact.php

<?php

class Contact extends AppModel {
  var $name = 'Contact';
  var $useTable = false;

  // a public field for Captcha validation status, adjusted by Controller code
  public $isCaptchaValid = false;

  var $validate = array(
    'name' => array(
      'rule' => '/.+/',
      'allowEmpty' => false,
      'required' => true,
    ),
    'subject' => array(
      'rule' => array('minLength', 10),
      'message' => 'Please enter at least 10 characters'
    ),
    'email' => array(
      'rule' => 'email',
      'message' => 'Please type your email address correctly'
    ),
    // Captcha code user input validation rules
    'CaptchaCode' => array(
      'rule' => 'checkIsCaptchaValid',
      'message' => 'Please retype the characters from the picture'
    )
  );

  // simply return the Captcha validation status
  function checkIsCaptchaValid() {
    return $this->isCaptchaValid;
  }

  function schema() {
    return array (
      'name' => array('type' => 'string', 'length' => 64),
      'email' => array('type' => 'string', 'length' => 64),
      'message' => array('type' => 'text', 'length' => 256),
      'subject' => array('type' => 'string', 'length' => 128),
    );
  }
}
?>

The Model implements a custom rule for the CaptchaCode field and bridges the validation state from the Controler via the $isCaptchaValid public class property. This property is modified in Controller code where the Captcha component is available for use.

Controller – /app/Controller/ContactController.php

<?php

class ContactController extends AppController {
  var $name = 'Contact';
  var $uses = 'Contact';
  var $helpers = array('Html', 'Form', 'Js');

  var $components = array(
    'Email', 
    'Session',
    // load the BotDetect Captcha component along with the other ones and set 
    // its parameters
    'Botdetect.Captcha' => array(
      'CaptchaId' => 'ContactCaptcha', // a unique Id for the Captcha instance
      'UserInputId' => 'ContactCaptchaCode' // Id of the Captcha code input 
      // textbox
    )
  );

  function index() {
    // make Captcha Html accessible to View code
    if (!$this->Captcha->IsSolved) {
      $this->set('captchaHtml', $this->Captcha->Html());
    } else {
      $this->set('captchaHtml', null);
    }

    if (isset($this->data['Contact'])) {
      // Captcha is only validated if not already solved
      if (!$this->Captcha->IsSolved) {
        // validate the user-entered Captcha code and adjust Model validity
        $this->Contact->isCaptchaValid = $this->Captcha->Validate($this->
          request->data['Contact']['CaptchaCode']);
      } else {
        $this->Contact->isCaptchaValid = true;
      }

      $this->Contact->create($this->data);

      // clear previous user input, since each Captcha code can only be 
      // validated once
      unset($this->request->data['Contact']['CaptchaCode']);

      if ($this->Contact->validates()) {
        // reset Captcha status after each sent email, since we don't want the
        // user to be able to send an unlimited number of emails after solving
        // the Captcha once
        $this->Captcha->Reset();

        // TODO: send email
        $this->redirect(array('action' => 'index'));
      } else {
        $this->Session->setFlash('Please correct the errors');
      }
    }
  }
}
?>

The Controller part of the sample provides the necessary helpers and data for the View to use, adding Captcha validation functionality as outlined in the BotDetect CakePHP integration guide.

After loading the component and setting the Captcha markup required for the View display, the $this->data variable is checked to see if the form was submitted or not. The form data is encapsulated in the Contact array which coresponds with the Model used by the Controller, and which the FormHelper creates automatically. The user-entered Captcha code coresponds to the CaptchaCode field inside that array.

After supplying the Captcha component with the correct data to validate, we want to pass the Captcha validation result to our Model, before starting the validation of model state itself.

Notice that the code proceeds to send the email only if the whole Model state validates, which includes the Captcha validation result we provided from outside of the model.

We also take advantage of the Captcha object's IsSolved property to correctly handle the case when the user retypes the Captcha code correctly but other field validation fails, as explained here.

View – /app/View/Contact/index.ctp

<h1>CakePHP Form Model Validation BotDetect CAPTCHA Sample</h1>

<?php
  // include the BotDetect layout stylesheet
  $this->Html->css(array('forms','/botdetect/public/lbd_layout'),'stylesheet', 
    array('inline' => false ) );

  echo $this->Form->create(null, array('url' => array('controller' => 
    'contact', 'action' => 'index')));

  echo $this->Form->input('name');
  echo $this->Form->input('email');
  echo $this->Form->input('subject');
  echo $this->Form->input('message');

  // Captcha is only shown if not already solved
  if (!is_null($captchaHtml)) {
    // display Captcha markup, wrapped in an extra div for layout purposes
    echo $this->Html->div('captcha', $captchaHtml, false);

    // Captcha code user input textbox
    echo $this->Form->input('CaptchaCode',
      array(
        'label' => 'Retype the characters from the picture:',
        'maxlength' => '10'
      )
    );
  }
  
  echo $this->Form->submit();
  echo $this->Form->end();
?>

The View part of this sample is straightforward. The FormHelper creates input fields of proper type and length in conjuction with the Model. Our Captcha Html is placed above the form field designated for retyping in the characters from the Captcha image, and only shown if the user hasn't already solved the Captcha challenge.


Please Note

BotDetect 3.0 PHP Captcha Library Beta1 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 3 PHP is already available for purchase at a 33% discounted pre-release price.