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.

About Jason Lengstorf

Jason Lengstorf lives in Portland, OR. He loves design, code, business, and marketing. He also writes and speaks about design, code, business, and marketing. View all posts by Jason Lengstorf →
  • http://ivorpadilla.net iPad

    Absolutely amazing tutorial, is so easy to understand :) Thanks a lot Jason. 3 more steps to go :P

  • http://scvinodkumar.wordpress.com scvinodkumar

    Wow very helpful tutorial on OOP based PHP with PDO.

  • Rafael

    I really like that you're working locally because that makes it easier to follow you guys.

  • http://www.sweettemplates.com John

    One of the best series I've seen thus far! You guys rock! Can I ask how long it took you to write up all that code?

  • http://www.ennuidesign.com Jason Lengstorf

    Thanks everyone!

    @John:

    It's hard to say how long it actually took. Chris and I both worked on this in the hours we had between other projects. We worked on it for a few months, and did a few rounds of tweaks after a limited beta release.

    So, it was a while. :)

  • Andrew

    Code blocks are quite a strain on the eyes

  • Gringo

    HI Jason,

    I'm blown away by these series. The completeness is incredible.

    Just a small thing I've noticed and don't quite get in “Modifying Account Information” account.php:

    if(isset($_GET['password']) && $_GET['password']==”changed”)

    {

    echo “Your password ”

    . “has been changed.”;

    }

    elseif(isset($_GET['password']) && $_GET['password']==”changed”)

    {

    echo “The two passwords ”

    . “did not match. Try again!”;

    }

    I'm not sure I understand that elseif statement. Isn't that the same as the if-statement ?

    I presume you want to somehow check the “r”-confirmpassword but it's not happening (not 100% sure tho)

    But then again, I don't know that much about this… It could be just fine the way it is, 's just that its looks a little weird to me.

    Gretos,

    Gringo

  • http://www.ennuidesign.com Jason Lengstorf

    @Gringo:

    Good catch! That elseif block should have read:

    elseif(isset($_GET['password']) && $_GET['password']==”nomatch”)

    I've corrected the code above. Thanks!

  • WebSurfer

    Really great series! Thanks!

    Just a question: why the method resetPassword() does not check if the e-mail address is in the database before resetting “verified”?

  • WebSurfer

    I've noticed that in the file “accountverify.php”, the type of the variable $ret is mixed because it can be set up to an array ($ret = $users->verifyAccount();) or to a boolean value ($ret = $users->updatePassword();) … but is managed only like an array!

    If the user verify his account by submitting the form, the code “echo isset($ret[1]) ? $ret[1] : NULL;” print NULL, because in this case $ret = = TRUE, so the user doesn't see any message in the web page!

    Or I'm doing something wrong?

    Thanks!

  • WebSurfer

    I'm sorry! I had not thought that in this case the user is logged in and the page displays the colored list!

    Thanks again!

  • WebSurfer

    Hello Jason,

    I think that there are two small bugs in the files account.php and login.php, caused by the include_once('common/header.php'); placed on top of the file itsel.

    In the file account.php, I think that include_once('__common/header.php'); should be placed after the first if..else; otherwise the execution of header(“Location: signup.php”); generate and error because of the header has been previously modified by include_once('common/header.php');

    Instead, in the file login.php, I think that include_once('__common/header.php'); should be placed just before each HTML section, otherwise echo ''; print the meta refresh in the HTML body and the user is not redirected to the home page after the login!

  • http://www.ennuidesign.com Jason Lengstorf

    @WebSurfer:

    You're absolutely right. Thanks for pointing that out! I'll get it corrected in the source code.

    Regarding your question on resetPassword(), because it uses the email address as the way to match the row, there's no way the account could be reset unless the email address exists in the table. Make sense?

  • WebSurfer

    Hello Jason!

    Nothing at all! You have shared with us a great piece of code and I'm happy to help you debug it!!!

    About the resetPassword() you have right! But, if an unregistered user try to use the “reset password form” by clicking on Did you forget your password?, the app sends a reset e-mail to the user even if his account actually doesn't exist!

    About this method, I've noticed that the variable $v is equal to NULL so the user receives and e-mail with a reset URL that looks like this:

    h**p://coloredLists/resetpassword.php?v=&e=fc43d84c6ee3e20a87b4fea950bb5e794d1e228a

    so, we he click on it, the verify method verifyAccount() fails every time and the user can't reset the password!

    Just an other thing … in the file account.php the form used to delete the account posts the HTTP-Request to a page that doesn't exists, I think that it should posts the request to db-interaction/users.php and it should contain the following hidden input field:

    ~input type=”hidden” name=”action” value=”deleteaccount” /~

    Regards,

    WebSurfer

  • WebSurfer

    [from the previous comment] … the app sends a reset e-mail to the user even if his account actually doesn't exist! Because the query UPDATE users SET verified=0 WHERE Username=:user LIMIT 1; doesn't fails with an error if there are not matched rows!!!

    Just the last question, but the file gone.php what contains?

    Thanks!

    Regards,

    WebSurfer

  • Chad Hietala

    Any clue why I would get a “Connection failed: could not find driver” error when looking at the signup page? I'm pretty positive I have the PDO_Mysql extension installed.

  • http://montana@complimedia.com Montana Flynn

    Wow, thanks for this wonderful series along with Chris! I appreciate your hard work very much and you have made learning PHP wonderfully intuitive and very easy to understand.

    Could I offer some advice as a UI/UX consultant, it would be wonderful and amazing if you could have your code block expand on mouseover or onclick. This could be as easy as adding this to your CSS file using CSS3:

    Add to the pre selector:

    -webkit-transition: width 1s ease-in;

    -moz-transition: width 1s ease-in;

    transition: width 1s ease-in;

    And create a pre:hover with your desired width.

    pre:hover {width:960px;}

  • http://www.ennuidesign.com Jason Lengstorf

    @WebSurfer:

    Well, shit. It turns out I omitted the whole part of the app dealing with deleting accounts. The file deleteaccount.php does exist, and serves to confirm that the account needs to be deleted.

    Also, gone.php is just a page that says, “Account has been DELETED.”

    These will be available in the source (which should be posted soon).

    You're right about the resetPassword() stuff, too. I guess I never considered that someone would try to reset a password for an account that didn't exist. I'll put it on my list of fixes.

    Thanks again for your debugging help!

    @Chad Hietala:

    If you're positive that PDO and MySQL are active, I'm not really sure what's going on. Can you paste a code snippet?

  • WebSurfer

    Thanks to you Jason! ;)

  • http://www.ennuidesign.com Jason Lengstorf

    @Montana Flynn:

    Good point! I've added the extra CSS. You might have to hard refresh to get the new CSS file.

    Thanks!

  • http://montana@complimedia.com Montana Flynn

    @Jason

    Thanks for the awesomely fast response! I love the blogging community spirit. Thanks for adding that. Of course if you were inclined you could add some javascript to keep the block from expanding if unneeded and add a little delay to polish it up. Maybe I will try my luck at writing a code-expanding jQuery plugin for my blog. Anyways thanks for this series, and your quick response!

  • Chad Hietala

    @Jason

    I'm trying to run this through MAMP and I just looked at my PHP Info file and I don't have PDO running for mySQL.

    I'm searching al over the place and I can't seem to find how I enable it for mySQL in MAMP. The only thing I found was adding the extension via php.ini.

    A pdo_mysql.so file is in the extensions folder but like I said its still not enabled.

  • WebSurfer

    Hello Jason,

    I would like to pose you just to questions:

    Is it really needed the use of the session variable $_SESSION['LoggedIn']? Because, to check if the user is logged in, could be checked just the variable $_SESSION['username'] … or have I forgot something?

    In the logout.php file, is it a good practice to destroy the session after unsetting the session variables?

    Thanks!

    WebSurfer

  • Chad Hietala

    @jason

    Nevermind I got it figured out.

  • http://www.ennuidesign.com Jason Lengstorf

    @WebSurfer:

    You could just use the $_SESSION['username'] variable to verify that the user is logged in, but I've always had a loggedIn setting as well. In some apps, you can use it to determine a clearance level or whatever you want. I think it's just a personal preference, really.

    As far as destroying the session, it definitely doesn't hurt. I'm not a security expert, so I don't know if there are risks involved with simply unsetting the session variables you no longer need. Anyone who knows more about this, I'd love to hear from you!

  • WebSurfer

    Thanks for your quick reply, Jason!

    Regards,

    WebSurfer

  • Glenn

    Hi,

    First of all, thanks for this great tutorial! But I have a little problem, I can't logg-in into my website with the login.php! He gives no errors but only say that there was something wrong with my password! Tried it several times with other accounts, but still the same…

    Does somebody else have this problem also?

    Thx

  • http://www.ennuidesign.com Jason Lengstorf

    @Glenn:

    Try debugging by outputting the values as they come into the accountLogin() method, and see what's going on in your database. It's probably some little thing you won't notice until you start working backward. Check the data at every point where it changes until you find the place it's breaking. Then it's pretty easy to spot and fix the bug.

    Good luck!

  • Albioner

    Hi. Great tutorial. in Creating a PDO Object of Part 5 (if I'm reading it correctly), you don't tell us the actual file name being created (“Base.php”). Only later, from a back reference, do I learn what it was supposed to be. Thanks for sharing this project.

  • http://www.ennuidesign.com Jason Lengstorf

    @Albioner:

    You're right, I definitely missed that. Thanks for pointing it out!

  • marko

    Thank you! Thank you! Thank you!

    This article is the best I've come across on building a user authentication system. So complete, easy-to-follow and well done. I really needed that! Thanks again!

    Quick question:

    Why do you guys use both a “common” folder and an “inc” folder? Why not just one folder for all those types of files?

  • http://montana@complimedia.com Montana Flynn

    @marko my understanding is that the /common/ directory is for files that will be used for all the pages and the /inc/ is for files that will only be called a few times or just once.

  • http://www.ennuidesign.com Jason Lengstorf

    @marko & Montana Flynn:

    Like Montana Flynn said, the common/ directory is for files accessed in nearly every page, while inc/ is for classes which aren't used throughout the whole site.

    The only inconsistency there is that we have constants.inc.php in the inc/ folder, when it should actually be in the common/ folder. I'm not really sure why we did that… :)

  • http://www.bobborst.com Bob Borst

    Hi Jason,

    Excellent series on app creation. I have one little tip. I noticed when you changed your CSS to expand the code blocks on hover, you mentioned that Montana Flynn might have to hard refresh to see the changes.

    One thing I do is add a query string to the link that calls the CSS file with the date and time I last edited it. This makes the file have a new URL and the browsers will reload it automatically. For example:

    href=”/css/default.css?dt=20091201-1509″

  • http://www.mad-at-teilar.gr/ fuSi0N

    I 'm trying to re-create your application for a university project of mine.

    Where do I have to put the “Creating a PDO Object” script?

    In /common folder but in what name?

    And also I coundn't find what to put inside the base.php file :(

  • http://www.ennuidesign.com Jason Lengstorf

    @fuSi0N:

    The PDO object goes in base.php. Sorry for the confusion.

  • security

    You appear to have neglected the following, as your application is vulernable to them all:

    XSS (fix: as you insert data into HTML, escape it with htmlspecialchars())

    CSRF (fix: require a per-session anti-csrf token to be submitted with all requests that will mutate data)

    SMTP injection (fix: sanitize what you pass to mail())

    Session fixation (fix: use session_regenerate_id() on login and at any other time there's a change in privileges)

    On a related note there is a severe lack of input validation which not only impacts security but the integrity of your data. For example, I verified the XSS vulnerability by inserting javascript into my signup username. Not only did it echo it back to me and execute it (XSS), but it also created the account! So now you have an email address in your database that makes absolutely no sense.

  • http://www.ennuidesign.com Jason Lengstorf

    @security:

    Thanks for pointing all of that out! We were made aware of the CSRF and XSS vulnerabilities, and we started working to implement solutions.

    We added regex email validation and implemented htmlentities(trim($input), ENT_QUOTES) to try and validate our input and avoid the SMTP injection. We're planning on releasing a Part 9 next week that will cover the patches we've made; I'd appreciate feedback on the article, as I'm always looking to improve my knowledge of security. Thanks!

  • Derik

    I was curious if there was ever any thought as to what would happen to users who never verify their accounts.

    For example: if a spam bot submitted an email address and the email address wouldn't ever get verified, would that users email address eventually be deleted from the database or would the database hold onto that email address for the life of the project.

    I would like to know your thoughts on this.

  • http://www.ennuidesign.com Jason Lengstorf

    @Derik:

    For this project, we didn't do anything to counter dead entries. Some kind of quick check for unverified users that are older than a certain length of time would probably be a good thing to do. It would cut down on unnecessary size in the database and leave only active rows to be indexed and queried.

    I'll add that to the list of 2.0 features for Colored Lists. Thanks!

  • http://www.mad-at-teilar.gr/ fuSi0N

    I'm having an issue with greek characters…

    I 've properly added array(PDO::MYSQL_ATTR_INIT_COMMAND => “SET NAMES utf8″) in the PDO constructor.

    I've also used SET NAMES UTF8 query in phpMyAdmin.

    But somehow, the application refuses to store and retrieve utf8 characters.

    Jquery passes utf8 characters with no problems, checked with console.log() and inserting a list_item manually wth phpMyAdmin also works fine.

    [desperate mode ON] :(

  • http://www.ennuidesign.com Jason Lengstorf

    @fuSi0N:

    I think the problem might be that I used utf8_general_ci — see what happens if you change the field to utf8_unicode_ci. I haven't tested this, but I've got a hunch.

    Let me know what happens!

  • http://www.mad-at-teilar.gr/ fuSi0N

    Hello and happy new year!

    Changing utf8_general_ci to utf8_unicode_ci doesn't help :(

    It seems like a bug because updating a list item into greek characters works fine and both methods addListItem() and updateListItem() are very much alike.

    Any ideas?

  • http://www.ennuidesign.com Jason Lengstorf

    @fuSi0N:

    Unfortunately, I'm not really familiar with character encoding, so I don't know that I can be of much help. The guts of addListItem() and updateListItem() can be made to read almost identically, so maybe you can try swapping the method that works in and see if that makes a difference. It's also possible that the JS could be causing a problem.

    If you get it figured out, let me know. Good luck!

  • http://www.mad-at-teilar.gr/ fuSi0N

    @Jason

    I 'm gonna present the project as it is.

    Thanx for your help. :)

  • jszeof

    “We're planning on releasing a Part 9 next week that will cover the patches we've made; I'd appreciate feedback on the article, as I'm always looking to improve my knowledge of security.”

    Is there going to be a part 9? I am looking to improve my knowledge of security too.

  • http://www.mad-at-teilar.gr/ fuSi0N

    @Jason

    My collegue, partner in the a project, found this mistake in lists.js.

    if(newListItemText.length > 0) {

    $.ajax({

    type: “POST”,

    url: “db-interaction/lists.php”,

    data: “action=add&list=” + forList + “&text=” + newListItemText + “&pos=” + newListItemRel, …

    UTF8 problem fixed! :)

    You should also change this in coloredlists.com, it is not possible to add non-english characters either there.

  • http://www.ennuidesign.com Jason Lengstorf

    @fuSi0N

    By removing the calls to strip_tags() and cleanHREF(), your application is exposed to some security risks. I see three functions that could be causing the UTF8 issue: cleanHREF(), strip_tags(), and escape(). I haven't tested any of them for UTF8 compatibility, so I'd recommend trying each one individually to see what happens.

    When I have a little extra time I'll do the same for Colored Lists and we'll see if we can't work it out.

    Thanks for pointing this out!

  • http://www.mad-at-teilar.gr/ fuSi0N

    escape() is the problem here, you could also use unescape() as you retrieve data.

    I 'm guessing this should work just fine. :)

  • Danny

    I am fairly new to PHP.

    I hope this doesen't sound stupid.

    I see mention of config.php file in the tutorial but it is not in the downloaded files.

    Am I missing something?

    How do you connect to the database?

    Thanks

    Danny

  • Angus

    The logout.php doesnt seem to work with IE, just constantly refreshes. It seems to be the meta refresh, although it doesn't mind the one used on the login page.

    I thought it might just be me as I am working locally, but the live app does the same.

    I have just replaced it with a header call instead: header(“Location: index.php”);

    which has done the trick.

  • http://www.ennuidesign.com Jason Lengstorf

    @Danny:

    I apologize, I got my names crossed: config.inc.php doesn't exist; the proper file is constants.inc.php. That's the file that connects to the database.

    Sorry about that.

    @Angus:

    Thanks! I'll look into that!

  • Angus

    @Jason

    No worries.

    I forgot to say thanks for the great tutorial, I'm using it as inspiration to learn the wonders of OOP, been meaning to for a while just needed a practical example of how to implement it.

  • Vitor Oliveira

    Hey Jason,

    Will it be a form validation do the sign up process? like if you leave it blank?

    Will this create bank entries in the database?

    Thanks and great work!!

    Vitor

  • kosaidpo

    hi dude tnx for this great tuts

    i really dont get this wht i kno that query methode is only to get the data from a DB but not to insert imtlkin abt this thingy u used to insert a default list for a user

    !$this->_db->query($sql)

    tnx again

  • anujv73

    Hey i wont find 4 part of series.. Pls any one help me to find that…thanks

  • http://www.ennuidesign.com Jason Lengstorf

    @kosaidpo:

    You can find the whole series navigation here, plus the source and demo: http://css-tricks.com/examples/WebAppFromScratch/

  • kosaidpo

    tnx dude fo ur reply yeh i kno : D

    noo i mean we use query->($sql) to output the data from db well $query('SELECT ….')

    we cant put INSERTE or UPDATE

    correct ??

  • http://www.ennuidesign.com Jason Lengstorf

    @kosaidpo:

    You can, but I'd recommend using prepare() for any query that will be putting data into your database. http://us3.php.net/manual/en/pdo.prepare.php

  • kosaidpo

    okies tnx dude i o use it i never used query b4 thtas why i was wonderin

    : D

  • kosaidpo

    okies tnx dude i o use it i never used query b4 thtas why i was wonderin

    : D

  • Henrik

    First of all, thanks for a great tutorial!

    If two passwords, that do not match ,are entered when verifying the account, no error message is displayed. The signup page is displayed.

    Btw, after successfully verifying the account, shouldn't the user be directed to the index.php?

  • http://www.ennuidesign.com Jason Lengstorf

    @Henrik:

    Are you using the final source we posted? http://css-tricks.com/examples/WebAppFromScratch/

  • Henrik

    Yes, using latest source. The problem is reproducable in your ColoredList app.

    $ret = $users->updatePassword(); // If pwd's don't match $ret == false

    // So the next check will be false

    if(isset($ret[0])):

    I think it should be something like this instead:

    if(isset($ret[0]) || !$ret):

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

    if($ret[0]

    Password mismatch – please try again

    Choose a Password

    Not sure if the inserted code here in my comment will show up correctly…

  • Henrik

    Hmm, my php statements in the comemnt above does not show, but I think you get my point anyway.

    if ret[0] < 3

    if !ret

    “password mismatch”

    else

    “Choose password.”

  • Ron Jacobson

    I'm having problem with the outgoing mail b/c I believe my ISP (time warner) blocks outgoing mail. Anyone know of any solutions to this and/or if I am incorrect in what I said? Thanks!

  • http://www.ennuidesign.com Jason Lengstorf

    @Ron Jacobson

    It's hard to guess at what would cause that. You could try the PEAR Mail extension, but I don't know if that will make a difference if your ISP is blocking email.

    Good luck!

  • javier amat cozar

    i receive an unexpected error as this:

    Parse error: syntax error, unexpected '{' in /home/egrupoma/public_html/common/base.php on line 25

    this is the { after try in base.php

  • http://www.ennuidesign.com Jason Lengstorf

    @javier amat cozar:

    That sounds like there's probably a typo somewhere in your code. Check your syntax above the try…catch block and see if you can find something.

    Good luck!

  • javier amat cozar

    i've just copied and pasted your code!, can you take a look?

  • http://www.ennuidesign.com Jason Lengstorf

    @javier amat cozar:

    Sure. Go ahead and post your code online, somewhere like Gist (http://gist.github.com/).

  • javier amat cozar

    here it is

    git://gist.github.com/539818.git

  • http://www.ennuidesign.com Jason Lengstorf

    @javier amat cozar:

    Well, that code shouldn't be throwing a parse error. Try commenting out the Firebug code and see if that makes a difference. It could be that your server configuration isn't playing nice, but I really can't say for sure from what I've seen.

    Sorry I couldn't be of more help. Good luck!

  • http://www.about.me/zmcmahon Zach McMahon

    Jason & Chris,

    I am only about half way through this tutorial and I have been amazed at your level of detail and the completeness of this walk through. It's really helping me to learn how all the different parts of a site work together. I'm not done yet, but I wanted to make sure I said thanks.

    Zach

  • http://www.thewebmasterservice.com rev30

    this tutorial is the best.

    I have a big tendency to think in terms of how I could pull something off by customizing the heck out of a CMS, or by using a framework I dont need. I love seeing some back to basics homebrew goodness. keep it up guys!

  • Jay

    Well written tutorial, very easy to follow. Only thing I don't like is the architecture of the apps the lack of strict enforcement of MVC principles makes the code very hard to maintain when it grows to certain size, which is the problem for most PHP shops.

  • http://- sham’un

    THanks for the wonderful tutorial.. a real help thankls

  • Karim

    I spend a lot of time wondering whether I need to be using MVC etc but you have reminded me that the most readable approach is best. Thanks.

We’re Ready When You Are

Ready to build something awesome?

Start Here