CakePHP 3.0 Form Model Validation BotDetect CAPTCHA Example (BotDetect v3.0; deprecated)

This code example shows how to integrate FormHelper and Model validation, which provide a lot of out-the-box functionality when used together.

First Time Here?

Check the BotDetect CakePHP 3.0 Captcha Quickstart for key integration steps.

The example 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.


BotDetect CakePHP 3.0 CAPTCHA Form Model Captcha validation screenshot

Files for this ('bd-captcha-cakephp-3.0-examples') example:

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

View – /src/Template/Contact/index.ctp

<!-- include the BotDetect layout stylesheet -->
<?= $this->Html->css(CaptchaUrls::LayoutStylesheetUrl(), ['inline' => false]) ?>

<div class="users form">

  <?= $this->Form->create(null, ['url' => ['controller' => 'contact', 'action' => 'index']]) ?>

  <fieldset>
    <legend><?= __('CakePHP Form Model Validation BotDetect CAPTCHA Example') ?></legend>

    <?php
      // show error messages
      if (!empty($errors)) {
        echo '<div class="cake-error"><ul>';

            foreach ($errors as $e) {
          echo '<li>' . reset($e) . '</li>';
        }

            echo '</ul></div>'; 
      }
    ?>

    <?= $this->Form->input('name') ?>
    <?= $this->Form->input('email') ?>
    <?= $this->Form->input('subject') ?>
    <?= $this->Form->input('message', ['type' => 'textarea']) ?>

  <?php 
    // Captcha is only shown if not already solved
    if (!is_null($captchaHtml)) {

      // show captcha image
      echo $captchaHtml;

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

  </fieldset>
  
  <?= $this->Form->button(__('Submit'), ['style' => 'float: left']) ?>
    <?= $this->Form->end() ?>
 </div>
  

The View part of this example 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.

Model – /src/Model/Validation/ContactValidator.php

<?php namespace App\Model\Validation;

use Cake\Validation\Validator;

class ContactValidator extends Validator {

  // a public field for Captcha validation status, adjusted by Controller code
  public $isCaptchaValid = false;
  
  public function __construct()
    {
        parent::__construct();
        
        $this
        ->notEmpty('name', 'The name field cannot be left empty')

        ->notEmpty('subject', 'The subject field cannot be left empty')
        ->add('subject', 'length', [
            'rule' => ['minLength', 10],
            'message' => 'Name must be at least 10 characters' 
        ])

        ->notEmpty('email', 'The email field cannot be left empty')
        ->add('email', 'validFormat', [
            'rule' => 'email',
            'message' => 'The email must be a valid email address'
        ])

        ->notEmpty('message', 'The message field cannot be left empty')
        ->add('message', 'length', [
            'rule' => ['minLength', 20],
            'message' => 'Message must be at least 20 characters' 
        ])

        ->notEmpty('CaptchaCode', 'The captcha code field cannot be left empty')
        ->add('CaptchaCode', 'validCaptcha', [
            'rule' => [$this, 'checkIsCaptchaValid'],
            'message' => 'CAPTCHA validation failed, try again'
        ]);
    }

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


}
  

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 object is available for use.

Controller – /src/Controller/ContactController.php

<?php namespace App\Controller;

use App\Controller\AppController;
use App\Model\Validation\ContactValidator;

// Importing the BotDetectCaptcha class
use CakeCaptcha\Integration\BotDetectCaptcha;

class ContactController extends AppController {

  
  // get captcha instance to handle for the contact page
    private function getContactCaptchaInstance() {
        // Captcha parameters
        $captchaConfig = [
          'CaptchaId' => 'ContactCaptcha', // a unique Id for the Captcha instance
          'UserInputId' => 'CaptchaCode', // Id of the Captcha code input textbox
          // The path of the Captcha config file is inside the config folder
          'CaptchaConfigFilePath' => 'captcha_config/ContactCaptchaConfig.php',
        ];
        return BotDetectCaptcha::GetCaptchaInstance($captchaConfig);
    }

  public function index() {

    // captcha instance of the contact page
    $captcha = $this->getContactCaptchaInstance();

    if ($this->request->is('post')) {

      $validator = new ContactValidator();

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

      $errors = $validator->errors($this->request->data());

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

      if (empty($errors)) {
        // 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
        $captcha->Reset();

        // TODO: send email

        $this->Flash->success('Your message was sent successfully');
        
        $this->redirect(['action' => 'index']);
      } else {
        $this->set('errors', $errors);
      }
      
    }

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

  }
}
  

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

After creating a function to get an instance of the Captcha class called getContactCaptchaInstance() and setting the Captcha markup required for the View display, the $this->request variable is checked to see if the form was submitted or not. The form data is encapsulated in the $this->request->data array, and which the FormHelper creates automatically. The user-entered Captcha code coresponds to the CaptchaCode field inside that array.

We want to pass the Captcha validation result to our ContactValidator, before starting the validation of model state itself.

Notice that the code proceeds to send the email only if the whole ContactValidator 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.

Captcha configuration options – /config/captcha_config/ContactCaptchaConfig.php

<?php if (!class_exists('CaptchaConfiguration')) { return; }

// BotDetect PHP Captcha configuration options

$LBD_CaptchaConfig = CaptchaConfiguration::GetSettings();

$LBD_CaptchaConfig->CodeLength = CaptchaRandomization::GetRandomCodeLength(4, 6);

In the code above, we have overridden the default settings of library. You can find a full list of available Captcha configuration options and related instructions at the Captcha configuration options page.



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.