First and foremost, a user needs to be able to create an account. This will give them access to the rest of the site's functionality.
As it stands, when a user visits our app, they're greeted with our "sales" page, which encourages them to click the "Sign Up" button in the top right of their screen:
The home screen of our appClicking that "Sign Up" button directs the user to /signup.php—our first order of business should probably be to build that page.
Creating the Sign-Up Form
In our app's root directory, create a file called signup.php and place the following code inside:
<?php
include_once "common/base.php";
$pageTitle = "Register";
include_once "common/header.php";
if(!empty($_POST['username'])):
include_once "inc/class.users.inc.php";
$users = new ColoredListsUsers($db);
echo $users->createAccount();
else:
?>
<h2>Sign up</h2>
<form method="post" action="signup.php" id="registerform">
<div>
<label for="username">Email:</label>
<input type="text" name="username" id="username" /><br />
<input type="submit" name="register" id="register" value="Sign up" />
</div>
</form>
<?php
endif;
include_once 'common/close.php';
?>
To start, we include our common/base.php and common/header.php files. Also, notice that we're declaring a variable called $pageTitle just before we include the header. Remember in Part 4 when we built the header file and left that comment in the title tag?
<title>Colored Lists | <!-- Do Something Smart Here --></title>
We're going to replace that with a snippet of PHP that reads:
<title>Colored Lists | <?php echo $pageTitle ?></title>
That gives us the opportunity to post a different title for each page of our app.
With the proper files included, we can then create our sign-up form. The form will submit to signup.php—itself—so we need to place an if-else check to see if the form has been submitted. If so, we create a new ColoredListsUsers object and call the createAccount() method (which we'll write in the next section).
Finally, we close the if-else statement and include the footer. Our sign-up page should look like this:
The sign-up page.Notice the use of alternative syntax for the if-else statement. Normally, I don't like to use this format, but in the case of outputting HTML, I prefer the way it ends with endif; instead of a closing curly brace (}), which helps with readability in the script.
Saving the User's Email Address
With our sign-up form ready, we need to write the createAccount() method that will be called when a user submits the form. This method will be public. Let's go back to inc/class.users.inc.php and declare this method:
class ColoredListsUsers
{
// Class properties and other methods omitted to save space
/**
* Checks and inserts a new account email into the database
*
* @return string a message indicating the action status
*/
public function createAccount()
{
$u = trim($_POST['username']);
$v = sha1(time());
$sql = "SELECT COUNT(Username) AS theCount
FROM users
WHERE Username=:email";
if($stmt = $this->_db->prepare($sql)) {
$stmt->bindParam(":email", $u, PDO::PARAM_STR);
$stmt->execute();
$row = $stmt->fetch();
if($row['theCount']!=0) {
return "<h2> Error </h2>"
. "<p> Sorry, that email is already in use. "
. "Please try again. </p>";
}
if(!$this->sendVerificationEmail($u, $v)) {
return "<h2> Error </h2>"
. "<p> There was an error sending your"
. " verification email. Please "
. "<a href=\"mailto:help@coloredlists.com\">contact "
. "us</a> for support. We apologize for the "
. "inconvenience. </p>";
}
$stmt->closeCursor();
}
$sql = "INSERT INTO users(Username, ver_code)
VALUES(:email, :ver)";
if($stmt = $this->_db->prepare($sql)) {
$stmt->bindParam(":email", $u, PDO::PARAM_STR);
$stmt->bindParam(":ver", $v, PDO::PARAM_STR);
$stmt->execute();
$stmt->closeCursor();
$userID = $this->_db->lastInsertId();
$url = dechex($userID);
/*
* If the UserID was successfully
* retrieved, create a default list.
*/
$sql = "INSERT INTO lists (UserID, ListURL)
VALUES ($userID, $url)";
if(!$this->_db->query($sql)) {
return "<h2> Error </h2>"
. "<p> Your account was created, but "
. "creating your first list failed. </p>";
} else {
return "<h2> Success! </h2>"
. "<p> Your account was successfully "
. "created with the username <strong>$u</strong>."
. " Check your email!";
}
} else {
return "<h2> Error </h2><p> Couldn't insert the "
. "user information into the database. </p>";
}
}
}
This method follows several steps to create an account: first, it retrieves the posted email address from the form (stored in the $_POST superglobal) and generates a hard-to-guess verification code (the SHA1 hash of the current timestamp); second, it makes sure the supplied email address isn't already in use; third, it generates and sends a verification email to the user with instructions on how to verify their account (we'll define the method that does this in the next section); fourth, it stores the email address and verification code in the database; and finally, it creates a list for the user.
Each of these steps is monitored, and if any of them should fail, a specific error message is generated. Upon success, a message is generated to let the user know they should expect an email.
Generating and Sending a Verification Email
When the user creates an account, we need to send them an email with a link that will confirm their account. This is a precautionary measure that proves the user provided a real email address that they have access to and prevents a ton of spam accounts from being created easily.
To send the email, we'll be using the built-in mail() function. In inc/class.users.inc.php, create the private sendVerificationEmail() method by inserting the following code:
class ColoredListsUsers
{
// Class properties and other methods omitted to save space
/**
* Sends an email to a user with a link to verify their new account
*
* @param string $email The user's email address
* @param string $ver The random verification code for the user
* @return boolean TRUE on successful send and FALSE on failure
*/
private function sendVerificationEmail($email, $ver)
{
$e = sha1($email); // For verification purposes
$to = trim($email);
$subject = "[Colored Lists] Please Verify Your Account";
$headers = <<<MESSAGE
From: Colored Lists <donotreply@coloredlists.com>
Content-Type: text/plain;
MESSAGE;
$msg = <<<EMAIL
You have a new account at Colored Lists!
To get started, please activate your account and choose a
password by following the link below.
Your Username: $email
Activate your account: http://coloredlists.com/accountverify.php?v=$ver&e=$e
If you have any questions, please contact help@coloredlists.com.
--
Thanks!
Chris and Jason
www.ColoredLists.com
EMAIL;
return mail($to, $subject, $msg, $headers);
}
}
The most important part of this method is the activation link, http://coloredlists.com/accountverify.php?v=$ver&e=$e. This link sends the user to our app's account verification file (which we'll write in the next step) and sends the user's hashed email address along with their verification code in the URI. This will allow us to identify and verify the user when they follow the link.
Verifying the User's Account
After our user follows the verification link in the email, we need to check that their email and verification code are valid, and then allow them to choose a password. After they choose a password, we need to update the database to reflect the user's new password, as well as setting the account's status to verified.
First, let's create a new file called accountverify.php in the root level of our app. Inside, place the following code:
<?php
include_once "common/base.php";
$pageTitle = "Verify Your Account";
include_once "common/header.php";
if(isset($_GET['v']) && isset($_GET['e']))
{
include_once "inc/class.users.inc.php";
$users = new ColoredListsUsers($db);
$ret = $users->verifyAccount();
}
elseif(isset($_POST['v']))
{
include_once "inc/class.users.inc.php";
$users = new ColoredListsUsers($db);
$ret = $users->updatePassword();
}
else
{
header("Location: /signup.php");
exit;
}
if(isset($ret[0])):
echo isset($ret[1]) ? $ret[1] : NULL;
if($ret[0]<3):
?>
<h2>Choose a Password</h2>
<form method="post" action="accountverify.php">
<div>
<label for="p">Choose a Password:</label>
<input type="password" name="p" id="p" /><br />
<label for="r">Re-Type Password:</label>
<input type="password" name="r" id="r" /><br />
<input type="hidden" name="v" value="<?php echo $_GET['v'] ?>" />
<input type="submit" name="verify" id="verify" value="Verify Your Account" />
</div>
</form>
<?php
endif;
else:
echo '<meta http-equiv="refresh" content="0;/">';
endif;
include_once("common/ads.php");
include_once 'common/close.php';
?>
Verifying the User's Email and Verification Code
Before we can allow our user to select a password, we need to make sure that their account exists, that their email matches their verification code, and that their account is unverified. To do that, we need a new method in inc/class.users.inc.php called verifyAccount():
class ColoredListsUsers
{
// Class properties and other methods omitted to save space
/**
* Checks credentials and verifies a user account
*
* @return array an array containing a status code and status message
*/
public function verifyAccount()
{
$sql = "SELECT Username
FROM users
WHERE ver_code=:ver
AND SHA1(Username)=:user
AND verified=0";
if($stmt = $this->_db->prepare($sql))
{
$stmt->bindParam(':ver', $_GET['v'], PDO::PARAM_STR);
$stmt->bindParam(':user', $_GET['e'], PDO::PARAM_STR);
$stmt->execute();
$row = $stmt->fetch();
if(isset($row['Username']))
{
// Logs the user in if verification is successful
$_SESSION['Username'] = $row['Username'];
$_SESSION['LoggedIn'] = 1;
}
else
{
return array(4, "<h2>Verification Error</h2>\n"
. "<p>This account has already been verified. "
. "Did you <a href=\"/password.php\">forget "
. "your password?</a>");
}
$stmt->closeCursor();
// No error message is required if verification is successful
return array(0, NULL);
}
else
{
return array(2, "<h2>Error</h2>\n<p>Database error.</p>");
}
}
}
This method executes a query that loads the user name stored in the database with the verification code, hashed user name, and a verified status of 0. If a user name is returned, login credentials are stored. This method returns an array with an error code in the first index, and a message in the second. The error code 0 means nothing went wrong.
Updating the User's Password and Verified Status
Once the user has selected a password and submitted the form, the if-else statement will catch the verification code sent using the POST method and execute the updatePassword() method. This method needs to set the account status to verified and save the user's hashed password in the database. Let's build this method in ColoredListsUsers:
class ColoredListsUsers
{
// Class properties and other methods omitted to save space
/**
* Changes the user's password
*
* @return boolean TRUE on success and FALSE on failure
*/
public function updatePassword()
{
if(isset($_POST['p'])
&& isset($_POST['r'])
&& $_POST['p']==$_POST['r'])
{
$sql = "UPDATE users
SET Password=MD5(:pass), verified=1
WHERE ver_code=:ver
LIMIT 1";
try
{
$stmt = $this->_db->prepare($sql);
$stmt->bindParam(":pass", $_POST['p'], PDO::PARAM_STR);
$stmt->bindParam(":ver", $_POST['v'], PDO::PARAM_STR);
$stmt->execute();
$stmt->closeCursor();
return TRUE;
}
catch(PDOException $e)
{
return FALSE;
}
}
else
{
return FALSE;
}
}
}
Finally, since verifying an account logs a user in, we need to update common/header.php to recognize that a user is logged in and display different options. In Part 4, common/header.php featured a code snippet that looked like this:
<!-- IF LOGGED IN -->
<p><a href="/logout.php" class="button">Log out</a> <a href="/account.php" class="button">Your Account</a></p>
<!-- IF LOGGED OUT -->
<p><a class="button" href="/signup.php">Sign up</a> <a class="button" href="/login.php">Log in</a></p>
<!-- END OF IF STATEMENT -->
To make those comments into functional code, we need to modify this snippet with an if-else block:
<?php
if(isset($_SESSION['LoggedIn']) && isset($_SESSION['Username'])
&& $_SESSION['LoggedIn']==1):
?>
<p><a href="/logout.php" class="button">Log out</a> <a href="/account.php" class="button">Your Account</a></p>
<?php else: ?>
<p><a class="button" href="/signup.php">Sign up</a> <a class="button" href="/login.php">Log in</a></p>
<?php endif; ?>
Notice that we store in the session both the user name ($_SESSION['Username']) and a flag that tells us if the user is logged in ($_SESSION['LoggedIn']).
Logging In
Next, let's build the login form and allow our user to log in. To start, let's create a new file named login.php at the root level of our app. Like our other publicly displayed files, this will include the base and header files. Then it checks if a user is already logged in, if the login form was submitted, or if the user needs to log in.
If logged in, the user is notified of this fact and asked if he or she wishes to log out.
If the form has been submitted, a new ColoredListsUsers object is created and the accountLogin() method is called. If the login succeeds, the user is directed to the home page, where his or her list will appear; otherwise, the login form is displayed again with an error.
If neither of the previous conditions exists, the login form is displayed.
Finally, the sidebar ads and footer are included to round out the file.
When the file is all put together, it should look like this:
<?php
include_once "common/base.php";
$pageTitle = "Home";
include_once "common/header.php";
if(!empty($_SESSION['LoggedIn']) && !empty($_SESSION['Username'])):
?>
<p>You are currently <strong>logged in.</strong></p>
<p><a href="/logout.php">Log out</a></p>
<?php
elseif(!empty($_POST['username']) && !empty($_POST['password'])):
include_once 'inc/class.users.inc.php';
$users = new ColoredListsUsers($db);
if($users->accountLogin()===TRUE):
echo "<meta http-equiv='refresh' content='0;/'>";
exit;
else:
?>
<h2>Login Failed—Try Again?</h2>
<form method="post" action="login.php" name="loginform" id="loginform">
<div>
<input type="text" name="username" id="username" />
<label for="username">Email</label>
<br /><br />
<input type="password" name="password" id="password" />
<label for="password">Password</label>
<br /><br />
<input type="submit" name="login" id="login" value="Login" class="button" />
</div>
</form>
<p><a href="/password.php">Did you forget your password?</a></p>
<?php
endif;
else:
?>
<h2>Your list awaits...</h2>
<form method="post" action="login.php" name="loginform" id="loginform">
<div>
<input type="text" name="username" id="username" />
<label for="username">Email</label>
<br /><br />
<input type="password" name="password" id="password" />
<label for="password">Password</label>
<br /><br />
<input type="submit" name="login" id="login" value="Login" class="button" />
</div>
</form><br /><br />
<p><a href="/password.php">Did you forget your password?</a></p>
<?php
endif;
?>
<div style="clear: both;"></div>
<?php
include_once "common/ads.php";
include_once "common/close.php";
?>
Notice the "Did you forget your password?" links — we'll be building this functionality a little later on in the article.
Building the Login Method
Now we need to build the accountLogin() method. This method will compare the supplied user name and the MD5 hash of the supplied password to verify that there is a matching pair in the database. If a match is found, the user's name and a login flag are stored in the session and the method returns TRUE. If no match is found, the method returns FALSE.
Build this method in ColoredListsUsers by inserting the following code:
class ColoredListsUsers
{
// Class properties and other methods omitted to save space
/**
* Checks credentials and logs in the user
*
* @return boolean TRUE on success and FALSE on failure
*/
public function accountLogin()
{
$sql = "SELECT Username
FROM users
WHERE Username=:user
AND Password=MD5(:pass)
LIMIT 1";
try
{
$stmt = $this->_db->prepare($sql);
$stmt->bindParam(':user', $_POST['username'], PDO::PARAM_STR);
$stmt->bindParam(':pass', $_POST['password'], PDO::PARAM_STR);
$stmt->execute();
if($stmt->rowCount()==1)
{
$_SESSION['Username'] = htmlentities($_POST['username'], ENT_QUOTES);
$_SESSION['LoggedIn'] = 1;
return TRUE;
}
else
{
return FALSE;
}
}
catch(PDOException $e)
{
return FALSE;
}
}
}
Logging Out
Next, our user needs to be able to log out. This is as easy as destroying the login data stored in the session and sending the user back to the login page.
Create a new file named logout.php at the root level of the app and place the following code inside:
<?php
session_start();
unset($_SESSION['LoggedIn']);
unset($_SESSION['Username']);
?>
<meta http-equiv="refresh" content="0;login.php">
Modifying Account Information
Next, we need to allow our users to modify their account information. In order to do that, we need to provide an "Account" page that will give them options to change their user name or password, as well as the option to delete their account.
Create a file named account.php at the root level of the app. There's a lot going on here because we're essentially combining three app functions within one file.
First, we include the base file and check that the user is logged in. If not, he or she gets sent out to the main page.
If the user is logged in, we check if any actions have already been attempted and assemble the corresponding success or failure messages if any are found.
Then we load the user's ID and verification code using the method retrieveAccountInfo() and build three forms: one to update the user name (which is an email address, remember), one to change the account password, and one to delete the account.
Finally, we include the sidebar ads and the footer. Altogether, the file should look like this:
<?php
include_once "common/base.php";
if(isset($_SESSION['LoggedIn']) && $_SESSION['LoggedIn']==1):
$pageTitle = "Your Account";
include_once "common/header.php";
include_once 'inc/class.users.inc.php';
$users = new ColoredListsUsers($db);
if(isset($_GET['email']) && $_GET['email']=="changed")
{
echo "<div class='message good'>Your email address "
. "has been changed.</div>";
}
else if(isset($_GET['email']) && $_GET['email']=="failed")
{
echo "<div class='message bad'>There was an error "
. "changing your email address.</div>";
}
if(isset($_GET['password']) && $_GET['password']=="changed")
{
echo "<div class='message good'>Your password "
. "has been changed.</div>";
}
elseif(isset($_GET['password']) && $_GET['password']=="nomatch")
{
echo "<div class='message bad'>The two passwords "
. "did not match. Try again!</div>";
}
if(isset($_GET['delete']) && $_GET['delete']=="failed")
{
echo "<div class='message bad'>There was an error "
. "deleting your account.</div>";
}
list($userID, $v) = $users->retrieveAccountInfo();
?>
<h2>Your Account</h2>
<form method="post" action="db-interaction/users.php">
<div>
<input type="hidden" name="userid"
value="<?php echo $userID ?>" />
<input type="hidden" name="action"
value="changeemail" />
<input type="text" name="username" id="username" />
<label for="username">Change Email Address</label>
<br /><br />
<input type="submit" name="change-email-submit"
id="change-email-submit" value="Change Email"
class="button" />
</div>
</form><br /><br />
<form method="post" action="db-interaction/users.php"
id="change-password-form">
<div>
<input type="hidden" name="user-id"
value="<?php echo $userID ?>" />
<input type="hidden" name="v"
value="<?php echo $v ?>" />
<input type="hidden" name="action"
value="changepassword" />
<input type="password"
name="p" id="new-password" />
<label for="password">New Password</label>
<br /><br />
<input type="password" name="r"
id="repeat-new-password" />
<label for="password">Repeat New Password</label>
<br /><br />
<input type="submit" name="change-password-submit"
id="change-password-submit" value="Change Password"
class="button" />
</div>
</form>
<hr />
<form method="post" action="deleteaccount.php"
id="delete-account-form">
<div>
<input type="hidden" name="user-id"
value="<?php echo $userID ?>" />
<input type="submit"
name="delete-account-submit" id="delete-account-submit"
value="Delete Account?" class="button" />
</div>
</form>
<?php
else:
header("Location: /");
exit;
endif;
?>
<div class="clear"></div>
<?php
include_once "common/ads.php";
include_once "common/close.php";
?>
Creating the Method to Retrieve Account Info
In order to have the user's login name and verification code available to our account option forms, we need to build a new method that will load this information from the database. In inc/class.users.inc.php, create a new method in ColoredListsUsers called retrieveAccountInfo() and add the following code:
class ColoredListsUsers
{
// Class properties and other methods omitted to save space
/**
* Retrieves the ID and verification code for a user
*
* @return mixed an array of info or FALSE on failure
*/
public function retrieveAccountInfo()
{
$sql = "SELECT UserID, ver_code
FROM users
WHERE Username=:user";
try
{
$stmt = $this->_db->prepare($sql);
$stmt->bindParam(':user', $_SESSION['Username'], PDO::PARAM_STR);
$stmt->execute();
$row = $stmt->fetch();
$stmt->closeCursor();
return array($row['UserID'], $row['ver_code']);
}
catch(PDOException $e)
{
return FALSE;
}
}
}





Comments