Thursday 14 November 2019

Web Tutorial: The Contact Us Page (Part 2/4)

In the last part, the form working as intended hinges upon users entering valid data. What if they didn't enter valid data? What if they missed stuff out? Worse, what if they entered stuff that breaks your HTML?

In this part, we implement controls to prevent that from happening. At the very basic level, we have server-side validation. For this, whatever data the user sends to the PHP server will be validated, and error messages will be displayed if data is not valid.

First, we put in placeholders for error messages. For Name, the only thing we need to worry about is missing data. So let's have a div, styled using the error CSS class. And within it, have a span tag with the error message within.
<div class="formrow">
    <label for="txtName">Name</label><br />
    <input type="text" id="txtName" name="txtName" maxlength="50" placeholder="e.g, Jose D'Cruz" />
</div>
<div class="error" id="name_required">
    <span>Name is required.</span>
</div>


For Email, you have two things to worry about - missing data and invalid data. You generally want there to be an email, and you want the email to be an actual email. So let's have two divs and two error messages.
<div class="formrow">
    <label for="txtEmail">Email</label><br />
    <input type="text" id="txtEmail" name="txtEmail" maxlength="50" placeholder="e.g, j_dcruz208@youremail.com" />
</div>
<div class="error" id="email_required">
    <span>Email is required.</span>
</div>
<div class="error" id="email_format_incorrect">
    <span>Email is in an incorrect format.</span>
</div>


And let's have one for Comments.
<div class="formrow">
    <label for="txtComments">Comments</label><br />
    <textarea type="text" id="txtComments" name="txtComments" rows="5" maxlength="500" wrap="hard" placeholder="e.g, You're awesome!"></textarea>
</div>
<div class="error" id="comments_required">
    <span>Comments are required.</span>
</div>


See your results!


In the PHP code, create the associative array, errors. Then fill in the array with key-value pairs as shown, setting all values to false.
$message = "";

$errors = array();
$errors["name_required"] = false;
$errors["email_required"] = false;
$errors["comments_required"] = false;
$errors["email_format_incorrect"] = false;

$mailsent = false;


Under the part where the form values are obtained, run a str_replace() function on each one, removing all spaces. If the result is an empty string, then the user tried to be funny somewhere. Either way, the string is blank and thus invalid.
$form_name = trim($_POST["txtName"]);
$form_email = trim($_POST["txtEmail"]);
$form_comments = trim($_POST["txtComments"]);

if (str_replace(" ", "", $form_name) == "")
{

}

if (str_replace(" ", "", $form_email) == "")
{

}

if (str_replace(" ", "", $form_comments) == "")
{

}   


Set the respective values of the key-value pairs in errors, to true.
$form_name = trim($_POST["txtName"]);
$form_email = trim($_POST["txtEmail"]);
$form_comments = trim($_POST["txtComments"]);

if (str_replace(" ", "", $form_name) == "")
{
    $errors["name_required"] = true;
}

if (str_replace(" ", "", $form_email) == "")
{
    $errors["email_required"] = true;
}

if (str_replace(" ", "", $form_comments) == "")
{
    $errors["comments_required"] = true;
}   


For Email, include an Else block. We only want to check for a valid email if there is an email. Run the string through the validateEmail() function, then set the key-value pair in errors if the function returns false.
if (str_replace(" ", "", $form_email) == "")
{
    $errors["email_required"] = true;
}
else
{
    if (!validateEmail($form_email))
    {
        $errors["email_format_incorrect"] = true;
    }           
}


And there we have the validateEmail() function. The parameter in this function is a string, str. It returns true by default.
    else
    {
        $message = "CSRF attack foiled!";
        $messageclass = "message_error";
    }
}

function validateEmail($str)
{
    return true;
}


Generally, there is no such thing as an email with less than 5 characters, so return false if that is true.
function validateEmail($str)
{
    if (strlen($str) < 5) return false;

    return true;
}


Obviously, if there is no "@" character in the string, it can't be an email.
function validateEmail($str)
{
    if (strlen($str) < 5) return false;
    if (strstr($str, "@") === false) return false;

    return true;
}


If the first or last letter of str is "@", it's also wrong.
function validateEmail($str)
{
    if (strlen($str) < 5) return false;
    if (strstr($str, "@") === false) return false;
    if (strpos($str, "@") == 0 || strpos($str, "@") == strlen($str) - 1) return false;

    return true;
}


Hold up.... why not use Regular Expressions?

Good question. Regular Expressions are really powerful. They're also a pain in the ass when dealing with email strings. These days, there are just too many ways a string can qualify as an actual email address. So stick with the basics.

There are ways to verify an email address by sending and receiving of course... but I really don't want to go there today.

Next, we declare an If block around the emailing portion.
if ()
{
    $headers = "MIME-Version: 1.0\r\n";
    $headers .= "Content-type:text/html;charset=UTF-8\r\n";
    $headers .= "From: " . $form_email . "\r\n";
    $headers .= "X-Mailer: PHP/" . phpversion();

    $subject = "Contact request from " . $form_name;
    $body = nl2br($form_comments);

    $mailsent = mail("teochewthunder@gmail.com", $subject, $body, $headers);

    if (!$mailsent)
    {
        $form_email = "";
        $form_name = "";
        $form_comments = "";

        $message = "Email sent. Thank you!";
        $messageclass = "message_success";
    }
    else
    {
        $message = "An error occured while trying to send your mail. Please try again.";
        $messageclass = "message_error";
    }
}   


This code should only fire off if the size of the array returned by running errors through an array_filter() function, is 0, which means there are no errors. That's because array_filter(), in its simplest form, filters out all values that are false.

More on array_filter() if you're interested!
if (sizeof(array_filter($errors)) == 0)
{
    $headers = "MIME-Version: 1.0\r\n";
    $headers .= "Content-type:text/html;charset=UTF-8\r\n";
    $headers .= "From: " . $form_email . "\r\n";
    $headers .= "X-Mailer: PHP/" . phpversion();

    $subject = "Contact request from " . $form_name;
    $body = nl2br($form_comments);

    $mailsent = mail("teochewthunder@gmail.com", $subject, $body, $headers);

    if (!$mailsent)
    {
        $form_email = "";
        $form_name = "";
        $form_comments = "";

        $message = "Email sent. Thank you!";
        $messageclass = "message_success";
    }
    else
    {
        $message = "An error occured while trying to send your mail. Please try again.";
        $messageclass = "message_error";
    }
}   


Now, we only want error messages to appear when input is invalid (duh), so let's add this to each div that's styled with error. This basically means that if that particular key-value pair in errors is not true, then style that div using hide as well.
<div class="formrow">
    <label for="txtName">Name</label><br />
    <input type="text" id="txtName" name="txtName" maxlength="50" placeholder="e.g, Jose D'Cruz" />
</div>
<div class="error <?php echo $errors["name_required"] ? "" : "hide" ?>" id="name_required">
    <span>Name is required.</span>
</div>

<div class="formrow">
    <label for="txtEmail">Email</label><br />
    <input type="text" id="txtEmail" name="txtEmail" maxlength="50" placeholder="e.g, j_dcruz208@youremail.com" />
</div>
<div class="error <?php echo $errors["email_required"] ? "" : "hide" ?>" id="email_required">
    <span>Email is required.</span>
</div>
<div class="error <?php echo $errors["email_format_incorrect"] ? "" : "hide" ?>" id="email_format_incorrect">
    <span>Email is in an incorrect format.</span>
</div>

<div class="formrow">
    <label for="txtComments">Comments</label><br />
    <textarea type="text" id="txtComments" name="txtComments" rows="5" maxlength="500" wrap="hard" placeholder="e.g, You're awesome!"></textarea>
</div>
<div class="error <?php echo $errors["comments_required"] ? "" : "hide" ?>" id="comments_required">
    <span>Comments are required.</span>
</div>


In the CSS, let's create the CSS class hide. It basically sets the display property to none.
<style>
    .hide
    {
        display: none;
    }
</style>


Refresh and test. Try entering no value, or all spaces for some. Try an obviously invalid email. See what happens?


Yep!


We're missing something...

You'll notice that the inputs in the form go blank when the error messages are displayed. This is not helpful. What if Name and Email were valid but only Comments had an error? Then the user would have to fill all these in again.

So add the appropriate value in these inputs. If the form hasn't been submitted, the variables will be empty strings anyway.
<div class="formrow">
    <label for="txtName">Name</label><br />
    <input type="text" id="txtName" name="txtName" maxlength="50" value="<?php echo $form_name; ?>" placeholder="e.g, Jose D'Cruz" />
</div>
<div class="error <?php echo $errors["name_required"] ? "" : "hide" ?>" id="name_required">
    <span>Name is required.</span>
</div>

<div class="formrow">
    <label for="txtEmail">Email</label><br />
    <input type="text" id="txtEmail" name="txtEmail" maxlength="50" value="<?php echo $form_email; ?>" placeholder="e.g, j_dcruz208@youremail.com" />
</div>
<div class="error <?php echo $errors["email_required"] ? "" : "hide" ?>" id="email_required">
    <span>Email is required.</span>
</div>
<div class="error <?php echo $errors["email_format_incorrect"] ? "" : "hide" ?>" id="email_format_incorrect">
    <span>Email is in an incorrect format.</span>
</div>

<div class="formrow">
    <label for="txtComments">Comments</label><br />
    <textarea type="text" id="txtComments" name="txtComments" rows="5" maxlength="500" wrap="hard" placeholder="e.g, You're awesome!"><?php echo $form_comments; ?></textarea>
</div>
<div class="error <?php echo $errors["comments_required"] ? "" : "hide" ?>" id="comments_required">
    <span>Comments are required.</span>
</div>


There you go!


But wait...

The fact that you're displaying user-input to screen not only puts you at risk of a XSS attack, but also, what if the input broke the HTML?

Try this as a name and submit.


Uh-oh!


What we should do here is sanitize the input. Create a function, sanitize(), in the PHP. Set it to run the argument, str, through the htmlentities() function, and add in other arguments to handle quotes, special characters and other HTML entities. That way, even if the user enters markup, it will not break your HTML when displayed as part of your form.
    else
    {
        $message = "CSRF attack foiled!";
        $messageclass = "message_error";
    }
}

function sanitize ($str)
{
    return htmlentities($str, ENT_COMPAT|ENT_QUOTES, "UTF-8", true);
}

function validateEmail($str)
{
    if (strlen($str) < 5) return false;
    if (strstr($str, "@") === false) return false;
    if (strpos($str, "@") == 0 || strpos($str, "@") == strlen($str) - 1) return false;

    return true;
}


And then ensure that your displayed output is sanitized.
<div class="formrow">
    <label for="txtName">Name</label><br />
    <input type="text" id="txtName" name="txtName" maxlength="50" value="<?php echo sanitize($form_name); ?>" placeholder="e.g, Jose D'Cruz" />
</div>
<div class="error <?php echo $errors["name_required"] ? "" : "hide" ?>" id="name_required">
    <span>Name is required.</span>
</div>

<div class="formrow">
    <label for="txtEmail">Email</label><br />
    <input type="text" id="txtEmail" name="txtEmail" maxlength="50" value="<?php echo sanitize($form_email); ?>" placeholder="e.g, j_dcruz208@youremail.com" />
</div>
<div class="error <?php echo $errors["email_required"] ? "" : "hide" ?>" id="email_required">
    <span>Email is required.</span>
</div>
<div class="error <?php echo $errors["email_format_incorrect"] ? "" : "hide" ?>" id="email_format_incorrect">
    <span>Email is in an incorrect format.</span>
</div>

<div class="formrow">
    <label for="txtComments">Comments</label><br />
    <textarea type="text" id="txtComments" name="txtComments" rows="5" maxlength="500" wrap="hard" placeholder="e.g, You're awesome!"><?php echo sanitize($form_comments); ?></textarea>
</div>
<div class="error <?php echo $errors["comments_required"] ? "" : "hide" ?>" id="comments_required">
    <span>Comments are required.</span>
</div>


There you are, it works!


It even works with accented and foreign characters!



Client-side validation

I know what you're thinking - why client-side validation when server-side validation is perfectly fine? Well, for starters, it's always faster than validating the data after it's been sent.

The task of client-side validation is to validate the data before it is submitted to the server. And because JavaScript can be turned off (not that I'd recommend doing so), the server-side validation, while slower, is a very dependable fallback.

So, long story short... you have server-side validation, and it's time for client-side validation.

In the form tag, add an onsubmit attribute. This is an event handler that triggers whenever the form is submitted. If the value is true, then it submits. If it is false, execution halts there. The function triggered is validateForm().
<form action="" method="POST" onsubmit="return validateForm();">


In the JavaScript, create the validateForm() function. First, get the placholders by declaring the variable placehlders and setting it to the array returned by the getElementsByClassName() method, and passing in "error" as an argument. This will get all elements with the CSS class of error.
<script>
    function validateForm()
    {
        var placeholders = document.getElementsByClassName("error");
    }
</script>


Iterate through the placeholders array. For every element whose class is "error", set it to "error hide". This means to hide all placeholders.
var placeholders = document.getElementsByClassName("error");

for (var i = 0; i < placeholders.length; i++)
{
    placeholders[i].className = "error hide";
}


Create an array, errors. Declare variables txtName, txtEmail and txtComments, and get the appropriate value from the DOM.
var placeholders = document.getElementsByClassName("error");

for (var i = 0; i < placeholders.length; i++)
{
    placeholders[i].className = "error hide";
}

var errors = [];

var txtName = document.getElementById("txtName");
var txtEmail = document.getElementById("txtEmail");
var txtComments = document.getElementById("txtComments");


Now, we're going to replicate in JavaScript what we did in the PHP. The JavaScript equivalent of replacing spaces with empty strings is the replace() method using a Regular Expression and an empty string as arguments.
var txtName = document.getElementById("txtName");
var txtEmail = document.getElementById("txtEmail");
var txtComments = document.getElementById("txtComments");

if (txtName.value.replace(/\s/g, "").length == 0)
{

}

if (txtEmail.value.replace(/\s/g, "").length == 0)
{

}
else
{
    if (!validateEmail(txtEmail.value))
    {

    }                   
}

if (txtComments.value.replace(/\s/g, "").length == 0)
{

}


For each error that is triggered, push the appropriate string into the errors array.
if (txtName.value.replace(/\s/g, "").length == 0)
{
    errors.push("name_required");
}

if (txtEmail.value.replace(/\s/g, "").length == 0)
{
    errors.push("email_required");
}
else
{
    if (!validateEmail(txtEmail.value))
    {
        errors.push("email_format_incorrect");
    }                   
}

if (txtComments.value.replace(/\s/g, "").length == 0)
{
    errors.push("comments_required");
}


And then let's create the validateEmail() function in JavaScript. It's just using equivalent functions of the ones we used in the PHP function we created.
function validateForm()
{
    var placeholders = document.getElementsByClassName("error");

    for (var i = 0; i < placeholders.length; i++)
    {
        placeholders[i].className = "error hide";
    }

    var errors = [];

    var txtName = document.getElementById("txtName");
    var txtEmail = document.getElementById("txtEmail");
    var txtComments = document.getElementById("txtComments");

    if (txtName.value.replace(/\s/g, "").length == 0)
    {
        errors.push("name_required");
    }

    if (txtEmail.value.replace(/\s/g, "").length == 0)
    {
        errors.push("email_required");
    }
    else
    {
        if (!validateEmail(txtEmail.value))
        {
            errors.push("email_format_incorrect");
        }                   
    }

    if (txtComments.value.replace(/\s/g, "").length == 0)
    {
        errors.push("comments_required");
    }
}

function validateEmail(str)
{
    if (str.length < 5) return false;
    if (str.indexOf("@") == -1) return false;
    if (str.indexOf("@") == 0 || str.indexOf("@") == str,length - 1) return false;

    return true;
}


Now, if the errors array is not empty, return true. This basically means that the form will submit.
if (txtComments.value.replace(/\s/g, "").length == 0)
{
    errors.push("comments_required");
}

if (errors.length == 0)
{
    return true;
}


If not, iterate through the errors array using a For loop and display the appropriate placeholders using the getElementById() method. And, of course, return false.
if (errors.length == 0)
{
    return true;
}
else
{               
    for (var i = 0; i < errors.length; i++)
    {
        document.getElementById(errors[i]).className = "error";
    }

    return false;
}


This should appear exactly like the PHP validation that we wrote in the first part of this tutorial, except that the form will not submit if there are errors! And if you turn JavaScript off, it should submit, then validate via the PHP code.


Yep!


Next

We will be beautifying this form somewhat and making it visually less confusing.

No comments:

Post a Comment