Lesson 9 - Improving the e-mail form in PHP
In the previous lesson, E-mail form in PHP, we programmed a simple e-mail form. In today's lesson, we're going to improve it a bit.
Pre-filling forms
With the way we have our form set up if we enter something wrong or leave a field empty, the script will display an error message and empty the form. This is because we enter the information, be it right or wrong, we would already be on a different page.
Since we are submitting and processing data in the same script, we can easily
fill the submitted values from the POST
back to the form. Then,
it'll look like the form was never cleared.
We'll fill the $name
, $email
and
$message
variables with the values from $_POST
in the
PHP block right above the form. It kind of goes without saying, but we will only
be able to use them when the values exist in POST
. If there aren't,
we'll fill the variables with empty strings.
With that in mind, we'd probably end up writing something like this:
if (isset($_POST['name'])) $name = $_POST['name']; else $name = ";
The code is kind of long and writing it out for each of the variables sounds
rather unsavory, to me at least. It would only get more and more annoying if the
form had a larger amount of fields. As a workaround, we'll use the
ternary operator, which is a shorter version of the
if
... else
sequence. The ternary operator
always returns a value, that means that we can't use it in
place of all conditions. It consists of 3 parts. In the first part, we write a
condition followed by a question mark and the value that we want to be returned
when the condition is true. Then, we write a colon and last of all, the value
that will be returned when the condition is false.
One way of doing it would be like this:
$name = (isset($_POST['name'])) ? $_POST['name'] : "";
Let's take a couple of steps back and see what we've got. If the given key
exists in POST
, we fill the $name
variable with its
value. If it doesn't, we assign an empty string to it, which is done simply by
writing out two empty quotes.
We'll do the same for the $email and the $message.
Now let's modify the form to make the variables fill in the inputs' value properties. If the form hasn't been submitted, an empty string will be inserted in its place. Otherwise, the data that the user provided will be set as the values.
Here's one way we could do it:
<input name="name" type="text" value="<?php echo $name ?>" />
However, if all we need to do is print content in a PHP sequence, we could
use the shorter version of the <?=
directive. You would get the
same result by doing this:
<input name="name" type="text" value="<?= $name ?>" />
There are still a couple of more things we have to add/fix. Since the
text in the $name
variable comes from the user, we
will have to validate it. What if the user filled it with HTML code? It would
become a part of our website.
In our case, it won't matter too much, but there are cases when it would. Imagine that you're printing messages from users, e.g. comments under an article, and you get a user that submits an HTML code instead of a form linked to the user's processing script on a different website. What if their processing form requires that your visitors enter their password in order to submit "your" form? If your form actually prints the message, its code would end up displaying the malicious form. Some of your visitors might actually fill their password in which would then be sent to the attacker...and you'd be in trouble.
This kind of attack is called XSS (Cross Site Scripting). The protecting
yourself against it is pretty simple, we'll use the
htmlspecialchars()
function before printing any variable to our
HTML code. The function will convert angle HTML brackets and other
special characters to HTML entities. The potentially dangerous code would then
be treated as regular text and the web browser no longer process it as HTML
code.
Keep in mind that we have to sanitize each and every variable, even those that don't contain user data. You never truly know whether a variable's content has been entered by the user, you simply cannot control everything. Therefore, we sanitize every single variable at the exact place where we're print it. Once you get to the more advanced lessons and you know a good bit about object-oriented architecture, you'll be able to automate this process (you won't have to sanitize data manually).
Let's take a look at our form's HTML code, including the directive above:
<?php if ($notice) echo('<p>' . htmlspecialchars($notice) . '</p>'); $name = (isset($_POST['name'])) ? $_POST['name'] : ""; $email = (isset($_POST['e-mail'])) ? $_POST['e-mail'] : ""; $message = (isset($_POST['message'])) ? $_POST['message'] : ""; ?> <form method="POST"> <table> <tr> <td>Your name</td> <td><input name="name" type="text" value="<?= htmlspecialchars($name) ?>"/></td> </tr> <tr> <td>Your e-mail</td> <td><input name="e-mail" type="e-mail" value="<?= htmlspecialchars($email) ?>"/></td> </tr> <tr> <td>Current year</td> <td><input name="abc" type="number" /></td> </tr> </table> <textarea name="message"><?= htmlspecialchars($message) ?></textarea> <br /> <input type="submit" value="Send" /> </form>
Let's try it out. We'll leave a field empty and submit it. An error notification will appear but the data we entered will be preserved:
Redirecting
There are two more crucial issues with our form now. It's always pre-filled, even if we send the message successfully. Also, if the user presses the F5 key, the form will re-send the email. Every single form that submits and processes data in the same file has this issue.
When the form processing is done, we'll redirect to the same exact URL. By
doing this, the data in $_POST
will be lost, and any further page
refreshing will not automatically send data.
We'll redirect using the header()
function, which sends a header
to the web browser. Headers are able to carry redirecting instructions when the
Location
keyword is included.
Let's redirect our form right after assigning the success notice:
if ($success) { $notice = 'E-mail was successfully sent, I'll get back to you soon.'; header('Location: mailform.php'); exit; }
We terminate the script using the exit()
function because
redirecting won't do it for us. All redirecting does is tell the visitor's
browser to go to another location.
The problem with re-submitting the form when the page is refreshed has now
been resolved. However, it no longer displays the success notice. This happened
because when we redirected the user to the "other site", the contents stored in
the variables were lost altogether, including the $notice
. There
are several solutions to this issue, the simplest one is to pass a variable
through the GET
method in the new site. Based off of that, the site
would recognize whether it was redirected to it after a successful form
submission or not, and display the appropriate notice. Let's modify the code to
suit our new needs:
header('Location: mailform.php?success=yes'); exit;
Let's move back to the very beginning of our script where we set the notice
to an empty string
. We'll check if we received the
success
parameter in the address. If that is the case, we'll set
the notice to the text that it would print out after a successful send. As we
already know, the parameters from the URL address are accessed via the
$_GET
array:
$notice = ""; if (isset($_GET['success'])) $notice = 'E-mail was successfully sent, I'll get back to you soon.';
Congratulations, You now have a fully functioning form!
BEWARE! You can only redirect if we have not yet printed any HTML. At the moment where anything is printed, PHP sends a header stating that you're sending an HTML document. The header can only be sent once, so if you tried to redirect in the middle of the file, you'd get a "Headers already sent" error message and no redirection would occur:
<html> <body> <?php // This code is wrong header('Location: index.php'); exit; ?> </body> </html>
You should always remain aware of what you're doing because mistakes aren't always that obvious:
-- Empty line added by new line character -- <?php header('Location: index.php'); ?> <html> <body> </body> </html>
There is a new-line character at the beginning of the file. Even that one character starts the output which forces PHP to send the header. The same problem comes with whitespaces. If you save your files using UTF-8 encoding with BOM, it may cause issues like this as well. However, if you use a smart IDE, you won't run into this sort of problem.
Note: There are people out there who redirect by echoing a JavaScript code that changes the browser window's address. Sort of like this:
// This code is wrong echo('<script type="text/javascript"> window.location = "index.php" </script>');
Doing it this way is reckless and can also prove to be dangerous in certain scenarios. Don't do it.
We could style the form as well, to make it look nicer. The message could roll out as a bubble or something, but that's not what our PHP lessons are about, we have other courses where you can learn all about styling HTML documents. In the next lesson, Putting web pages together using PHP, you'll learn how to create websites dynamically. The completed e-mail form is available for download below.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 82x (1.29 kB)
Application includes source codes in language PHP