Forums

The forums ran from 2008-2020 and are now closed and viewable here as an archive.

Home Forums Other Can someone recommend a customizable contact form, please? Reply To: Can someone recommend a customizable contact form, please?

#169176
__
Participant

Had some free time this evening. I assume you’ve had a chance to test the code so far.
(If not, shame, shame!)

So, once we print the form, the user will fill it out, and then click [Submit].

While I know your original form had javascript validation (and this one can too, but hear me out), remember that javascript runs client-side, and users have complete control over it. They can change it to do whatever they like, or even simply turn it off if it bothers them.

Javascript is a convenience —a nicety, for nice users—, not a security feature.

Everything must be checked server-side.

So, we write our validate method. First, we’ll add a property named $_option, which will be used for —you guessed it— options on how to handle validating the form submission.

protected $_option = array(
    "token.max"  => 1800,
    "token.min"  => 2,
    "value.trim" => true
);
  • token.max is how long (in seconds) it takes our security token to expire. 1800 is 60 (seconds) × 30 (minutes).
  • token.min is how long it takes our security tokens to become valid. (See me post above regarding “timers.”)
  • value.trim tells the validator whether or not to trim whitespace from submitted values.

We also need instructions on how each field is supposed to be validated. We can use an array, where the keys are the field names, and the values are (lists of) validation keywords that the validate method can use to decide how to validate each field.

Using this property as a validation “map” will also allow us to make sure we only accept fields that we expect: no one can “sneak in” extra form fields.

protected $_validation = array(
    "name"    => "required",
    "email"   => array("required","email"),
    "message" => null
);

Here’s the validate method itself, with lots of comments:

public function validate( array $_post ){
    // if the token is missing, or the honeypot is full,
    //  we conclude the submitter is either a spambot or an attacker
    if( ! isset( $_post["token"] ) || isset( $_post["required"] ) ){
        $this->_spamTrap();
        return false;
    }
    // check if the provided token is valid
    if( ! $this->_validateToken( $_post["token"] ) ){
        return false;
    }
    // validate each form field
    foreach( $this->_validation as $field=>$validation ){
        // missing values might simply be optional
        $value = isset( $_post[$field] )?
            $_post[$field]:
            "";
        // trim whitespace?
        if( $this->_option["value.trim"] ){
            $value = trim( $value );
        }
        // try any validation methods
        foreach( (array)$validation as $method ){
            $methodName = "_validate{$method}";
            if( ! $this->$methodName( $value,$m ) ){
                // $m is a _reference_ {@see http://php.net/references},
                //  which validation methods will use to provide our error messages.
                $this->_errors[$field] = $m;
                // discard the invalid value
                $value = "";
                // stop checking if any validation fails
                break;
            }
        }
        // if the submitted value was valid, we'll keep it.
        //  if it was invalid, then it will have been replaced with an empty string.
        $this->_values[$field] = $value;
    }
    // save antispam for last, since legitimate users will sometimes miss it
    //  (give them a chance; we don't want to discard an otherwise valid form submission)
    if( isset( $_post["antispam"] ) && $this->_validateAntispam( $_post["antispam"] ) ){
        // submission was good!
        return true;
    }
    // submission was bad.
    return false;
}

This method is something of a controller, basically managing which other methods should be called to validate each part of the submission. It’ll return true (if the submission was valid) or false (if something was wrong).

Two things to take special note of:

This method does not access the $_POST superglobal directly.
You have to pass $_POST in as a argument, i.e.,

$formIsValid = $contactForm->validate( $_POST );

The reason behind this is twofold. The first thing you might think of is that, at some point, you might want to change the form to use the GET method. You shouldn’t, but good thinking. There are other changes you might make, however. Someday you might want to do some pre-validation processing (or something). If the method used $_POST directly, it would be difficult to do so (and if you modify the $_POST values “in place,” you’re just making things confusing and easy to break). Using this approach, however, it’s easy:

$coolValues = do_something_cool_with( $_POST );
$formIsValid = $cntactFrom->validate( $coolValues );

This is called dependency injection. If a method needs something, you provide it —you never leave the method to “go find it” on its own. The second reason this is good is less obvious, but more important: it makes your code testable.

This method can be tested without actually having to set up a test domain and fill out and submit the form. You can make sure it works just by creating an array of “good” values and making sure it returns true, then an array of various “bad” values and making sure it returns false.

Another consequence of this is, if something does go wrong, it ends up being much easier to track down the problem; and much, much easier to fix.

Next,
This method simply reports whether the form is valid.

It doesn’t “do” anything. If there are errors, it finds them and provides error messages, but it doesn’t print them. If spam is suspected, it says so, but doesn’t grab control of the entire script, ban IPs, and die. This is good because, as above, it makes things much more flexible and robust.

I’ll leave you with that to think about. Let me know if you have any questions; next, we’ll look at all the methods that validate uses to do its work.

updated gist