Hooray we made it! First of all, thanks for following along this whole journey. You can go check out the real live app for yourselves:


Below we're going to wrap up a few things by talking about some of the choices we made, security precautions, and ideas we (and you) have for a version 2.0 of this app.

Object-Oriented Programming

Because we should always aim to be efficient when programming, we built this app with the concept of DRY programming in mind. DRY stands for "Don't Repeat Yourself" and should lie somewhere near the core of our programming philosophy.

In our opinion, taking the object-oriented programming (OOP) approach was the best way to keep this app DRY. OOP allows us to group common methods together and separate tasks out without needing to pass parameters from function to function. For a little more information on OOP and why it's beneficial, read Jason's introduction to OOP.


Security is incredibly important in any application. We have users with accounts who are storing data with us. Those users are putting their trust in us to make sure their data is safe, which includes their password and all the information they've entered into the lists. This app is already pretty darn secure. Passwords are stored in encrypted formats and never sent in the clear via Email. All the interaction that happens with the database is secure. Only users who are logged in can issue commands which result in database changes, and those users are only able to issue commands that affect their own data.

But because there is a variety of AJAX stuff going on in this app, our security needs to take into account a few more scenarios. First, our JavaScript (like all JavaScript) is publically viewable. This JavaScript contains the code for making AJAX calls, meaning the URL we are sending to and what data that URL is expecting. This tells potential attackers a good bit of information regarding how they might send malicious requests. Because of this, we need to be very careful and ensure that all incoming data is escaped properly.

Security on the Server Side

Avoiding attacks on the server side involves two major risk factors: first, the potential for database attacks; and second, the potential that a malicious user could submit dangerous data that hurts our app or users in some way when read out of the database and displayed. Fortunately, PHP provides us with several methods to combat these risks.


Database attacks, called SQL injection, are a particularly nasty form of attack. A vulnerable database can be read, manipulated, or deleted entirely by a malicious user. This means that it is really important that we keep any kind of SQL injection from happening.

Lucky for us, PHP Data Objects (PDO) virtually eliminates the risk for SQL injection through the use of prepared statements, which are like query templates that we can customize with parameters. All the escaping is done for us when the parameters are inserted into the query, so it's virtually impossible for SQL injection to occur while using prepared statements.

It was because of this powerful security advantage that we chose PDO for this app. (Keep in mind that prepared statements are not exclusive to PDO; other database extensions, such as MySQLi, also support them.)

Data Escaping

While PDO is powerful against SQL injection, it doesn't help us when we've read the information out of the database. If a malicious user injects dangerous tags into our database, they'll still be dangerous when they're retrieved unless we take further measures to sanitize user data.

Fortunately, PHP has built-in functions that will allow us to perform basic sanitization of user input. We're namely using strip_tags() with a whitelist to make sure no <script> tags or other potentially dangerous tags make it into the database. Also, because we never want that sort of thing to be allowed, we're performing this escaping before the data is inserted into the database.

Security in the JavaScript

First, a good measure is to "Pack" the javascript so it isn't so easily readable, as well as downloads faster. There are a lot of tools available to do this, including this one by Dean Edwards.

Client Side Sanitization

Secondly, because we are inputting data and turn it around to display immediately on the screen, it's best to do some of that input scrubbing directly in the JavaScript. When a user enters a new list item, we'll take two steps to scrub it. First we'll ensure they aren't naughtily trying to insert immediately executable JavaScript into links:

// Check for JS in the href attribute
function cleanHREF(str) {
    return str.replace(/\<a(.*?)href=['"](javascript:)(.+?)<\/a>/gi, "Naughty!");

Then we'll also scrub that input text for any other HTML. Some HTML we will allow, in case users want to format their lists a bit like with <strong> tags and the like. With the function below, we'll strip away all tags except those set up in a whitelist.

NOTE: The strip_tags() function used below is part of the php.js project, which has ported a number of useful PHP functions to JavaScript.

var $whitelist = '<b><i><strong><em><a>',

// Strip HTML tags with a whitelist
function strip_tags(str, allowed_tags) {
    var key = '', allowed = false;
    var matches = [];
    var allowed_array = [];
    var allowed_tag = '';
    var i = 0;
    var k = '';
    var html = '';
    var replacer = function(search, replace, str) {
        return str.split(search).join(replace);
    // Build allowes tags associative array
    if (allowed_tags) {
        allowed_array = allowed_tags.match(/([a-zA-Z]+)/gi);
    str += '';
    // Match tags
    matches = str.match(/(<\/?[\S][^>]*>)/gi);
    // Go through all HTML tags
    for (key in matches) {
        if (isNaN(key)) {
            // IE7 Hack
        // Save HTML tag
        html = matches[key].toString();
        // Is tag not in allowed list? Remove from str!
        allowed = false;
        // Go through all allowed tags
        for (k in allowed_array) {
            // Init
            allowed_tag = allowed_array[k];
            i = -1;
            if (i != 0) { i = html.toLowerCase().indexOf('<'+allowed_tag+'>');}
            if (i != 0) { i = html.toLowerCase().indexOf('<'+allowed_tag+' ');}
            if (i != 0) { i = html.toLowerCase().indexOf('</'+allowed_tag)   ;}
            // Determine
            if (i == 0) {
                allowed = true;
        if (!allowed) {
            str = replacer(html, "", str); // Custom replace. No regexing
    return str;

These functions are implemented in js/lists.js before sending off the AJAX request that adds a new list item...

. . .
    // AJAX style adding of list items
        // HTML tag whitelist. All other tags are stripped.
        var $whitelist = '<b><i><strong><em><a>',
            forList = $("#current-list").val();
            newListItemText = strip_tags(cleanHREF($("#new-list-item-text").val()), $whitelist),
. . .


One last small measure we've taken to secure our app is to use POST over GET for all of our AJAX calls. This is done because the GET method should only be used for retrieval, and not for any action that will modify data in any way.

The primary reason not to use GET for modifying data is that a request made using GET is sent in the URL (i.e. http://example.com?get=request&is=this&part=here). There's an inherent danger in modifying data based on the information passed in the URL in that a user can cause duplicate processing by accidentally refreshing his or her browser.

A secondary, less important reason to use POST is that it's a little harder to send a bogus request using POST, which provides a (minor) deterrent to malicious users.

2.0 Features

Of course our work as designers and developers is never done. This is a great start on a simple and usable list application, but right away new features jump to mind. Here are some ideas of ways to expand functionality. Perhaps they slightly complicate things, but are all probably great ideas assuming they are implemented well.

  • List sharing
    Enter an email address for someone to share the list with. Sharing meaning literally collaborative editing. The user would need an account, so if they already have one they would just be emailed and asked to join the list (they can accept or not accept). If that email address did not have an account, they would be promoted to join first.
  • Multiple lists
    Right now a user can have only one list. It would probably be useful for users to keep multiple lists. Perhaps a dropdown menu for toggling between lists and a simple button for adding new ones. Plenty of interface to think about here, including figuring out how to delete lists.
  • RSS
    Each list could have it's own RSS feed. Options would probably be necessary, like what the RSS feed would contain (e.g. Do you wish to see entries for when list items are completed or not?). Feed URLs could be long gibberish URL's, so they are essentially completely private unless specifically shared.
  • iPhone interface
    Logging in via iPhone or other mobile device would have a better more optimized experience.

End It with a Giveaway!

In an effort to promote his new book, PHP for Absolute Beginners, Jason is giving away five copies of it at random. To enter the contest, leave a comment on this article and use the text "PHP for Absolute Beginners" in the comment. Make sure you use your real email address so we can get in touch with you. We'll pick the random winners next Friday.

Even if you don't win a free copy (or don't want to wait) we want to give you a little somethin' somethin' for sticking through this series: you can get 10% off the eBook version of PHP for Absolute Beginners using this discount code: PHPXBRZQXSIKG (good through 12/31/2009).

What Do You Think?

Give it to us straight: what do you think? What features would you like to see included in the 2.0 version of Colored Lists? Did we miss anything? Are there holes in our code? We'd love to see your optimizations, ideas, and other constructive criticisms; let's hear it in the comments!

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.