I've used this PHP/AJAX method of validation for a bit, but I think it could use a little peer review. The basic idea was to create a way of verifying forms in a nice manor using AJAX, but also fall-back to PHP when the user disables JavaScript. Anyway, here's the code with some explanation as to what is happening. One extra note: jQuery is used throughout the JavaScript files.
field_validation.php
if (isset($_POST['field']) && isset($_POST['value'])) {
require_once('includes/functions.php'); // Allows connection to the database
$field = $_POST['field'];
$value = $_POST['value'];
echo validate_input($field, $value);
}
function validate_input($field, $input) {
switch ($field) {
case 'email':
if ($input != '') {
// We need the validate_email() function, so we now require the file with it in. (Not included as I'm happy with it's validation and this is only used as an example)
require_once ('validate_email.php');
if (validate_email($input) == false) {
return 'Please enter a valid email';
}
} else {
return 'Please enter an email';
}
// Check email address is not already taken
$db = mysqlConnect();
$stmt = $db->stmt_init();
$stmt->prepare("SELECT EXISTS(SELECT 1 FROM users WHERE email = ?)");
$stmt->bind_param('s', $input);
$stmt->execute();
$stmt->bind_result($alreadyTaken);
$stmt->fetch();
$stmt->close();
$db->close();
if ($alreadyTaken) {
return 'Email address already in use, you cannot sign up again';
}
return true;
break;
case 'password':
if (strlen($input) <= 6) {
return 'Please enter a password more than 6 characters in length';
}
return true;
break;
case 'twitter':
if (strlen($input) < 1 ) {
return true;
}
if (strlen($input) >= 16) {
return 'Twitter Usernames cannot be more than 15 cahraters in length (excluding the @ symbol)';
}
$apiurl = 'http://api.twitter.com/1/users/show.xml?screen_name=' . $input;
if (!@fopen($apiurl, 'r'))
{
return 'Username does not exist, please enter a valid username';
}
// Check twitter name is not already taken
$db = mysqlConnect();
$stmt = $db->stmt_init();
$stmt->prepare("SELECT EXISTS(SELECT 1 FROM users WHERE twitter = ?)");
$stmt->bind_param('s', $input);
$stmt->execute();
$stmt->bind_result($alreadyTaken);
$stmt->fetch();
$stmt->close();
$db->close();
if ($alreadyTaken) {
return 'Twitter name already in use, please chose another name';
}
return true;
break;
case 'displayname':
if (strlen($input) < 1) {
return 'Please enter a Display Name';
}
if (strlen($input) > 64) {
return 'Please enter a Display Name shorter than 65 charaters';
}
$db = mysqlConnect();
$stmt = $db->stmt_init();
$stmt->prepare("SELECT EXISTS(SELECT 1 FROM users WHERE display_name = ?)");
$stmt->bind_param('s', $input);
$stmt->execute();
$stmt->bind_result($alreadyTaken);
$stmt->fetch();
$stmt->close();
$db->close();
if ($alreadyTaken) {
return 'Display Name already in use, please chose another name';
}
return true;
break;
case 'termsandconditons':
if ($input != 'on') {
return 'You must accept the Terms and Conditions';
}
return true;
break;
default:
return true;
break;
}
}
function showErrors($errors) {
?>
<div id="errors">
Errors<br>
<ul>
<?
for ($i = 0; $i < count($errors); $i++) {
if (isset($errors[$i]['name'])) {
// Highlight the field
?>
<script>
var cross = '<img src="/images/cross.png">';
$(document).ready(function(){
$('#<? echo $errors[$i]['name']; ?>hint').html(cross);
});
</script>
<?
}
?>
<li>
<? echo $errors[$i]['message']; ?><br >
</li>
<?
}
?>
</ul>
</div>
<?
}
This is included in each of the pages that require validation. I have removed some of the extra cases since they are pretty much the same as the ones shown.
validatefields.js
var cross = '<img src="/images/cross.png">';
var tick = '<img src="/images/tick.png">';
var loading = '<img src="images/loading.gif">';
// Send the data from the form to the PHP file 'jqueryvalidate.php', where they will call the function to validate the data
$('input:text,input:password,input[type=email]').blur(function() {
// Get the input from the form
var fieldid = $(this).attr('id');
var fieldvalue = $(this).attr('value');
$('#' + fieldid + 'hint').html(loading);
$.ajax ({
url: 'field_validation.php',
data: {field: fieldid, value: fieldvalue},
type: 'post',
cache: false,
success: function(message) {
if (message != true) {
// Error was returned, show a cross
$('#' + fieldid + 'hint').html(cross + message);
} else {
// No error, show a tick
$('#' + fieldid + 'hint').html(tick);
}
}
});
});
$('input:checkbox').click(function() {
// Get the input from the form
var fieldid = $(this).attr('id');
if ($(this).attr('checked') == 'checked') {
var fieldvalue = 'on';
} else {
var fieldvalue = 'off';
}
$('#' + fieldid + 'hint').html(loading);
$.ajax ({
url: 'jqueryvalidate.php',
data: {field: fieldid, value: fieldvalue},
type: 'post',
cache: false,
success: function(message) {
if (message != true) {
// Error was returned, show a cross
$('#' + fieldid + 'hint').html(cross + message);
} else {
// No error, show a tick
$('#' + fieldid + 'hint').html(tick);
}
}
});
});
As you can see this has 2 different functions for the validation. One for text, password and email fields and another for checkboxes. This could probably be tidied up a little?
Finally, an example form (mypage.php)
if($_SERVER['REQUEST_METHOD'] == "POST") {
$errors = array();
// Form has been submitted
foreach($_POST as $key => $value) {
$$key = $value; // Set the title of the field to its name
if (validate_input($key, $value) != 'true') {
// Value is invalid
$error['message'] = validate_input($key, $value);
$error['name'] = $key;
array_push($errors, $error);
}
}
if (!isset($_POST['termsandconditions'])) {
// This could be improved
if (validate_input('termsandconditions', 'off') != 'true') {
// Value is invalid
$error['message'] = validate_input('termsandconditions', 'off');
$error['name'] = 'termsandconditions';
array_push($errors, $error);
}
}
if (count($errors) > 0) {
// Input data failed validation
showErrors($errors);
} else {
// The form has successfully validated
}
<form action="mypage.php" method="POST">
<label for="displayname">*Display Name: </label>
<input type="text" name="displayname" id="displayname" <? if (isset($_POST['displayname'])) echo 'value="' . $_POST['displayname'] . '"' ?> ><br>
<div id="displaynamehint"></div><br>
<label for="email">*Email: </label>
<input type="email" name="email" id="email" <? if (isset($_POST['email'])) echo 'value="' . $_POST['email'] . '"' ?> ><br>
<div id="emailhint"></div><br>
<label for="password">*Password: </label>
<input type="password" name="password" id="password"><br>
<div id="passwordhint"></div><br>
<label for="twitter">Twitter Username: </label>
<span class="twitterinput">@<input type="text" name="twitter" id="twitter" <? if (isset($_POST['twitter'])) echo 'value="' . $_POST['twitter'] . '"' ?> ></span><br>
<div id="twitterhint"></div><br>
<input type="checkbox" name="termsandconditions" id="termsandconditions" <? if (isset($_POST['termsandconditions'])) echo 'checked="checked"'?> ><label for="termsandconditions">*I have fully read, understand and agree to <a href="termsandconditions.php" target="_blank">the terms and conditions</a></label><div id="termsandconditionshint"></div><br>
<input type="submit" name="submit" value="Submit">
</form>
Well, that was a lot of code to look through, so if you've looked through it, thank you! This was my first post to Code Review, so I'm hoping I've not missed anything vital.
if ($a)
orif ($a === true)
orif (!$a)
orif ($a === false)
(if you know it's a boolean, I'd tend towards the===
options). Pass your DB instance to the validate_input function. You're unnecessarily tying your validate_input function to mysqlConnect(). A random function should not be responsible for setting up a DB connection. And lastly, validate_input is doing way too much. Try to break it into smaller logical functions. – Corbin May 16 '12 at 18:26