} // password length if strlen $_POST['register_password'] < 6 { $allClear = false; $this->registrationErrors[] = 'Your password is too short, it must be at least 6 characters';
Trang 1}
// password length
if( strlen( $_POST['register_password'] ) < 6 )
{
$allClear = false;
$this->registrationErrors[] = 'Your password is too short, it
must be at least 6 characters';
$this->registrationErrorLabels['register_password_label'] =
'error';
$this->registrationErrorLabels['register_password_confirm_
label'] = 'error';
}
Next, we have the e-mail address—we need to check it for header injection, and
that the format of the e-mail address is correct The first highlighted section of code
shows the header injection check, and the second shows the format check.
// email headers
if( strpos( ( urldecode( $_POST[ 'register_email' ] ) ), "\r" )
=== true || strpos( ( urldecode( $_POST[ 'register_email' ]
) ), "\n" ) === true )
{
$allClear = false;
$this->registrationErrors[] = 'Your email address is not
valid (security)';
$this->registrationErrorLabels['register_email_label'] =
'error';
}
// email valid
if( ! preg_match(
z0-9-]+)*(\.[a-z]{2,4})^", $_POST[ 'register_email' ] ) )
{
$allClear = false;
$this->registrationErrors[] = 'You must enter a valid email
address';
$this->registrationErrorLabels['register_email_label'] =
'error';
}
To help protect us from a legal perspective, we should get legal advice on the policies
and terms and conditions we need to enforce on our social network When we have
such terms in place, we will want our users to accept these before allowing them to
join—let's ensure they ticked the appropriate box on our registration form template:
Trang 2// terms accepted
if( ! isset( $_POST['register_terms'] ) || $_POST['register_
terms'] != 1 )
{
$allClear = false;
$this->registrationErrors[] = 'You must accept our terms and
conditions.';
$this->registrationErrorLabels['register_terms_label'] =
'error';
}
If a user signs up with the e-mail address or username of an existing user, we will
have some problems—particularly when they come to log in, or request an e-mail to
reset their password To prevent this, we need to check that the username and e-mail
address are not currently in use by another user, which can be done with a simple
database query:
// duplicate user+email check
$u = $this->registry->getObject('db')->sanitizeData( $_
POST['register_user'] );
$e = $this->registry->getObject('db')->sanitizeData( $_
POST['register_email'] );
$sql = "SELECT * FROM users WHERE username='{$u}' OR
email='{$e}'";
$this->registry->getObject('db')->executeQuery( $sql );
if( $this->registry->getObject('db')->numRows() == 2 )
{
$allClear = false;
// both
$this->registrationErrors[] = 'Both your username and email
address are already in use on this site.';
$this->registrationErrorLabels['register_user_label'] =
'error';
$this->registrationErrorLabels['register_email_label'] =
'error';
}
elseif( $this->registry->getObject('db')->numRows() == 1 )
{
// possibly both, or just one
$u = $this->registry->getObject('db')->sanitizeData( $_
POST['register_user'] );
$e = $this->registry->getObject('db')->sanitizeData( $_
POST['register_email'] );
$data = $this->registry->getObject('db')->getRows();
if( $data['username'] == $u && $data['email'] == $e )
{
Trang 3$allClear = false;
$this->registrationErrors[] = 'Both your username and
password are already in use on this site.';
$this->registrationErrorLabels['register_user_label'] =
'error';
$this->registrationErrorLabels['register_email_label'] =
'error';
// both
}
elseif( $data['username'] == $u )
{
$allClear = false;
// username
$this->registrationErrors[] = 'Your username is already
in use on this site.';
$this->registrationErrorLabels['register_user_label'] =
'error';
}
else
{
$allClear = false;
// email address
$this->registrationErrors[] = 'Your email address is
already in use on this site.';
$this->registrationErrorLabels['register_email_label'] =
'error';
}
}
Finally, before we go onto profile fields, we check to see if we have enabled
CAPTCHA If we have, then we should do a check that the user is a human
and not an automated spam bot We will discuss CAPTCHA implementation
later in this chapter.
// captcha
if( $this->registry->getSetting('captcha.enabled') == 1 )
{
// captcha check
}
Trang 4Now that we have checked all of the core fields, we pass control to our registration
extension, which will process all of the profile related fields:
// hook
if( $this->registrationExtention->checkRegistrationSubmission()
== false )
{
$allClear = false;
}
If all is clear (that is, there were no errors either from this function, or the registration
controller extension), then we store our sanitized data, and return true—so that
another method can create the user account and profile:
if( $allClear == true )
{
$this->sanitizedValues['username'] = $u;
$this->sanitizedValues['email'] = $e;
$this->sanitizedValues['password_hash'] = md5( $_
POST['register_password'] );
$this->sanitizedValues['active'] = $this->activeValue;
$this->sanitizedValues['admin'] = 0;
$this->sanitizedValues['banned'] = 0;
$this->submittedValues['register_user'] = $_POST['register_
user'];
$this->submittedValues['register_password'] = $_
POST['register_password'];
return true;
}
else
{
$this->submittedValues['register_user'] = $_POST['register_
user'];
$this->submittedValues['register_email'] = $_POST['register_
email'];
$this->submittedValues['register_password'] = $_
POST['register_password'] ;
$this->submittedValues['register_password_confirm'] = $_
POST['register_password_confirm'] ;
$this->submittedValues['register_captcha'] = ( isset( $_
POST['register_captcha'] ) ?
$_POST['register_captcha'] : '' );
return false;
}
}
Trang 5Hooking additional fields on
Depending on the social network we were developing, we will have different
profile fields To make our code flexible, these fields are abstracted to a registration
extension, so if we reuse our code, we simply need to change this one file to process
these additional fields, and we know that it won't interfere with our core fields In
Dino Space land, citizens are only permitted to keep one Dinosaur, after all, who
could cope with looking after more than one! This would need to be tweaked slightly
if we wanted to extend our registration form to accept any number of a particular
set of fields There is some JavaScript available to help with such a situation
(
http://www.michaelpeacock.co.uk/blog/entry/add-another-item-with-php-and-jquery and
http://www.michaelpeacock.co.uk/blog/entry/add-another-the-jquery-plugin should get you started if you want to try it).
For Dino Space, there are going to be certain profile fields we want, and some
examples include:
• Dinosaur's name
• Dinosaur's breed
• Dinosaur's gender
• Dinosaur's date of birth
The registry extension works in a similar way to the core registration controller,
except the data validation is more dynamic, based on how the additional profile
fields are defined For example, to create the four additional profile fields from
above, we would define the following:
private $registry;
private $extraFields = array();
private $errors = array();
private $submittedValues = array();
private $sanitizedValues = array();
private $errorLabels = array();
public function construct( $registry )
{
$this->registry = $registry;
$this->extraFields['dino_name'] = array( 'friendlyname' =>
'Pet Dinosaurs Name', 'table' => 'profile', 'field' =>
'dino_name', 'type' => 'text', 'required' => false );
$this->extraFields['dino_breed'] = array( 'friendlyname' =>
'Pet Dinosaurs Breed', 'table' => 'profile', 'field' =>
'dino_breed', 'type' => 'text', 'required' => false );
$this->extraFields['dino_gender'] = array( 'friendlyname' =>
Trang 6'Pet Dnosaurs Gender', 'table' => 'profile', 'field' =>
'dino_gender', 'type' => 'list', 'required' => false,
'options' => array( 'male', 'female') );
$this->extraFields['dino_dob'] = array( 'friendlyname' => 'Pet
Dinosaurs Date of Birth', 'table' => 'profile', 'field' =>
'dino_dob', 'type' => 'DOB', 'required' => false );
}
Let's take a look at why we structure our extraFields like this To do this, we need
to look at how the extension validates the registration submission.
public function checkRegistrationSubmission()
{
We set a $valid variable (just like our allClear variable in the registration
controller) If there are errors, we set this to false.
$valid = true;
We now iterate through the fields to process them individually:
foreach( $this->extraFields as $field => $data )
{
Firstly, we check to see whether the field is required (from the required element of
the data array) If it is, we check that the user has submitted a value If they haven't,
we store the necessary errors.
if( ( ! isset( $_POST['register_' $field] ) ||
$_POST['register_' $field] == '' )
&& $data['required'] = true )
{
$this->submittedValues[ $field ] = $_POST['register_'
$field];
$this->errorLabels['register_' $field '_label'] =
'error';
$this->errors[] = 'Field ' $data['friendlyname'] '
cannot be blank';
$valid = false;
}
If the field isn't required, and hasn't been set, then we note that, and move on.
elseif( $_POST['register_' $field] == '' )
{
$this->submittedValues[ 'register_' $field ] = '';
}
else
{
Trang 7If our field is set, we then validate it depending on the type of data we are expecting
By default, there are three options:
• Text—text inputs
• Int—integers
• List—list of predefined options
However, it has been designed to allow other types to be plugged in, for instance,
dates The type also dictates how the data should be sanitized.
if( $data['type'] == 'text' )
{
$this->sanitizedValues[ 'register_' $field ] = $this-
>registry->getObject('db')->sanitizeData( $_
POST['register_' $field] );
$this->submittedValues['register_' $field] = $_
POST['register_' $field];
}
elseif( $data['type'] == 'int' )
{
$this->sanitizedValues[ 'register_' $field ] =
intval( $_POST['register_' $field] );
$this->submittedValues['register_' $field] = $_
POST['register_' $field];
}
elseif( $data['type'] == 'list' )
{
If the data type is a list, we simply check to see whether the value is in the array of
options, if it isn't we have an error, if it is—everything is OK.
if( ! in_array( $_POST['register_' $field],
$data['options'] ) )
{
$this->submittedValues[ $field ] = $_
POST['register_' $field];
$this->errorLabels['register_' $field '_label'] =
'error';
$this->errors[] = 'Field ' $data['friendlyname']
' was not valid';
$valid = false;
}
else
{
$this->sanitizedValues[ 'register_' $field ] =
intval( $_POST['register_' $field] );
$this->submittedValues['register_' $field] = $_
POST['register_' $field];
Trang 8}
}
else
{
Finally, for non-standard cases, we call a custom method, which we would create for
each such type:
$method = 'validate_' $data['type'];
if( $this->$method( $_POST['register_' $field] ) ==
true )
{
$this->sanitizedValues[ 'register_' $field ] =
$this->registry->getObject('db')->sanitizeData(
$_POST['register_' $field] );
$this->submittedValues['register_' $field] = $_
POST['register_' $field];
}
else
{
$this->sanitizedValues[ 'register_' $field ] =
$this->registry->getObject('db')->sanitizeData(
$_POST['register_' $field] );
$this->submittedValues['register_' $field] = $_
POST['register_' $field];
$this->errors[] = 'Field ' $data['friendlyname']
' was not valid';
$valid = false;
}
}
}
}
Once all the processing has been done, and sanitized data has been stored, we simply
return whether there were errors or not:
if( $valid == true )
{
return true;
}
else
{
return false;
}
}
Trang 9Processing the registration
Once we have processed all of the fields submitted and checked that they are all
valid, we are then ready to create our user account and our profile Creating the user
is a simple case of inserting the serialized values into the users table Then, we pass
control to the extension so that it can process the profile fields.
/**
* Process the users registration, and create the user and users
profiles
* @return int
*/
private function processRegistration()
{
// insert
$this->registry->getObject('db')->insertRecords( 'users',
$this->sanitizedValues );
// get ID
$uid = $this->registry->getObject('db')->lastInsertID();
// call extension to insert the profile
$this->registrationExtention->processRegistration( $uid );
// return the ID for the frameworks reference - autologin?
return $uid;
}
Creating the profile
Within our extra fields array, we noted the table and field that the submitted value
should be inserted into This method goes through the array, and groups the values
for each table, to ensure that only one insert is performed per additional table.
The advantage of this means if we added fields for another table (perhaps
subscription information for paid user accounts), we can do this without
needing to add more functionality to our extension.
/**
* Create our user profile
* @param int $uid the user ID
* @return bool
*/
public function processRegistration( $uid )
{
$tables = array();
$tableData = array();
Trang 10
// group our profile fields by table, so we only need to do one
insert per table
foreach( $this->extraFields as $field => $data )
{
if( ! ( in_array( $data['table'], $tables ) ) )
{
$tables[] = $data['table'];
$tableData[ $data['table'] ] = array( 'user_id' => $uid,
$data['field'] => $this->sanitizedValues[ 'register_'
$field ]);
}
else
{
$tableData[ $data['table'] ] = array( 'user_id' => $uid,
$data['field'] => $this->sanitizedValues[ 'register_'
$field ]);
}
}
foreach( $tableData as $table => $data )
{
$this->registry->getObject('db')->insertRecords( $table,
$data );
}
return true;
}
Putting it all together: registration constructor
So, we have gone through our registration controller, and our registration controller
extension to see how they process data to create our user account and user profile
We now just need to bring this all together in the constructor.
Firstly, we assign our registry object:
$this->registry = $registry;
Next, we include the extension file, and create the object:
require_once FRAMEWORK_PATH 'controllers/authenticate/
registrationcontrollerextention.php';
$this->registrationExtention = new
Registrationcontrollerextention( $this->registry );
We then check to see if the user has tried to submit the registration form:
if( isset( $_POST['process_registration'] ) )
{