Ajax Form Validation

garrensilverwing

New Member
Messages
148
Reaction score
0
Points
0
Hey guys,

Been a while for me. I have a new problem that I've run into recently while constructing a registration form for a website. The form is at http://www.denverchess.com/register. I got 99% of it working but there are two things. #1 -- if you submit the page with incomplete/invalid required information the Ajax declares it is invalid and tells the user however it does not refresh the captcha so when they submit the form again they get a new error saying the captcha is incorrect. Is there a way to refresh the captcha via ajax? (I guess you would have to have experience with captcha) #2 -- Since the form is validated using ajax after the submit button is pressed the page is not redirected. I would like to redirect the user to a success page briefly and then redirect to the log in page. The second part of that is easy enough but I am having trouble getting the page redirected in the first place. Is there a way to redirect the page after the validation returns as a success? I can post code but it is relatively long and probably won't fit in this post.

Thanks a bunch -- Chris
 

misson

Community Paragon
Community Support
Messages
2,572
Reaction score
72
Points
48
Both problems should be relatively simple to solve. To update the captcha, have the form handler return the info for a new captcha in the failure response to the AJAX call. The AJAX failure handler can then update the captcha widget. For success, have the form handler return the new URL in the success response and have the success handler set the window's location.
 

garrensilverwing

New Member
Messages
148
Reaction score
0
Points
0
as far as the redirect goes I tried adding a header('location:') to the ajax script but all it does is load the new page inside the current page. here is my php code:

PHP:
<?php
	include('../functions/index.php');
	include('functions.php');
	$results = mysql_query("SELECT Email Username FROM member") or die(mysql_error());
	$row = mysql_fetch_array($results) or $row = array();
	if (valid_captcha() == FALSE) $errors[] = "You did not enter the visual verifcation correctly!";
	if (valid_username($_POST['Username'],$row)==FALSE)
	{
		$errors[] = 'Username is required and must be alpha-numeric';
	}
	if (strlen($_POST['First_name'])<2 || strlen($_POST['Last_name'])<2)
		{
			$errors[] = "First and Last name must be at least 2 characters long.";
		}
	if (strlen($_POST['Password'])<5 || $_POST['Password']=='' || alpha_numeric($_POST['Password'])==FALSE)
	{
		$errors[] = 'A password is required and must be alpha-numeric';
	}

	if ($_POST['Password']!=$_POST['re_Password'])
	{
		$errors[] = 'The two passwords must match';
	}
	if (valid_email($_POST['Email'],$row) == FALSE)
	{
		$errors[] = "The email address you entered is either invalid, or is currently in use.";
	}
	if(is_array($errors))
	{
		echo "<h1><img src=\"nogood.png\" alt=\"Error!\" />The Following Errors Occured</h1><ul>";
		while (list($key,$value) = each($errors))
		{
			echo '<li>'.$value.'</li>';
		}
		echo "</ul>";
	}
	else {
		foreach($_POST as $key => $value) $$key = $value;
		$confirmnumber = rand(1000,9999);
		$ip=$_SERVER['REMOTE_ADDR'];
		$RegisteredDate = date('Y-m-d');
		if($emailprivacy === "on") $emailprivacy = 1; else $emailprivacy = 0;
		if($phoneprivacy === "on") $phoneprivacy = 1; else $phoneprivacy = 0;
		if($genderprivacy === "on") $genderprivacy = 1; else $genderprivacy = 0;
		if($Year !== -1 && $Month !== -1 && $Day !== -1) $birthday = "$Year-$Month-$Day";
		$sql = "INSERT INTO `member`(`uscf`,`ipaddress`,`FirstName`,`LastName`,`Username`,`Password`,`Email`,`Gender`,`Birthday`,`DateRegistered`,
		`Phone`,`Street1`,`Street2`,`City`,`State`,`Zip`,`ShowEmail`,`ShowGender`,`ShowPhone`,`ConfirmationNum`,`Confirmed`) 
		VALUES ('$uscf','$ip','$First_name','$Last_name','$username','$Password','$Email','$gender','$birthday','$RegisteredDate',
		'$phone','$address1','$address2','$city','$state','$zip','$emailprivacy','$genderprivacy','$phoneprivacy','$confirmnumber',0)";
		//mysql_query($sql) or die(mysql_error());
		header('location: ../games');
	}
?>
Code:
	<script type="text/javascript">
		window.addEvent('domready', function(){
	                $('registerForm').addEvent('submit', function(e) {
	                    new Event(e).stop();
	                    var log = $('log_res').empty().addClass('ajax-loading');
						this.send({
	                        update: log,
	                        onComplete: function() {
	                            log.removeClass('ajax-loading');
	                        }
	                    });
	                });
	            });
		function go_now () 
			{
				window.location.href = "http://localhost/denverchess";
			}
		function theChecker()
			{
				if(document.getElementById("ToS").checked==false)
					{
						document.getElementById("submit").disabled=true;
					}
				else
					{
						document.getElementById("submit").disabled=false;
					}
			}
	</script>
<div id="log">
	<div id="log_res">
	<!-- SPANNER -->
	</div>
</div>
<form method="post" action="register.php" enctype="multipart/form-data" id="registerForm">
<input type="submit" id="submit" name="submit" value="Submit" disabled="true" onclick="javascript:scroll(0,0);" />
</form>

the javascript is here: http://denverchess.com/register/js/mootools.js dunno if its readable
 

misson

Community Paragon
Community Support
Messages
2,572
Reaction score
72
Points
48
If you're using JS for validation without refresh, doing it server-side seems unnecessary. Why not use inline validation client-side?

Two problems with your use of MooTools. The first is that Element.send doesn't take an options object; it only takes a URL. To set options for an element, you must set the element's send property, as shown in the documentation:
Code:
$('registerForm').set('send', { /* options */ });
The second is that there is neither an "update" nor an "onComplete" option (there also isn't a "complete" option, if you were about to ask). Instead, you must subscribe to the "success" and "complete" events:
Code:
var sender = $('registerForm').get('send');
sender.addEvent('complete', function () {
    $('log_res').empty().removeClass('ajax-loading');
});

Setting a "Location" header is working, it's just that the response is going back to an AJAX object, rather than the window.

As for the response to registration form submission, the server-side form handler should output whatever info is needed client side: error messages and new captcha info for validation failure and the URL to redirect to for successful registration. You need to pick what response format to use. The two main contenders are JSON and HTML. With JSON, the AJAX response handler is responsible for (in the case of validation failure) displaying the error messages and changing the captcha or (in the case of success) redirecting. With HTML, the server-side form handler includes a script element in its output that (in the case of validation failure) changes the captcha or (in the case of success) redirects; the AJAX response handler simply splices the response into the document. The advantage to using JSON is errors can more readily be displayed next to the appropriate form field. The advantage to using HTML is simpler response handling.

example for registration form page, JSON response:
HTML:
<style type="text/css">
    .error {
        display: none;
    }
    .error.shown {
         color: red;
         display: inline;
    }
    .error.shown:before {
         content: "⚠";
    }
</style>
...
<div id="log">
    <ul id="errors">
    </ul>
</div>
<form ...>
   ...
    <label for="Username">Username</label>: <input name="Username" /><span id="Username_error" class="error"> </span>
...
<script type="text/javascript">
    // hideErrors, setCaptcha and displayValidationError need to be defined
    // example displayValidationError:
    /* displayValidationError shows the message 'msg' for input with name 'name'
     * For some inputs, the message is shown next to the input. For others, it's shown
     * in a list above the form.
     */
    function displayValidationError(name, msg) {
        var errElt;
        if (errElt = $(name . "_error")) {
            errElt.firstChild.nodeValue = msg;
            errElt.addClass('show');
        } else {
            errElt = document.createElement('li');
            errElt.appendChild(document.createTextNode(msg));
            $('errors').appendChild(errElt);
        }
    }
    ...
    var registerForm = $('registerForm');
    var sender = registerForm.get('send');    
    sender.addEvent('success', function(responseText, responseXML) {
        var response = JSON.decode(responseText);
        if (response.errors) {
            hideErrors(registerForm);
            setCaptcha(response.captcha);
            for (var name in response.errors) {
                displayValidationError(name, response.errors[name]);
            }
        } else {
            window.location = response.redirect;
        }
    });
   ...

in register.php:
PHP:
header('Content-type: application/json');
...
if (empty($_POST['Username']) || invalid_username($_POST['Username'])) { 
	$errors['Username'] = 'Username is required and must be alpha-numeric'; 
}
...
if ($errors) {
    $captcha = array('id' => ..., 'img' => ...);
    echo json_encode(array('errors' => $errors, 'captcha' => $captcha));
} else {
    echo json_encode(array('errors' => False, 'redirect' => '../games'));
}

example for registration form page, HTML response:
HTML:
<div id="log">
    <div id="log_res">
    </div>
</div>
...
<script type="text/javascript">
    ...
    var sender = $('registerForm').get('send', {evalScripts: true});
    sender.addEvent('success', function(responseText, responseXML) {
        $('log_res').set('html', responseText);
    });

in register.php:
PHP:
<?php
$successURL = '../games';
$captcha=array('id' => ..., ...);
...
if ($errors) {
    ?>
    <script type="text/javascript">setCaptcha(<?php echo json_encode($captcha); ?>)</script>
    <ul>
      <li><?php 
        echo implode("</li>\n      <li>", $errors); 
      ?></li>
    </ul>
    <?php
} else {
    ?><script type="text/javascript">window.location="<?php echo $successURL; ?>";</script><?php
}

And that's a quick sketch on handling registration responses. Later I'll post some feedback on the form handler script.
 
Last edited:

misson

Community Paragon
Community Support
Messages
2,572
Reaction score
72
Points
48
PHP:
        foreach($_POST as $key => $value) $$key = $value; 
        ...
        $sql = "INSERT INTO `member`(`uscf`,`ipaddress`,`FirstName`,`LastName`,`Username`,`Password`,`Email`,`Gender`,`Birthday`,`DateRegistered`, 
        `Phone`,`Street1`,`Street2`,`City`,`State`,`Zip`,`ShowEmail`,`ShowGender`,`ShowPhone`,`ConfirmationNum`,`Confirmed`)  
        VALUES ('$uscf','$ip','$First_name','$Last_name','$username','$Password','$Email','$gender','$birthday','$RegisteredDate', 
        '$phone','$address1','$address2','$city','$state','$zip','$emailprivacy','$genderprivacy','$phoneprivacy','$confirmnumber',0)";
This has some massive injection attacks. The $$key = $value allows a user to overwrite any variable. If you've started a session and use it to hold user data, they could authenticate themselves or escalate their privileges. The SQL statement is wide open to SQL injection via multiple inputs.

If you must import the contents of user input into the symbol table, first filter the input through a whitelist. Additionally, use PHP's extract rather than a foreach, as you can tell extract not to overwrite existing variables.

As for the SQL injection, is there any reason you haven't switched to prepared statements?

Some very minor stylistic points:

PHP:
   if (valid_captcha() == FALSE)
There's never a need to test whether a value is equal to TRUE or FALSE. It's more readable to use the not operator to check for FALSE and use nothing to check for TRUE.

PHP:
   if (not valid_captcha()) ...
// or rename validation functions
    if (invalid_captcha()) ...

PHP:
if(is_array($errors))
This will generate a warning if $errors isn't defined. Since empty arrays are cast to FALSE, simply set $errors to an array at the start, and use it as the sole component of the test.
PHP:
$errors = array();
if (not valid_captcha()) {
  ... // rest of validation tests
if ($errors) {
  ... // validation failed

PHP:
        while (list($key,$value) = each($errors))
Any reason you're using a while .. each here and a foreach later on? A big part of style is consistency. foreach is my personal preference, as I find it more readable. It's also slightly more performant, but not so much as you'd notice the difference.
 

garrensilverwing

New Member
Messages
148
Reaction score
0
Points
0
Thanks a million misson! the simple answer to all of your questions is this was just a rough draft where I was looking at a bunch of ajax tutorials. I threw this all together and thats why the style is inconsistent. I actually got it to work how I wanted before you replied but thanks for all the effort. If I put as much thought and time into the code I write as you did fixing other people's code I think I would have the best website ever.
 
Top