Chapter 9. Performing Form Validation with Regular Expressions

It's your responsibility as a developer to ensure that your users' data is useful to your app, so you need to ensure that critical information is validated before storing it in your database.

In the case of the calendar application, the date format is critical: if the format isn't correct, the app will fail in several places. To verify that only valid dates are allowed into the database, you'll use regular expressions (regexes), which are powerful pattern-matching tools that allow developers much more control over data than a strict string comparison search.

Before you can get started with adding validation to your application, you need to get comfortable using regular expressions. In the first section of this chapter, you'll learn how to use the basic syntax of regexes. Then you'll put regexes to work doing server-side and client-side validation.

Getting Comfortable with Regular Expressions

Regular expressions are often perceived as intimidating, difficult tools. In fact, regexes have such a bad reputation among programmers that discussions about them are often peppered with this quote:

 

Some people, when confronted with a problem, think, "I know, I'll use regular expressions." Now they have two problems.

 
 --—Jamie Zawinski

This sentiment is not entirely unfounded because regular expressions come with a complex syntax and little margin for error. However, after overcoming the initial learning curve, regexes are an incredibly powerful tool with myriad applications in day-to-day programming.

Understanding Basic Regular Expression Syntax

In this book, you'll learn Perl-Compatible Regular Expression (PCRE) syntax. This syntax is compatible with PHP and JavaScript, as well as most other programming languages.

Note

You can read more about PCRE at http://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions.

Setting up a Test File

To learn how to use regexes, you'll need a file to use for testing. In the public folder, create a new file called regex.php and place the following code inside it:

<!DOCTYPE html
  PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
    <meta http-equiv="Content-Type"
          content="text/html;charset=utf-8" />
    <title>Regular Expression Demo</title>
    <style type="text/css">
        em {
            background-color: #FF0;
            border-top: 1px solid #000;
            border-bottom: 1px solid #000;
        }
    </style>
</head>

<body>
<?php

/*
 * Store the sample set of text to use for the examples of regex
 */
$string = <<<TEST_DATA

<h2>Regular Expression Testing</h2>
<p>
    In this document, there is a lot of text that can be matched
    using regex. The benefit of using a regular expression is much
    more flexible — albeit complex — syntax for text
    pattern matching.
</p>
<p>
    After you get the hang of regular expressions, also called
    regexes, they will become a powerful tool for pattern matching.
</p>
<hr />
TEST_DATA;
/*
 * Start by simply outputting the data
 */
echo $string;

?>

</body>

</html>

Save this file, then load http://localhost/regex.php in your browser to view the sample script (see Figure 9-1).

The sample file for testing regular expressions

Figure 9-1. The sample file for testing regular expressions

Replacing Text with Regexes

To test regular expressions, you'll wrap matched patterns with <em> tags, which are styled in the test document to have top and bottom borders, as well as a yellow background.

Accomplishing this with regexes is similar using str_replace() in PHP with the preg_replace() function. A pattern to match is passed, followed by a string (or pattern) to replace the matched pattern with. Finally, the string within which the search is to be performed is passed:

preg_replace($pattern, $replacement, $string);

Note

The p in preg_replace() signifies the use of PCRE. PHP also has ereg_replace(), which uses the slightly different POSIX regular expression syntax; however, the ereg family of functions has been deprecated as of PHP 5.3.0.

The only difference between str_replace() and preg_replace() on a basic level is that the element passed to preg_replace() for the pattern must use delimiters, which let the function know which part of the regex is the pattern and which part consists of modifiers, or flags that affect how the pattern matches. You'll learn more about modifiers a little later in this section.

The delimiters for regex patterns in preg_replace() can be any non-alphanumeric, non-backslash, and non-whitespace characters placed at the beginning and end of the pattern. Most commonly, forward slashes (/) or hash signs (#) are used. For instance, if you want to search for the letters cat in a string, the pattern would be /cat/ (or #cat#, %cat%, @cat@, and so on).

Choosing Regexes vs. Regular String Replacement

To explore the differences between str_replace() and preg_replace(), try using both functions to wrap any occurrence of the word regular with <em> tags. Make the following modifications to regex.php:

<!DOCTYPE html
  PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
    <meta http-equiv="Content-Type"
          content="text/html;charset=utf-8" />
    <title>Regular Expression Demo</title>
    <style type="text/css">
        em {
            background-color: #FF0;
            border-top: 1px solid #000;
            border-bottom: 1px solid #000;
        }
    </style>
</head>

<body>
<?php

/*
 * Store the sample set of text to use for the examples of regex
 */
$string = <<<TEST_DATA

<h2>Regular Expression Testing</h2>
<p>
    In this document, there is a lot of text that can be matched
    using regex. The benefit of using a regular expression is much
    more flexible — albeit complex — syntax for text
    pattern matching.
</p>
<p>
    After you get the hang of regular expressions, also called
regexes, they will become a powerful tool for pattern matching.
</p>
<hr />
TEST_DATA;

/*
 * Use str_replace() to highlight any occurrence of the word
 * "regular"
 */
echo str_replace("regular", "<em>regular</em>", $string);

/*
 * Use preg_replace() to highlight any occurrence of the word
 * "regular"
 */
echo preg_replace("/regular/", "<em>regular</em>", $string);

?>

</body>

</html>

Executing this script in your browser outputs the test information twice, with identical results (see Figure 9-2).

The word regular highlighted with both regexes and regular string replacement

Figure 9-2. The word regular highlighted with both regexes and regular string replacement

Drilling Down on the Basics of Pattern Modifiers

You may have noticed that the word regular in the title is not highlighted. This is because the previous example is case sensitive.

To solve this problem with simple string replacement, you can opt to use the str_ireplace() function, which is nearly identical to str_replace(), except that it is case insensitive.

With regular expressions, you will still use preg_replace(), but you'll need a modifier to signify case insensitivity. A modifier is a letter that follows the pattern delimiter, providing additional information to the regex about how it should handle patterns. For case insensitivity, the modifier i should be applied.

Modify regex.php to use case-insensitive replacement functions by making the modifications shown in bold:

<!DOCTYPE html
  PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
    <meta http-equiv="Content-Type"
          content="text/html;charset=utf-8" />
    <title>Regular Expression Demo</title>
    <style type="text/css">
        em {
            background-color: #FF0;
            border-top: 1px solid #000;
            border-bottom: 1px solid #000;
        }
    </style>
</head>

<body>
<?php

/*
 * Store the sample set of text to use for the examples of regex
 */
$string = <<<TEST_DATA

<h2>Regular Expression Testing</h2>
<p>
    In this document, there is a lot of text that can be matched
    using regex. The benefit of using a regular expression is much
    more flexible — albeit complex — syntax for text
    pattern matching.
</p>
<p>
    After you get the hang of regular expressions, also called
    regexes, they will become a powerful tool for pattern matching.
</p>
<hr />
TEST_DATA;

/*
 * Use str_ireplace() to highlight any occurrence of the word
 * "regular"
 */
echo str_ireplace("regular", "<em>regular</em>", $string);

/*
 * Use preg_replace() to highlight any occurrence of the word
 * "regular"
 */
echo preg_replace("/regular/i", "<em>regular</em>", $string);

?>

</body>

</html>

Now loading the file in your browser will highlight all occurrences of the word regular, regardless of case (see Figure 9-3).

A case-insensitive search of the sample data

Figure 9-3. A case-insensitive search of the sample data

As you can see, this approach has a drawback: the capitalized regular in the title is changed to lowercase when it is replaced. In the next section, you'll learn how to avoid this issue by using groups in regexes.

Getting Fancy with Backreferences

The power of regexes starts to appear when you apply one of their most useful features: grouping and backreferences. A group is any part of a pattern that is enclosed in parentheses. A group can be used in the replacement string (or later in the pattern) with a backreference, a numbered reference to a named group.

This all sounds confusing, but in practice it's quite simple. Each set of parentheses from left to right in a regex is stored with a numeric backreference, which can be accessed using a backslash and the number of the backreference (1) or by using a dollar sign and the number of the backreference ($1).

The benefit of this is that it gives regexes the ability to use the matched value in the replacement, instead of a predetermined value as in str_replace() and its ilk.

To keep the replacement contents in your previous example in the proper case, you need to use two occurrences of str_replace(); however, you can achieve the same effect by using a backreference in preg_replace() with just one function call.

Make the following modifications to regex.php to see the power of backreferences in regexes:

<!DOCTYPE html
  PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
    <meta http-equiv="Content-Type"
          content="text/html;charset=utf-8" />
    <title>Regular Expression Demo</title>
    <style type="text/css">
        em {
            background-color: #FF0;
            border-top: 1px solid #000;
            border-bottom: 1px solid #000;
        }
    </style>
</head>

<body>
<?php

/*
 * Store the sample set of text to use for the examples of regex
 */
$string = <<<TEST_DATA

<h2>Regular Expression Testing</h2>
<p>
    In this document, there is a lot of text that can be matched
    using regex. The benefit of using a regular expression is much
    more flexible — albeit complex — syntax for text
    pattern matching.
</p>
<p>
After you get the hang of regular expressions, also called
    regexes, they will become a powerful tool for pattern matching.
</p>
<hr />
TEST_DATA;

/*
 * Use str_replace() to highlight any occurrence of the word
 * "regular"
 */
$check1 = str_replace("regular", "<em>regular</em>", $string);

/*
 * Use str_replace() again to highlight any capitalized occurrence
 * of the word "Regular"
 */
echo str_replace("Regular", "<em>Regular</em>", $check1);

/*
 * Use preg_replace() to highlight any occurrence of the word
 * "regular", case-insensitive
 */
echo preg_replace("/(regular)/i", "<em>$1</em>", $string);

?>

</body>

</html>

As the preceding code illustrates, it's already becoming cumbersome to use str_replace() for any kind of complex string matching. After saving the preceding changes and reloading your browser, however, you can achieve the desired outcome using both regexes and standard string replacement (see Figure 9-4).

A more complex replacement

Figure 9-4. A more complex replacement

Note

The remaining examples in this section will use only regexes.

Matching Character Classes

In some cases, it's desirable to match more than just a word. For instance, sometimes you want to verify that only a certain range of characters was used (i.e., to make sure only numbers were supplied for a phone number or that no special characters were used in a username field).

Regexes allow you to specify a character class, which is a set of characters enclosed in square brackets. For instance, to match any character between the letter a and the letter c, you would use [a-c] in your pattern.

You can modify regex.php to highlight any character from A-C. Additionally, you can move the pattern into a variable and output it at the bottom of the sample data; this helps you see what pattern is being used when the script is loaded. Add the code shown in bold to accomplish this:

<!DOCTYPE html
  PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
    <meta http-equiv="Content-Type"
          content="text/html;charset=utf-8" />
    <title>Regular Expression Demo</title>
<style type="text/css">
        em {
            background-color: #FF0;
            border-top: 1px solid #000;
            border-bottom: 1px solid #000;
        }
    </style>
</head>

<body>
<?php

/*
 * Store the sample set of text to use for the examples of regex
 */
$string = <<<TEST_DATA

<h2>Regular Expression Testing</h2>
<p>
    In this document, there is a lot of text that can be matched
    using regex. The benefit of using a regular expression is much
    more flexible — albeit complex — syntax for text
    pattern matching.
</p>
<p>
    After you get the hang of regular expressions, also called
    regexes, they will become a powerful tool for pattern matching.
</p>
<hr />
TEST_DATA;

/*
 * Use regex to highlight any occurence of the letters a-c
 */
$pattern = "/([a-c])/i";
echo preg_replace($pattern, "<em>$1</em>", $string);

/*
 * Output the pattern you just used
 */
echo "
<p>Pattern used: <strong>$pattern</strong></p>";

?>

</body>

</html>

After reloading the page, you'll see the characters highlighted (see Figure 9-5). You can achieve identical results using [abc], [bac], or any other combination of the characters because the class will match any one character from the class. Also, because you're using the case-insensitive modifier (i), you don't need to include both uppercase and lowercase versions of the letters. Without the modifier, you would need to use [A-Ca-c] to match either case of the three letters.

Any character from A-C is highlighted

Figure 9-5. Any character from A-C is highlighted

Matching Any Character Except...

To match any character except those in a class, prefix the character class with a caret (^). To highlight any characters except A-C, you would use the pattern /([^a-c])/i (see Figure 9-6).

Highlighting all characters, except letters A-C

Figure 9-6. Highlighting all characters, except letters A-C

Note

It's important to mention that the preceding patterns enclose the character class within parentheses. Character classes do not store backreferences, so parentheses still must be used to reference the matched text later.

Using Character Class Shorthand

Certain character classes have a shorthand character. For example, there is a shorthand class for every word, digit, or space character:

  • Word character class shorthand (w): Matches patterns like [A-Za-z0-9_]

  • Digit character class shorthand (d): Matches patterns like [0-9]

  • Whitespace character class shorthand (s): Matches patterns like [ ]

Using these three shorthand classes can improve the readability of your regexes, which is extremely convenient when you're dealing with more complex patterns.

You can exclude a particular type of character by capitalizing the shorthand character:

  • Non-word character class shorthand (W): Matches patterns like [^A-Za-z0-9_]

  • Non-digit character class shorthand (D): Matches patterns like [^0-9]

  • Non-whitespace character class shorthand (S): Matches patterns like [^ ]

Note

, , and are special characters that represent tabs and newlines; a space is represented by a regular space character ( ).

Finding Word Boundaries

Another special symbol to be aware of is the word boundary symbol (). By placing this before and/or after a pattern, you can ensure that the pattern isn't contained within another word. For instance, if you want to match the word stat, but not thermostat, statistic, or ecstatic, you would use this pattern: /stat/.

Using Repetition Operators

When you use character classes, only one character out of the set is matched, unless the pattern specifies a different number of characters. Regular expressions give you several ways to specify a number of characters to match:

  • The star operator (*) matches zero or more occurrences of a character.

  • The plus operator (+) matches one or more occurrences of a character.

  • The special repetition operator ({min,max}) allows you to specify a range of character matches.

Matching zero or more characters is useful when using a string that may or may not have a certain piece of a pattern in it. For example, if you want to match all occurrences of either John or John Doe, you can use this pattern to match both instances: /John( Doe)*/.

Matching one or more characters is good for verifying that at least one character was entered. For instance, if you want to verify that a user enters at least one character into a form input and that the character is a valid word character, you can use this pattern to validate the input: /w+/.

Finally, matching a specific range of characters is especially useful when matching numeric ranges. For instance, you can use this pattern to ensure a value is between 0 and 99: /d{1,2}/.

In your example file, you use this regex pattern to find any words consisting of exactly four letters: /(w{4})/ (see Figure 9-7).

Matching only words that consist of exactly four letters

Figure 9-7. Matching only words that consist of exactly four letters

Detecting the Beginning or End of a String

Additionally, you can force the pattern to match from the beginning or end of the string (or both). If the pattern starts with a caret (^), the regex will only match if the pattern starts with a matching character. If it ends with a dollar sign ($), the regex will match only if the string ends with the preceding matching character.

You can combine these different symbols to make sure an entire string matches a pattern. This is useful when validating input because you can verify that the user only submitted valid information. For instance, you can you can use this regex pattern to verify that a username contains only the letters A-Z, the numbers 0-9, and the underscore character: /^w+$/.

Using Alternation

In some cases, it's desirable to use either one pattern or another. This is called alternation, and it's accomplished using a pipe character (|). This approach allows you to define two or more possibilities for a match. For instance, you can use this pattern to match either three-, six-, or seven-letter words in regex.php: /(w{3}|w{6,7})/ (see Figure 9-8).

Using alternation to match only three-, six-, and seven-letter words

Figure 9-8. Using alternation to match only three-, six-, and seven-letter words

Using Optional Items

In some cases, it becomes necessary to allow certain items to be optional. For instance, to match both single and plural forms of a word like expression, you need to make the s optional.

To do this, place a question mark (?) after the optional item. If the optional part of the pattern is longer than one character, it needs to be captured in a group (you'll use this technique in the next section).

For now, use this pattern to highlight all occurrences of the word expression or expressions: /(expressions?)/i (see Figure 9-9).

Matching a pattern with an optionals at the end

Figure 9-9. Matching a pattern with an optionals at the end

Putting It All Together

Now that you've got a general understanding of regular expressions, it's time to use your new knowledge to write a regex pattern that will match any occurrence of the phrases regular expression or regex, including the plural forms.

To start, look for the phrase regex: /(regex)/i (see Figure 9-10).

Matching the word regex

Figure 9-10. Matching the word regex

Next, add the ability for the phrase to be plural by inserting an optional es at the end: /(regex(es)?)/i (see Figure 9-11).

Adding the optional match for the plural form of regex

Figure 9-11. Adding the optional match for the plural form of regex

Next, you will add to the pattern so that it also matches the word regular with a space after it; you will also make the match optional: /(reg(ulars)?ex(es)?)/i (see Figure 9-12).

Adding an optional check for the word regular

Figure 9-12. Adding an optional check for the word regular

Now expand the pattern to match the word expression as an alternative to es: /(reg(ulars)?ex(pression|es)?)/i (see Figure 9-13).

Adding alternation to match expression

Figure 9-13. Adding alternation to match expression

Finally, add an optional s to the end of the match for expression: /(reg(ulars)?ex(pressions?|es)?)/i (see Figure 9-14).

The completed regular expression

Figure 9-14. The completed regular expression

Tip

The examples in this chapter go over the most common features of regular expressions, but they don't cover everything that regexes have to offer. Jan Goyvaerts has put together a fantastic resource for learning all of the ins-and-outs of regexes, as well as some tools for testing them, at http://www.regular-expressions.info/.

Adding Server-Side Date Validation

Now that you have a basic understanding of regexes, you're ready to start validating user input. For this app, you need to ensure that the date format is correct, so that the app doesn't crash by attempting to parse a date that it can't understand.

You'll begin by adding server-side validation. This is more of a fallback because later you'll add validation with jQuery. However, you should never rely solely on JavaScript to validate user input because the user can easily turn off JavaScript support and therefore completely disable your JavaScript validation efforts.

Defining the Regex Pattern to Validate Dates

The first step toward implementing date validation is to define a regex pattern to match the desired format. The format the calendar app uses is YYYY-MM-DD HH:MM:SS.

Setting up Test Data

You need to modify regex.php with a valid date format and a few invalid formats, so you can test your pattern. Start by matching zero or more numeric characters with your regex pattern. Do this by making the following changes shown in bold:

<!DOCTYPE html
  PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
    <meta http-equiv="Content-Type"
          content="text/html;charset=utf-8" />
    <title>Regular Expression Demo</title>
    <style type="text/css">
        em {
            background-color: #FF0;
            border-top: 1px solid #000;
            border-bottom: 1px solid #000;
        }
    </style>
</head>

<body>
<?php

/*
 * Set up several test date strings to ensure validation is working
 */
$date[] = '2010-01-14 12:00:00';
$date[] = 'Saturday, May 14th at 7pm';
$date[] = '02/03/10 10:00pm';
$date[] = '2010-01-14 102:00:00';

/*
 * Date validation pattern
 */
$pattern = "/(d*)/";

foreach ( $date as $d )
{
    echo "<p>", preg_replace($pattern, "<em>$1</em>", $d), "</p>";
}

/*
 * Output the pattern you just used
 */
echo "
<p>Pattern used: <strong>$pattern</strong></p>";

?>

</body>

</html>

After saving the preceding code, reload http://localhost/regex.php in your browser to see all numeric characters highlighted (see Figure 9-15).

Matching any numeric character

Figure 9-15. Matching any numeric character

Matching the Date Format

To match the date format, start by matching exactly four digits at the beginning of the string to validate the year: /^(d{4})/ (see Figure 9-16).

Validating the year section of the date string

Figure 9-16. Validating the year section of the date string

Next, you need to validate the month by matching the hyphen and two more digits: /^(d{4}(-d{2}))/ (see Figure 9-17).

Expanding the validate month section of the date string

Figure 9-17. Expanding the validate month section of the date string

Notice that the month and date sections are identical: a hyphen followed by two digits. This means you can simply repeat the month-matching pattern to validate the day using a repetition operator after the group: /^(d{4}(-d{2}){2})/ (see Figure 9-18).

Adding the day part of the date string to the pattern

Figure 9-18. Adding the day part of the date string to the pattern

Now match a single space and the hour section: /^(d{4}(-d{2}){2} (d{2}))/ (see Figure 9-19).

Note

Make sure that you include the space character. The shorthand class (s) shouldn't be used because new lines and tabs should not match in this instance.

Validating the hour section of the date string

Figure 9-19. Validating the hour section of the date string

To validate the minutes, you match a colon and exactly two digits: /^(d{4}(-d{2}){2} (d{2})(:d{2}))/ (see Figure 9-20).

Validating the minutes section of the date string

Figure 9-20. Validating the minutes section of the date string

Finally, repeat the pattern for the minutes to match the seconds, and then use the dollar sign modifier to match the end of the string: /^(d{4}(-d{2}){2} (d{2})(:d{2}){2})$/ (see Figure 9-21).

Validating the seconds section of the date string and completing the pattern

Figure 9-21. Validating the seconds section of the date string and completing the pattern

Armed with this regex pattern, you can now validate the date input in your application.

Adding a Validation Method to the Calendar Class

To validate the date string, you will add a new private method to the Calendar class called _validDate().

This method will accept the date string to be validated, then compare it to the validation pattern using preg_match(), which returns the number of matches found in the given string. Because this particular pattern will only match if the entire string conforms to the pattern, a valid date will return 1, while an invalid date will return 0.

If the date is valid, the method will return TRUE; otherwise, it will return FALSE.

Add this method to the Calendar class by inserting the following bold code into class.calendar.inc.php:

<?php

class Calendar extends DB_Connect
{

    private $_useDate;

    private $_m;

    private $_y;

    private $_daysInMonth;

    private $_startDay;

    public function __construct($dbo=NULL, $useDate=NULL) {...}

    public function buildCalendar() {...}

    public function displayEvent($id) {...}

    public function displayForm() {...}

    public function processForm() {...}

    public function confirmDelete($id) {...}

    /**
     * Validates a date string
     *
     * @param string $date the date string to validate
     * @return bool TRUE on success, FALSE on failure
     */
    private function _validDate($date)
    {
        /*
         * Define a regex pattern to check the date format
*/
        $pattern = '/^(d{4}(-d{2}){2} (d{2})(:d{2}){2})$/';

        /*
         * If a match is found, return TRUE. FALSE otherwise.
         */
        return preg_match($pattern, $date)==1 ? TRUE : FALSE;
    }

    private function _loadEventData($id=NULL) {...}

    private function _createEventObj() {...}

    private function _loadEventById($id) {...}

    private function _adminGeneralOptions() {...}

    private function _adminEntryOptions($id) {...}

}

?>

Returning an Error if the Dates Don't Validate

Your next step is to modify the processForm() method so it calls the _validDate() method on both the start and end times for new entries. If the validation fails, simply return an error message.

Add the following bold code to processForm() to implement the validation:

<?php

class Calendar extends DB_Connect
{

    private $_useDate;

    private $_m;

    private $_y;

    private $_daysInMonth;

    private $_startDay;

    public function __construct($dbo=NULL, $useDate=NULL) {...}

    public function buildCalendar() {...}

    public function displayEvent($id) {...}
public function displayForm() {...}

    /**
     * Validates the form and saves/edits the event
     *
     * @return mixed TRUE on success, an error message on failure
     */
    public function processForm()
    {
        /*
         * Exit if the action isn't set properly
         */
        if ( $_POST['action']!='event_edit' )
        {
            return "The method processForm was accessed incorrectly";
        }

        /*
         * Escape data from the form
         */
        $title = htmlentities($_POST['event_title'], ENT_QUOTES);
        $desc = htmlentities($_POST['event_description'], ENT_QUOTES);
        $start = htmlentities($_POST['event_start'], ENT_QUOTES);
        $end = htmlentities($_POST['event_end'], ENT_QUOTES);

        /*
         * If the start or end dates aren't in a valid format, exit
         * the script with an error
         */
        if ( !$this->_validDate($start)
                || !$this->_validDate($end) )
        {
            return "Invalid date format! Use YYYY-MM-DD HH:MM:SS";
        }

        /*
         * If no event ID passed, create a new event
         */
        if ( empty($_POST['event_id']) )
        {
            $sql = "INSERT INTO `events`
                        (`event_title`, `event_desc`, `event_start`,
                            `event_end`)
                    VALUES
                        (:title, :description, :start, :end)";
        }

        /*
         * Update the event if it's being edited
         */
        else
{
            /*
             * Cast the event ID as an integer for security
             */
            $id = (int) $_POST['event_id'];
            $sql = "UPDATE `events`
                    SET
                        `event_title`=:title,
                        `event_desc`=:description,
                        `event_start`=:start,
                        `event_end`=:end
                    WHERE `event_id`=$id";
        }

        /*
         * Execute the create or edit query after binding the data
         */
        try
        {
            $stmt = $this->db->prepare($sql);
            $stmt->bindParam(":title", $title, PDO::PARAM_STR);
            $stmt->bindParam(":description", $desc, PDO::PARAM_STR);
            $stmt->bindParam(":start", $start, PDO::PARAM_STR);
            $stmt->bindParam(":end", $end, PDO::PARAM_STR);
            $stmt->execute();
            $stmt->closeCursor();

            /*
             * Returns the ID of the event
             */
            return $this->db->lastInsertId();
        }
        catch ( Exception $e )
        {
            return $e->getMessage();
        }
    }

    public function confirmDelete($id) {...}

    private function _validDate($date) {...}

    private function _loadEventData($id=NULL) {...}

    private function _createEventObj() {...}

    private function _loadEventById($id) {...}

    private function _adminGeneralOptions() {...}

    private function _adminEntryOptions($id) {...}
}

?>

You can test the validation by entering a bad entry into the form at http://localhost/admin.php (see Figure 9-22).

An entry with bad date values that should fail validation

Figure 9-22. An entry with bad date values that should fail validation

Note

You use http://localhost/admin.php because the only reason your server-side validation will be invoked is if the user has JavaScript disabled. In that case, the modal windows would not function, and the user would be brought to this form. In situations where JavaScript is enabled, the server-side acts as a double-check and an additional security measure against mischievous users.

After this form is submitted, the app will simply output the error message and die (see Figure 9-23). The calendar application is designed for users with JavaScript enabled; you use this approach to prevent the app from displaying errors.

The error message displayed when invalid dates are supplied

Figure 9-23. The error message displayed when invalid dates are supplied

Adding Client-Side Date Validation

For most users, JavaScript will be enabled. It's far more convenient as a user to get instant feedback on the form, so you will add new jQuery functionality to validate date strings on the client side.

Creating a New JavaScript File to Validate the Date String

Because you're going to continue to work with this script in the next chapter, you should put it in a separate file in the js folder called valid-date.js. This file will contain a function that is functionally equivalent to the _validDate() method in the Calendar class.

It will accept a date to validate, check it against the date-matching regex pattern you wrote previously using match(), and then return true if a match is found or false if match() returns null.

You build this function by inserting the following code into valid-date.js:

// Checks for a valid date string (YYYY-MM-DD HH:MM:SS)
function validDate(date)
{
    // Define the regex pattern to validate the format
    var pattern = /^(d{4}(-d{2}){2} (d{2})(:d{2}){2})$/;

    // Returns true if the date matches, false if it doesn't
    return date.match(pattern)!=null;
}

Note

The regex pattern is not enclosed in quotes. If you used quotes, the pattern would be stored as a string and interpreted accordingly—this would result in the script looking for an exact character match, rather than interpreting the regex pattern properly.

Including the New File in the Footer

To use the validDate() function, you'll need to include the new JavaScript file before init.js, so that the function is available to be called. Do this by opening footer.inc.php in the common folder and inserting the following bold code:

<script type="text/javascript"
          src="http://www.google.com/jsapi"></script>
    <script type="text/javascript">
        google.load("jquery", "1");
    </script>
    <script type="text/javascript"
          src="assets/js/valid-date.js"></script>
    <script type="text/javascript"
          src="assets/js/init.js"></script>
</body>

</html>

Preventing the Form Submission if Validation Fails

Now that validDate() is available in init.js, you need to add date validation before the form can be submitted. Store the start and end dates in variables (start and end, respectively), then check them using validDate() before allowing the form to be submitted.

Next, modify the click handler to the Submit button on the form that edits or creates events, and then trigger an alert with a helpful error message if either date input has an invalid value. You need to prevent the form from being submitted as well, so the user doesn't have to repopulate the other form fields.

You accomplish this by inserting the following bold code into init.js:

// Makes sure the document is ready before executing scripts
jQuery(function($){

var processFile = "assets/inc/ajax.inc.php",
    fx = {...}

$("li a").live("click", function(event){...});

$(".admin-options form,.admin")
    .live("click", function(event){...});

// Edits events without reloading
$(".edit-form input[type=submit]").live("click", function(event){

        // Prevents the default form action from executing
        event.preventDefault();

        // Serializes the form data for use with $.ajax()
        var formData = $(this).parents("form").serialize(),
// Stores the value of the submit button
            submitVal = $(this).val(),

        // Determines if the event should be removed
            remove = false,

        // Saves the start date input string
            start = $(this).siblings("[name=event_start]").val(),

        // Saves the end date input string
            end = $(this).siblings("[name=event_end]").val();

        // If this is the deletion form, appends an action
        if ( $(this).attr("name")=="confirm_delete" )
        {
            // Adds necessary info to the query string
            formData += "&action=confirm_delete"
                + "&confirm_delete="+submitVal;

            // If the event is really being deleted, sets
            // a flag to remove it from the markup
            if ( submitVal=="Yes, Delete It" )
            {
                remove = true;
            }
        }

        // If creating/editing an event, checks for valid dates
        if ( $(this).siblings("[name=action]").val()=="event_edit" )
        {
            if ( !validDate(start) || !validDate(end) )
            {
                alert("Valid dates only! (YYYY-MM-DD HH:MM:SS)");
                return false;
            }
        }

        // Sends the data to the processing file
        $.ajax({
                type: "POST",
                url: processFile,
                data: formData,
                success: function(data) {
                    // If this is a deleted event, removes
                    // it from the markup
                    if ( remove===true )
                    {
                       fx.removeevent();
                    }
// Fades out the modal window
                    fx.boxout();

                    // If this is a new event, adds it to
                    // the calendar
                    if ( $("[name=event_id]").val().length==0
                        && remove===false )
                    {
                        fx.addevent(data, formData);
                    }
                },
                error: function(msg) {
                    alert(msg);
                }
            });

    });

$(".edit-form a:contains(cancel)")
    .live("click", function(event){...});

});

Now save these changes, load http://localhost/ in your browser, and then create a new event with bad parameters using the modal window form (see Figure 9-24).

An entry that will fail to validate

Figure 9-24. An entry that will fail to validate

If you click the Submit button at this point, the validation will fail, and the app will show an alert box with the error message about the date format (see Figure 9-25).

The error message in an alert box after failing validation

Figure 9-25. The error message in an alert box after failing validation

After clicking the OK button in the alert box, the user will be able to edit her entry without having to repopulate any fields.

Summary

In this chapter, you tackled using regular expressions for form validation. The concepts you learned can be applied to validating any type of data, and they will greatly aid you in making sure the information supplied in forms is usable by your applications.

In the next chapter, you'll learn how to extend the jQuery object, both by directly extending the jQuery core and by developing a custom plugin for jQuery.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset