Jump to content

A go on using ProcessWire forms with CAPTCHA


Valery
 Share

Recommended Posts

Hi everybody,

As much as we hate the CAPTCHA, it is still used. I decided to share my solution of using ProcessWire forms with CAPTCHA.

My goals were quite simple:

1. Create a form and customize it.

2. Get an Inputfield for a page field and append this Inputfield to the form.

3. Display a CAPTCHA.

4. Process the form and check its fields for correctness.

5. Check the CAPTCHA.

6. If steps 4-5 fail, display the form back with the user input intact.

What I found to be not quite easy was how to push a different CAPTCHA into an already submitted form. (A spoiler: this is done quite smoothly with the power of ProcessWire's Inputfields.)

Let's start, then.

My Form class has several methods:

  • appendFields() accepts an array of field names, gets their Inputfields from a template and appends these to the form
  • displayForm() adds a CAPTCHA and returns the form's HTML 
  • processForm() checks the CSRF session ID and user input and delegates CAPTCHA verification
  • getCAPTCHA() returns the html for the CAPTCHA
  • checkCAPTCHA() checks the user input against the CAPTCHA and sets the verification flag to true or false.

That's mostly it. Now to the code itself.


class Form
{

    public  $form;
    
    public  $captchaVerificationResult; // captcha verification flag (bool)    
    
    
    public function __construct () {
        
        // form is created upon class instantiation

        $form = wire('modules')->get("InputfieldForm");
        $form->action = wire('page')->path;
        $form->method = 'post';
        $form->attr("id+name", 'form');
        $form->attr("class", 'pure form'); // I use PureCSS class name here as an example
        
        // how form items are displayed can be customized

        $form->setMarkup(array (
            'list' => "\n{out}\n",
            'item' => "\n\t{out}\n\t",
            'item_label' => "\n\t\t<label class='pure-form' for='{for}'>{out}</label>",
            'item_content' => "\n\t{out}\n\t",
            'item_error' => "\n<p><span class='ui-state-error'>{out}</span></p>",
            'item_description' => "\n<p class='description'>{out}</p>",
            'item_head' => "\n<h2>{out}</h2>",
            'item_notes' => "\n<p class='notes'>{out}</p>",
                )
        );
        
        // form classes can also be customized

        $form->setClasses(array (
            'list' => 'Inputfields',
            'list_clearfix' => 'ui-helper-clearfix',
            'item' => '',
            'item_required' => '',
            'item_error' => 'ui-state-error InputfieldStateError',
            'item_collapsed' => '',
            'item_column_width' => '',
            'item_column_width_first' => '',
                )
        );

        $this->form = $form;
    }

    public function appendFields ($fields) {
        
        /*
         * $fields is an array of field names
         * 
         * for each field name a corresponding Inputfield is fetched from a page
         * and appended to the form
         * 
         */

        $page = wire('pages')->get("999999999"); // this is the page that has your fields

        foreach ($fields as $f_name) {

            $field = wire('fields')->get($f_name);
            $if = $field->getInputfield($page); // this page must have field name $f_name among its fields!           

            $this->form->append($if);
        }

        /*
         * 
         * CAPTCHA placeholder
         */
        
        /*
         * create an InputfieldMarkup field,
         * give it an ID and append to the form
         * 
         * Later this Inputfield can be easily found by its ID.
         *          
         * 
         */

        $inputfield = new InputfieldMarkup; // inputfield markup is appendable to form!        
        $inputfield->id = 'captcha1'; // id of the InputfieldMarkup with CAPTCHA

        $this->form->append($inputfield);

        /**
         * finally, the SUBMIT button
         * 
         */
        $submit = wire('modules')->get('InputfieldSubmit');
        $submit->skipLabel = Inputfield::skipLabelBlank; // remove the label
        $submit->name = 'submit_save';
        $submit->value = "Save";

        $this->form->append($submit);
    }

    public function get () {
        return $this->form;
    }

    public function displayForm ($form) {

        /* 
         * Each time the form is displayed, the CAPTCHA should be there
         * 
         * So, behore doing $form->render() let's get an InputFieldMarkup field by ID 
         * and pour some fresh CAPTCHA in it
         * 
         * 
         */
        
        
        $form->children->findOne("id=captcha1")->value .= $this->getCAPTCHA(); 

        return $form->render();
    }

    public function processForm ($form) {

        try {
            $form->processInput(wire('input')->post);
            wire('session')->CSRF->validate();
        } catch (WireCSRFException $e) {            
            die ($this->displayForm($form));
        }
        
        $this->checkCAPTCHA(); // sets class property $captcha_verification_result to false/true

        if (!$this->captcha_verification_result) {

            $form->children->findOne("id=captcha1")->value = "Wrong captcha, try again <br />"; // this will go before the CAPTCHA code
            
            die($this->displayForm($form));
        }
        else {
            
            // let's do some simple checks
            // you can and should do more of yours, of course
            
            $email = $form->get("b_email");
            if (filter_var($email->value, FILTER_VALIDATE_EMAIL)) {
                $email->error = "Incorrect email format";
            }

            if ($form->getErrors()) {
            // the form contains errors, display it with user input
                die($this->displayForm($form));
            }
            else {

                echo "Thanks for the submission!"; // everything is just right
            }
        }
    }

    public function getCAPTCHA () {        
        /*
         * 
         * $captcha_html = your CAPTCHA code
         * 
         */
        return $captcha_html; // have a lovely CAPTCHA
    }

    public function checkCAPTCHA () {
        /*
         * 
         * get some response and check it
         * 
         * 
         */

        if ($response->failed) {

            $this->captchaVerificationResult = false;
        }
        else {
            $this->captchaVerificationResult = true;
        }
    }

}

Here's how this can be used:

$form = new Form();

$formFields = array ("name", "email");

$form->appendFields($formFields);

$newForm = $form->get();

if ($input->post->submit_save) {

    // user submitted the form, process it and check for errors
    $form->processForm($newForm);    
    
    // the form was processed successfully because nothing die()'d. Now it's time to play with what's been submitted.

}
else {
    echo $form->displayForm($newForm); // if it's not been submitted, just display it and the CAPTCHA
}

With this being just a generic idea on how to do form processing with CAPTCHA, I hope portions of this code will prove helpful in your development.

Your comments, corrections and suggestions are very much welcome.

  • Like 6
Link to comment
Share on other sites

Hats off for that code and thanks for putting the comments.

The best captcha I ever saw was a puzzle so easy that even

a child could do it, like parking a car with drag and drop,

(still) too difficult for (current) captcha breakers.

  • Like 1
Link to comment
Share on other sites

Hats off for that code and thanks for putting the comments.

The best captcha I ever saw was a puzzle so easy that even

a child could do it, like parking a car with drag and drop,

(still) too difficult for (current) captcha breakers.

Is this code you used or wrote yourself. I would love to add a little bit on top of the honey pot we used. 

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...