We love Robots !

Stop spammers (part 1) : Create one custom registration form

friday 28 june 2013 at 17h20 • categories : How to

The last 1.0.x ionize lets you creating form in one very easy way.
Sadly, we often get spammed from bots which think their is something interesting in sending bullshit to our beautiful form...

How to build one custom registration form and protect it against spammers ?
This post will give you the answer...

What we will do to limit spam on registration

We will try to limit most of the bots by using one honeypot field.
This technique is not working at 100% but it is a good start.

One honeypot field is quite simple :

  • Your form has one field which should be stay empty
  • This field isn't visible to humans
  • The bot will stupidly fill this field (we hope he will)
  • Your form processing lib will check if this field is empty

1. Introduction : How the original Register Form works

This part is optional and describes how forms are declared and processed.
If you wants to implement your custom form, go to part 2 !

If you look at /application/config/forms.php, you will see the entry:

$config['forms']['register']

This config array setups the register form, its fields, the emails which must be sent but also the lib which will process the data when the form is posted.
The lib which process the data sent by this form is :

'register' => array
(
   'process' => 'TagManager_User::process_data',
   ...
)


We can have a look to TagManager_User() to see how it works in /application/libraries/Tagmanager/User.php.
The method called process_data() handles the register form.

First, it gets the form name, set in the form itself through one hidden field :

$form_name = self::$ci->input->post('form');

Then, depending on the form name, it processes data. In our case, the form is called 'register'.
We can have a look at the code from line 324 of this file :

case 'register':
   // Do some stuff here …

We will now create our own registration form and the linked registration processing library.

2. Create your custom registration form

What we want is to create one custom registration data processing lib, without hacking the core (means without writing anything in the TagManager_User() class.


2.1 Edit or create the file /themes/<your_theme>/config/forms.php :

This form definition will replace the default one.
Take care to declare each field you will be use through tags in your form view !

$config['forms'] = array
(
    // Register form
    'register' => array
    (
        // We will create this lib in the next step !
        'process' => 'TagManager_Register::process_data',
        'redirect' => 'referer',
        'messages' => array(
            'success' => 'form_alert_error_message',
        '    error' => 'form_alert_success_message',
        ),
        // Each field of the form
        'fields' => array
        (
            'firstname' => array(
                'rules' => 'trim|required|xss_clean',
                'label' => 'form_label_firstname',
            ),
            'email' => array(
                'rules' => 'trim|required|min_length[5]|valid_email|xss_clean',
                'label' => 'form_label_email',
            ),
            'password' => array(
                'rules' => 'trim|required|min_length[4]|matches[password2]|xss_clean',
                'label' => 'form_label_password',
            ),
            'password2' => array(
                'rules' => 'trim|required|min_length[4]|xss_clean',
                'label' => 'form_label_password_confirmation',
                // If set to FALSE, ths field will not be saved to DB
                'save' => FALSE,
            ),
            // This is our robots honeypot
            // In fact, we do not ask the city to the user: this field must be empty
            'city' => array(
                'label' => 'form_label_city'
            ),
        ),
    ),
);


2.2 Create your form view

Create the form in one view (can be ether one page or article view) :

<form method="post" action="">

    <input type="hidden" name="form" value="register" />

    <label for="firstname"><ion:lang key="form_label_firstname" /></label>
    <input id="firstname" name="firstname" type="text" value="<ion:form:register:field:firstname />" />
    <ion:form:register:error:firstname tag="p" class="input-error" />

    <label for="email"><ion:lang key="form_label_email" /></label>
    <input type="text" id="email-reg" name="email" value="<ion:form:register:field:email />"/>
    <ion:form:register:error:email tag="p" class="input-error" />

    <label for="password"><ion:lang key="form_label_password" /></label>
    <input type="password" id="password" name="password" value="<ion:form:register:field:password />"/>
    <ion:form:register:error:password tag="p" class="input-error" />

    <label for="password2"><ion:lang key="form_label_password_confirmation" /></label>
    <input type="password" id="password2" name="password2" value="<ion:form:register:field:password2 />"/>
    <ion:form:register:error:password2 tag="p" class="input-error" />

    <div class="city">
        <label for="city">Website</label>
        <input type="text" id="city" name="city" value="<ion:form:register:field:website />"/>
    </div>


    <input type="submit" class="button success" value="<ion:lang key='form_button_register' />" />

</form>

Add in your CSS file :

div.city {
   position: absolute;text-indent: -9999em;margin:0 0 0 -9999em;
}


2.3 Create the custom TagManager_Register() class

Create the file /themes/<your_theme>/librairies/Tagmanager/Register.php :

<?php

class TagManager_Register extends TagManager
{
    public static function process_data(FTL_Binding $tag)
    {
        if (TagManager_Form::validate('register'))
        {
            $honeypot = self::$ci->input->post('city');

            // SPAM !!!
            if ( ! empty($honeypot))
            {
                $message = TagManager_Form::get_form_message('success');
                TagManager_Form::set_additional_success('register', $message);

                $redirect = TagManager_Form::get_form_redirect();
                if ($redirect !== FALSE) redirect($redirect);
            }
            else
            {
                // Get user's allowed fields
                $fields = TagManager_Form::get_form_fields('register');
                if ( is_null($fields))
                    show_error('No definition for the form "register"');

                $fields = array_fill_keys($fields, FALSE);
                $user = array_merge($fields, self::$ci->input->post());

                // Compliant with User, based on username
                $user['username'] = $user['email'];
                $user['join_date'] = date('Y-m-d H:i:s');

                if ( ! User()->register($user))
                {
                    $message = User()->error();
                    if ( empty($message))
                        $message = TagManager_Form::get_form_message('error');

                    TagManager_Form::set_additional_error('register', $message);
                }
                else
                {
                    // Get the user saved in DB
                    $user = self::$ci->user_model->find_user($user['username']);

                    if (is_array($user))
                    {
                        // Must be set before set the clear password
                        $user['activation_key'] = User()->calc_activation_key($user);

                        $user['password'] = User()->decrypt($user['password'], $user);

                        // Merge POST data for email template
                        $user = array_merge($user, self::$ci->input->post());

                        // Create data array and Send Emails
                        TagManager_Email::send_form_emails($tag, '', $user);

                        $message = TagManager_Form::get_form_message('success');
                        TagManager_Form::set_additional_success('register', $message);

                        // Potentially redirect to the page setup in /application/config/forms.php
                        $redirect = TagManager_Form::get_form_redirect();
                        if ($redirect !== FALSE) redirect($redirect);
                    }
                    else
                    {
                        $message = TagManager_Form::get_form_message('error');
                        TagManager_Form::set_additional_error('register', $message);
                    }
                }
            }
        }
    }
}


That's it!

Of course some advanced system like http://wangguard.com could better help to avoid spam on your forms.

Base on the same approach, it should be easy to plug such service.

If you implement it, do not hesitate to share your code on the forum :
http://ionizecms.com/forum/ !