Creating an App from Scratch: Part 5

Creating a Web App from Scratch Part 5

Where Are We?

Now that we have a workflow put together and the HTML and CSS to make it look good, we can actually start building the classes that will run this puppy.

We’ll focus this installment of the series on the user’s account interactions. These include:

  • Creating an Account
  • Modifying Account Information
  • Resetting a Lost Password
  • Deleting an Account

Connecting to the Database

Before our class will be able to do much of anything, we need to connect to our database. To do this, we’ll need to create a couple of small files.

Defining Site-Wide Constants

Our site won’t require many constants, but in the interest of keeping them easy to maintain, we’ll create a separate file to contain any information that is site-wide. This will be called constants.inc.php, and it will reside in a new folder called inc — this folder will contain our PHP classes as well.

Creating a constants file is a good idea for pieces of information that will be used often and in different scopes throughout a site. That way, if your database changes, you’re able to change every database connection simply by swapping out the information in one file.

Inside constants.inc.php, we want to define our database credentials. Since we’re starting out by developing locally, constants.inc.php will look like this:

<?php

    // Database credentials
    define('DB_HOST', 'localhost');
    define('DB_USER', 'root');
    define('DB_PASS', '');
    define('DB_NAME', 'cl_db');

?>

As we develop, we’ll add more to this file.

Creating a PDO Object

Next, we want to create a connection so that our application can communicate with our database. This file will reside in the common folder along with the header, footer, and sidebar files. This file will create a database connection using PDO (PHP Data Objects), as well as setting up a couple other site-wide features: error reporting and opening a session.

The file will look like this when all’s said and done:

<?php
    // Set the error reporting level
    error_reporting(E_ALL);
    ini_set("display_errors", 1);

    // Start a PHP session
    session_start();

    // Include site constants
    include_once "inc/constants.inc.php";

    // Create a database object
    try {
        $dsn = "mysql:host=".DB_HOST.";dbname=".DB_NAME;
        $db = new PDO($dsn, DB_USER, DB_PASS);
    } catch (PDOException $e) {
        echo 'Connection failed: ' . $e->getMessage();
        exit;
    }
?>

Because we’re in the development stage, we want to see any and every error that occurs on the site. By setting error_reporting() to E_ALL and changing the display_errors directive to 1 using ini_set(), we ensure that even notices will be displayed, which will keep our code cleaner and more secure.

Next, we use session_start() to start a PHP session. This will allow our users to stay logged in when we build that functionality later.

Finally, we include config.inc.php and create a PDO object using the constants defined within it. Note the use of the try-catch statement—this gives us the ability to use Exceptions, which help improve error handling. In this case, if the database connection fails, we’re simply going to output the error message.

Why PDO?

The reason we’re using PDO for this project is because of its support for prepared statements, which virtually eliminates the risk of SQL injection. There are other options that allow prepared statements, such as the MySQLi extension. However, PDO is not database-specific, so migrating the app to Oracle or PostgreSQL wouldn’t require a full rewrite of our code.

Also, having used both MySQLi and PDO in projects, it’s just my personal preference to use PDO. Feel free to use whatever method of connecting to the database you prefer, but keep in mind that all database interactions in this exercise are assuming the use of PDO, and as such will probably require some reworking to accommodate your changes.

Framing Out a User Interactions Class

As we discussed in Part 2 of this series, we’ll be taking the object-oriented approach with this app. All of these actions will be contained within our ColoredListsUsers class. We’ll also need to create several files that will display information to the user and interact with the class, which we’ll cover as we get to them.

Building the Class

To get started, we need to create the file class.users.inc.php to contain the PHP class, which we’ll place in the inc folder.

With the file created, let’s build the skeleton of the class:

<?php

/**
 * Handles user interactions within the app
 *
 * PHP version 5
 *
 * @author Jason Lengstorf
 * @author Chris Coyier
 * @copyright 2009 Chris Coyier and Jason Lengstorf
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 *
 */
class ColoredListsUsers
{



}


?>

Connecting the Class to the Database

Before our class can do much of anything, it needs to have access to the database object we created in base.php. Our database connection within the object will be stored in a private property called $_db, and this property will be set by the class constructor, which will accept the instance of PDO created in base.php as an argument. If no instance of PDO is passed, one will be created by the constructor.

This ends up looking like this:

class ColoredListsUsers
{
    /**
     * The database object
     *
     * @var object
     */
    private $_db;

    /**
     * Checks for a database object and creates one if none is found
     *
     * @param object $db
     * @return void
     */
    public function __construct($db=NULL)
    {
        if(is_object($db))
        {
            $this->_db = $db;
        }
        else
        {
            $dsn = "mysql:host=".DB_HOST.";dbname=".DB_NAME;
            $this->_db = new PDO($dsn, DB_USER, DB_PASS);
        }
    }
}

Now we are able to create an instance of our ColoredListsUsers object and use it to communicate with our database. Next, let’s start building user interactions!

Creating an Account

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:

App home screen The home screen of our app

Clicking 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 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> &nbsp; <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> &nbsp; <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&mdash;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;
        }
    }
}

Building the Interactions File

In account.php, all three forms direct to a file called db-interaction/users.php when submitted. This file helps relieve some of the clutter in account.php by determining form actions, creating a ColoredListsUsers object, and calling the appropriate methods to handle the action.

This file will be placed in a new folder called db-interaction, and it will be named users.php. Place the following code in the new file:

<?php

session_start();

include_once "../inc/constants.inc.php";
include_once "../inc/class.users.inc.php";
$userObj = new ColoredListsUsers();

if(!empty($_POST['action'])
&& isset($_SESSION['LoggedIn'])
&& $_SESSION['LoggedIn']==1)
{
    switch($_POST['action'])
    {
        case 'changeemail':
            $status = $userObj->updateEmail() ? "changed" : "failed";
            header("Location: /account.php?email=$status");
            break;
        case 'changepassword':
            $status = $userObj->updatePassword() ? "changed" : "nomatch";
            header("Location: /account.php?password=$status");
            break;
        case 'deleteaccount':
            $userObj->deleteAccount();
            break;
        default:
            header("Location: /");
            break;
    }
}
elseif($_POST['action']=="resetpassword")
{
    if($resp=$userObj->resetPassword()===TRUE)
    {
        header("Location: /resetpending.php");
    }
    else
    {
        echo $resp;
    }
    exit;
}
else
{
    header("Location: /");
    exit;
}

?>

Updating the Email Address

When a user submits a request to change their email address, the method updateEmail() is called. This function simply executes a query that changes the email address associated with an account. It returns TRUE if the email is successfully changed, and FALSE otherwise.

class ColoredListsUsers
{
    // Class properties and other methods omitted to save space


    /**
     * Changes a user's email address
     *
     * @return boolean    TRUE on success and FALSE on failure
     */
    public function updateEmail()
    {
        $sql = "UPDATE users
                SET Username=:email
                WHERE UserID=:user
                LIMIT 1";
        try
        {
            $stmt = $this->_db->prepare($sql);
            $stmt->bindParam(':email', $_POST['username'], PDO::PARAM_STR);
            $stmt->bindParam(':user', $_POST['userid'], PDO::PARAM_INT);
            $stmt->execute();
            $stmt->closeCursor();

            // Updates the session variable
            $_SESSION['Username'] = htmlentities($_POST['username'], ENT_QUOTES);

            return TRUE;
        }
        catch(PDOException $e)
        {
            return FALSE;
        }
    }
}

Updating the Password

Quite similarly to updateEmail(), updatePassword() is called if the user submits a request to change their password. The only difference in the methods is that this one will compare the password and the password confirmation to make sure they match before saving.

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;
        }
    }
}

Deleting the Account

If the user wants to delete their account, we need to go through several steps. First, we need to double-check that the user is logged in, because we certainly don’t want any accidental account deletions. If the user is logged in, we then delete their list items. If the list items are successfully deleted, we move on to delete the user’s lists. Finally, if the lists are successfully deleted, we delete the user from the database, destroy their session information, and send them to a page called gone.php, which we’ll build in a minute.

The method, when it’s all written, will look like this:

class ColoredListsUsers
{
    // Class properties and other methods omitted to save space


    /**
     * Deletes an account and all associated lists and items
     *
     * @return void
     */
    public function deleteAccount()
    {
        if(isset($_SESSION['LoggedIn']) && $_SESSION['LoggedIn']==1)
        {
            // Delete list items
            $sql = "DELETE FROM list_items
                    WHERE ListID=(
                        SELECT ListID
                        FROM lists
                        WHERE UserID=:user
                        LIMIT 1
                    )";
            try
            {
                $stmt = $this->_db->prepare($sql);
                $stmt->bindParam(":user", $_POST['user-id'], PDO::PARAM_INT);
                $stmt->execute();
                $stmt->closeCursor();
            }
            catch(PDOException $e)
            {
                die($e->getMessage());
            }

            // Delete the user's list(s)
            $sql = "DELETE FROM lists
                    WHERE UserID=:user";
            try
            {
                $stmt = $this->_db->prepare($sql);
                $stmt->bindParam(":user", $_POST['user-id'], PDO::PARAM_INT);
                $stmt->execute();
                $stmt->closeCursor();
            }
            catch(PDOException $e)
            {
                die($e->getMessage());
            }

            // Delete the user
            $sql = "DELETE FROM users
                    WHERE UserID=:user
                    AND Username=:email";
            try
            {
                $stmt = $this->_db->prepare($sql);
                $stmt->bindParam(":user", $_POST['user-id'], PDO::PARAM_INT);
                $stmt->bindParam(":email", $_SESSION['Username'], PDO::PARAM_STR);
                $stmt->execute();
                $stmt->closeCursor();
            }
            catch(PDOException $e)
            {
                die($e->getMessage());
            }

            // Destroy the user's session and send to a confirmation page
            unset($_SESSION['LoggedIn'], $_SESSION['Username']);
            header("Location: /gone.php");
            exit;
        }
        else
        {
            header("Location: /account.php?delete=failed");
            exit;
        }
    }
}

Resetting an Account Password

At this point, we’re almost done. The last thing we need to do is allow a user to reset a forgotten password. To do this, we need to create the file password.php at the root level of our app and place the following code inside:

<?php
    include_once "common/base.php";
    $pageTitle = "Reset Your Password";
    include_once "common/header.php";
?>

        <h2>Reset Your Password</h2>
        <p>Enter the email address you signed up with and we'll send
        you a link to reset your password.</p>

        <form action="db-interaction/users.php" method="post">
            <div>
                <input type="hidden" name="action"
                    value="resetpassword" />
                <input type="text" name="username" id="username" />
                <label for="username">Email</label><br /><br />
                <input type="submit" name="reset" id="reset"
                    value="Reset Password" class="button" />
            </div>
        </form>
<?php
    include_once "common/ads.php";
    include_once "common/close.php";
?>

When a user visits this page, they’ll be able to enter their email address. Submitting the form will return the account to unverified and send the user an email with a link to reset their password.

Returning the Account to “Unverified” Status

When the form in password.php is submitted, the information is sent to db-interaction/users.php and the resetPassword() method is called before sending the user to resetpending.php.

The resetPassword() method sets the verified field of our user’s database entry to 0, then calls the sendResetEmail() method.

class ColoredListsUsers
{
    // Class properties and other methods omitted to save space


    /**
     * Resets a user's status to unverified and sends them an email
     *
     * @return mixed    TRUE on success and a message on failure
     */
    public function resetPassword()
    {
        $sql = "UPDATE users
                SET verified=0
                WHERE Username=:user
                LIMIT 1";
        try
        {
            $stmt = $this->_db->prepare($sql);
            $stmt->bindParam(":user", $_POST['username'], PDO::PARAM_STR);
            $stmt->execute();
            $stmt->closeCursor();
        }
        catch(PDOException $e)
        {
            return $e->getMessage();
        }

        // Send the reset email
        if(!$this->sendResetEmail($_POST['username'], $v))
        {
            return "Sending the email failed!";
        }
        return TRUE;
    }
}

Building the Reset Pending Page

After the user’s account is back in an unverified state and the email has been sent with their password reset link, we send them to resetpending.php to let them know what their next steps are. Create this file at the root level of the app and insert the following:

<?php
    include_once "common/base.php";
    $pageTitle = "Reset Pending";
    include_once "common/header.php";
?>

        <h2>Password Reset Requested</h2>
        <p>Check your email to finish the reset process.</p>
<?php
    include_once "common/ads.php";
    include_once "common/close.php";
?>

Generating a “Reset Password” Email

The sendResetEmail() method is very similar to the sendVerificationEmail() method. The main difference here is that the link sent to the user directs them to a page called resetpassword.php where they’re able to choose a new password.

class ColoredListsUsers
{
    // Class properties and other methods omitted to save space


    /**
     * Sends a link to a user that lets them reset their password
     *
     * @param string $email    the user's email address
     * @param string $ver    the user's verification code
     * @return boolean        TRUE on success and FALSE on failure
     */
    private function sendResetEmail($email, $ver)
    {
        $e = sha1($email); // For verification purposes
        $to = trim($email);

        $subject = "[Colored Lists] Request to Reset Your Password";

        $headers = <<<MESSAGE
From: Colored Lists <donotreply@coloredlists.com>
Content-Type: text/plain;
MESSAGE;

        $msg = <<<EMAIL
We just heard you forgot your password! Bummer! To get going again,
head over to the link below and choose a new password.

Follow this link to reset your password:
http://coloredlists.com/resetpassword.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);
    }
}

Resetting the Password

Our very last step in this part of the app is to create the file resetpassword.php in the root level of the site. This file is very similar to the accountverify.php file we created earlier. After including the base and header files, it checks if the user is just arriving from their reset email.

If so, we are able to use the verifyAccount() method we wrote earlier to ensure that their credentials are correct. After verifying their credentials, we display a form that allows them to choose a password and confirm it.

After submitting the form, our script will fire the updatePassword() method we created earlier to save the new password. Then we redirect the user to account.php, where they’re shown a confirmation message letting them know that their password was changed.

Inside resetpassword.php, add the following code:

<?php
    include_once "common/base.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);
        $status = $users->updatePassword() ? "changed" : "failed";
        header("Location: /account.php?password=$status");
        exit;
    }
    else
    {
        header("Location: /login.php");
        exit;
    }

    $pageTitle = "Reset Your Password";
    include_once "common/header.php";

    if(isset($ret[0])):
        echo isset($ret[1]) ? $ret[1] : NULL;

        if($ret[0]<3):
?>

        <h2>Reset Your Password</h2>

        <form method="post" action="accountverify.php">
            <div>
                <label for="p">Choose a New 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="Reset Your Password" />
            </div>
        </form>

<?php
        endif;
    else:
        echo '<meta http-equiv="refresh" content="0;/">';
    endif;

    include_once("common/ads.php");
    include_once 'common/close.php';
?>

Moving On…

This article covered a whole lot of ground, and I went over some of it pretty quickly. Please don’t hesitate to ask for clarification in the comments!

In the next part of this series, our front-end designer will use some dummy lists to create AJAX effects. After he’s finished with the dummy lists, we’ll explore how to combine those AJAX effects with our back-end and build the list interactions class in part 7.

Series Authors

Jason Lengstorf is a software developer based in Missoula, MT. He is the author of PHP for Absolute Beginners and regularly blogs about programming. When not glued to his keyboard, he’s likely standing in line for coffee, brewing his own beer, or daydreaming about being a Mythbuster.
Chris Coyier is a designer currently living in Chicago, IL. He is the co-author of Digging Into WordPress, as well as blogger and speaker on all things design. Away from the computer, he is likely to be found yelling at Football coaches on TV or picking a banjo.