16.3 Common Components


This section describes the components of the winestore that are used by all parts of the application. We discuss our extensions of the PEAR ITX templates that provide a framework for all winestore pages and special-purpose tools for building form pages. We also discuss our validation functions, database parameters, custom error handler, and general-purpose constants and functions. The authentication module is discussed in Chapter 20.

16.3.1 Application Templates

The winestore application uses the PEAR ITX template class discussed in Chapter 7 to abstract presentation from code structure. Templates make the code easier to modify and the HTML presentation easy to change. For example, if you want to change the look and feel of the application, you only need to edit the template files in the templates directory.

We don't use the ITX templates directly. Instead, because we populate the same placeholders with similar data in each script, we've extended them to create two new child classes with the reusable features we need. This saves coding in script files, and leads to a simpler application that's easy to adapt. These two new classes are stored in the includes/template.inc file discussed later.

The first class we've created is the winestoreTemplate class that has a basic skeleton structure used throughout the winestore. It's associated with the generic template templates/winestore.tpl that's shown later in Example 16-1 and uses the template to show error messages to the user, optionally show a shopping cart icon and the total items in the cart, display the user login status, and present a configurable set of buttons at the bottom of the page.

The second class we've built extends the winestoreTemplate class to provide form data entry and error reporting features. This class is called winestoreFormTemplate. It includes features to create mandatory text widgets, optional text widgets, drop-down lists, and password entry widgets. The template that's used with it is templates/detail.tpl; it is included at runtime into the body of the parent templates/winestore.tpl template.

Both classes are discussed in more detail throughout this section.

16.3.2 The winestoreTemplate Class

The winestoreTemplate class provides a generic framework for all winestore pages. We've developed it to include the features we want on all winestore pages, and to save writing and maintaining different pages in the winestore. This is a practical example of how the templates discussed in Chapter 7 make application development easier.

The class works as follows. When the class constructor is called, the skeleton template templates/winestore.tpl that is shown in Example 16-1 is loaded and its placeholder PAGE_BODY is replaced with the page passed as a parameter to the constructor. Part of the class's function is also to add buttons, messages, the login status, and an optional cart icon to the page. We decide in each script what combination of these should be displayed. When we've finished working with the page body, the method winestoreTemplate::showWinestore( ) outputs the page.

The template page ends with a link to the W3C HTML validator. If you host the winestore scripts on a web server that's accessible over the Web, then you can click on the link and the HTML in the page will be validated. If your web server isn't externally accessible, clicking on the link won't work.

Example 16-1. The templates/winestore.tpl generic winestore template
<!DOCTYPE HTML PUBLIC                  "-//W3C//DTD HTML 4.01 Transitional//EN"                  "http://www.w3.org/TR/html401/loose.dtd"> <html> <head>   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">   <title>{TITLE}</title> </head> <body bgcolor="white"> <p align="right"><b>{LOGIN_STATUS}</b></p> <!-- BEGIN cartheader --> <table>   <tr>     <td><a href="{S_SHOWCART}" onMouseOut="cart.src='/books/2/581/1/html/2/{I_CART_OFF}'"                                onMouseOver="cart.src='/books/2/581/1/html/2/{I_CART_ON}'">         <img src="/books/2/581/1/html/2/{I_CART_OFF}" vspace=0 border=0              alt="cart picture" name="cart"></a>     </td>     <td>Total in cart: ${TOTAL} ({COUNT} items)      </td>    </tr> </table> <!-- END cartheader --> <!-- BEGIN message --> <br><b><font color="red">{INFO_MESSAGE}</font></b> <!-- END message --> {PAGE_BODY} <!-- BEGIN buttons --> <table> <tr> <!-- BEGIN form -->   <td><form action="{ACTION}" method="GET">     <input type="submit" name="{NAME}" value="{VALUE}">   </form></td> <!-- END form --> </tr> </table> <!-- END buttons --> <br><a href="http://validator.w3.org/check/referer">   <img src="/books/2/581/1/html/2/http://www.w3.org/Icons/valid-html401" height="31" width="88"         align="right" border="0" alt="Valid HTML 4.01!"></a> </body> </html>

Let's consider an example. Suppose we want to write a very simple HTML page for the winestore that says that the user has received an order discount; we don't actually use this page in the winestore, it's only an example to illustrate how to reuse our template. Here's the HTML body that we want to include in the page:

<h1>Hugh and Dave's Online Wines<h1> Congratulations! You've received a discount of ${AMOUNT}  off your cart total!

Let's assume this fragment is stored in the file templates/discount.tpl.

Now, to create a winestore page, we need to decide if we want to show the user a cart icon that they can click on to show their shopping cart. Let's do that because the page is about their cart. We also need to decide what buttons we want to show. In this case, let's assume we want two buttons: one to return to the main page of the store and another to visit the cart. We discuss the buttons more later.

Here's the very short but complete code to create our page:

<?php require_once "../includes/template.inc"; set_error_handler("customHandler"); $template = new winestoreTemplate("discount.tpl"); $template->setCurrentBlock( ); $template->setVariable("AMOUNT", "5.00"); // Don't show a cart icon, and show only the "home" button // Then, output the page $template->showWinestore(SHOW_ALL, B_HOME | B_SHOW_CART); ?>

The output of the example in a Mozilla browser is shown in Figure 16-3.

Figure 16-3. The winestoreTemplate class in action
figs/wda2_1603.gif


16.3.2.1 How the class works

The code for the winestoreTemplate class is shown in Example 16-2.

Example 16-2. The includes/template.inc file that shows the winestoreTemplate class
<?php // ITX template class extensions for the winestore // -- winestoreTemplate is a generic page // -- winestoreFormTemplate is a <form> page (and extends winestoreTemplate) require_once "DB.php"; require_once "HTML/Template/ITX.php"; require_once "winestore.inc"; define("P_TITLE", "Hugh and Dave's Online Wines"); // An extension of HTML_Template_ITX for the winestore pages class winestoreTemplate extends HTML_Template_ITX {    // Class constructor    // Loads the winestore.tpl skeleton, and a named page    // Sets the page title    function winestoreTemplate($pageBody, $pageTitle = P_TITLE)    {       $this->template = $this->HTML_Template_ITX(D_TEMPLATES);       $this->loadTemplatefile(T_SKELETON, true, true);       $this->setVariable("TITLE", $pageTitle);       $this->addBlockFile("PAGE_BODY", "pageBody", "{$pageBody}");    }    // Completes the page, and outputs with show( )    function showWinestore($options = NO_CART, $buttons = B_HOME)    {       $this->setCurrentBlock( );       // Show the user login status       $this->showLogin( );       if ($options & ~NO_CART)          // Show the dollar and item total of the cart          $this->showCart( );       // Display any messages to the user       $this->showMessage( );       // Set up the buttons       if ($buttons != 0)          $this->showButtons($buttons);       $this->setCurrentBlock( );       $this->parseCurrentBlock( );       $this->show( );    }    // Show the total number of items and dollar value of the shopping    // cart, as well as a clickable cart icon    function showCart( )    {       global $dsn;       $connection = DB::connect($dsn, true);       if (DB::isError($connection))          trigger_error($connection->getMessage( ), E_USER_ERROR);       // initialize an empty cart       $cartAmount = 0;       $cartCount = 0;       // If the user has added items to their cart, then       // the variable order_no will be registered       if (isset($_SESSION["order_no"]))       {          $cartQuery = "SELECT qty, price FROM items                        WHERE cust_id = -1                        AND order_id = {$_SESSION["order_no"]}";          // Find out the number and the dollar value of          // the items in the cart. To do this, we run the          // cartQuery through the connection on the database          $result = $connection->query($cartQuery);          if (DB::isError($result))             trigger_error($result->getMessage( ), E_USER_ERROR);          while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC))          {             $cartAmount += $row["price"] * $row["qty"];             $cartCount += $row["qty"];          }       }       $this->setCurrentBlock("cartheader");       $this->setVariable("I_CART_OFF", I_CART_OFF);       $this->setVariable("I_CART_ON", I_CART_ON);       $this->setVariable("S_SHOWCART", S_SHOWCART);       $this->setVariable("TOTAL", sprintf("%-.2f", $cartAmount));       $this->setVariable("COUNT", sprintf("%d", $cartCount));       $this->parseCurrentBlock("cartheader");    }    // Display any messages that are set, and then    // clear the message    function showMessage( )    {       // Is there an error message to show the user?       if (isset($_SESSION["message"]))       {          $this->setCurrentBlock("message");          $this->setVariable("INFO_MESSAGE", $_SESSION["message"]);          $this->parseCurrentBlock("message");          // Clear the error message          unset($_SESSION["message"]);       }    }    // Show whether the user is logged in or not    function showLogin( )    {       // Is the user logged in?       if (isset($_SESSION["loginUsername"]))          $this->setVariable("LOGIN_STATUS",                      "You are currently logged in as {$_SESSION["loginUsername"]}");       else          $this->setVariable("LOGIN_STATUS",                      "You are currently not logged in");    }    // Output the buttons for a winestore page    function showButtons($buttons)    {       $this->setCurrentBlock("buttons");       // If the cart has contents, offer the opportunity to view the cart       // or empty the cart.       if (isset($_SESSION["order_no"]))       {          if ($buttons & B_EMPTY_CART)          {             $this->setCurrentBlock("form");             $this->setVariable("ACTION", S_EMPTYCART);             $this->setVariable("NAME", "empty");             $this->setVariable("VALUE", "Empty Cart");             $this->parseCurrentBlock("form");          }          if ($buttons & B_SHOW_CART)          {             $this->setCurrentBlock("form");             $this->setVariable("ACTION", S_SHOWCART);             $this->setVariable("NAME", "view");             $this->setVariable("VALUE", "View Cart");             $this->parseCurrentBlock("form");          }          // Must be logged in and have items in cart          if (($buttons & B_PURCHASE) &&              isset($_SESSION["loginUsername"]) &&              isset($_SESSION["order_no"]))          {             $this->setCurrentBlock("form");             $this->setVariable("ACTION", S_ORDER_1);             $this->setVariable("NAME", "purchase");             $this->setVariable("VALUE", "Make Purchase");             $this->parseCurrentBlock("form");          }       }       if ($buttons & B_SEARCH)       {          $this->setCurrentBlock("form");          $this->setVariable("ACTION", S_SEARCHFORM);          $this->setVariable("NAME", "search");          $this->setVariable("VALUE", "Search Wines");          $this->parseCurrentBlock("form");       }       if ($buttons & B_HOME)       {          $this->setCurrentBlock("form");          $this->setVariable("ACTION", S_MAIN);          $this->setVariable("NAME", "home");          $this->setVariable("VALUE", "Main Page");          $this->parseCurrentBlock("form");       }       if ($buttons & B_DETAILS)       {          $this->setCurrentBlock("form");          $this->setVariable("ACTION", S_DETAILS);          if (isset($_SESSION["loginUsername"]))          {             $this->setVariable("NAME", "account");             $this->setVariable("VALUE", "Change Details");          }          else          {             $this->setVariable("NAME", "account");             $this->setVariable("VALUE", "Become a Member");          }          $this->parseCurrentBlock("form");       }       if ($buttons & B_LOGINLOGOUT)       {          $this->setCurrentBlock("form");          if (isset($_SESSION["loginUsername"]))          {             $this->setVariable("ACTION", S_LOGOUT);             $this->setVariable("NAME", "logout");             $this->setVariable("VALUE", "Logout");          }          else          {             $this->setVariable("ACTION", S_LOGIN);             $this->setVariable("NAME", "login");             $this->setVariable("VALUE", "Login");          }          $this->parseCurrentBlock("form");       }       if (($buttons & B_PASSWORD) && isset($_SESSION["loginUsername"]))       {          $this->setCurrentBlock("form");          $this->setVariable("ACTION", S_PASSWORD);          $this->setVariable("NAME", "password");          $this->setVariable("VALUE", "Change Password");          $this->parseCurrentBlock("form");       }       $this->setCurrentBlock("buttons");       $this->parseCurrentBlock("buttons");    } } ?>

The skeleton of templates/winestore.tpl shown in Example 16-1 and the methods in Example 16-2 provide the features in the template. The winestoreTemplate::showLogin( ) method populates the LOGIN_STATUS placeholder in the template and informs the user whether they're logged in. The winestoreTemplate::showMessage( ) method populates the INFO_MESSAGE placeholder with any messages that have been set in the $_SESSION["message"] session variable; this is used throughout the winestore for displaying errors. The winestoreTemplate::showButtons( ) and winestoreTemplate::showCart( ) methods show optional buttons at the bottom of the page and an optional cart icon at the top, depending on the parameters that are passed to the winestoreTemplate::showWinestore( ) method. The winestoreTemplate::showWinestore( ) method itself outputs the template.

The winestoreTemplate::showWinestore( ) method takes two parameters: whether or not to show the cart icon, and what buttons to display at the base of the page. The list of possible buttons is defined in the includes/winestore.inc file that's listed later in this chapter:

// Button definitions define("B_EMPTY_CART", 1); define("B_SHOW_CART", 2); define("B_UPDATE_CART", 4); define("B_PURCHASE", 8); define("B_SEARCH", 16); define("B_HOME", 32); define("B_DETAILS", 64); define("B_LOGINLOGOUT", 128); define("B_PASSWORD", 256); define("B_ALL", 511);

16.3.2.2 The buttons and the button parameter

Let's take a detour for a moment and explain the technique used for displaying buttons. If you don't need the details, skip to the next section.

To create a page that shows the cart, and the search and home buttons, the method winestoreTemplate::showWinestore( ) can be called as follows:

$template->showWinestore(SHOW_ALL, B_SEARCH | B_HOME);

In turn, the winestoreTemplate::showWinestore( ) method calls winestoreTemplate::showButtons( ) and passes through the B_SEARCH | B_HOME value.

Several buttons are selected by a logical OR between them using the | operator. The option B_ALL means all buttons, and it can be combined with the AND operator & and NOT operator ~ to unselect one or more buttons. For example, everything but the purchase button can be shown with:

$template->showWinestore(SHOW_ALL, B_ALL & ~B_PURCHASE);

The button parameter passing implements a useful feature: it allows you to pass through several options without having several parameters. It's the same technique used in other parts of PHP such as, for example, the method of setting the error reporting level with the error_reporting( ) library function.

The selection of buttons works as follows. When you OR button values, you get a unique number that is equal to the sum of the values. For example, consider the first three button settings B_EMPTY_CART which has a value of 1, B_SHOW_CART which has a value of 2, and B_UPDATE_CART which has a value of 4. If you evaluate the expression B_EMPTY_CART | B_UPDATE_CART you get 5; there's no other combination of buttons that can give you that value. Similarly, if you OR all three values you get 7, and there's no other combination that arrives at that value.

Notice that the button values are all power of 2. The first button is 1, the second is 2, the third is 4, the fourth is 8, the fifth 16, and so on. This is what guarantees there's only one combination of buttons that can lead to each possible summed value. Notice also that B_ALL is 511, which is the same value you'll obtain if you OR (or sum) together all of the other button values.

To test if a button is set, a code fragment such as the following is used in the method winestoreTemplate::showButtons( ):

if ($buttons & B_EMPTY_CART) {

The $buttons variable contains the value for the button setting that has been passed to winestoreTemplate::showWinestore( ). The result of the & operation in the if statement is only true if the value in $buttons was created using the value for B_EMPTY_CART. For example, suppose the $buttons value is 5. Because B_EMPTY_CART is 1, the if expression evaluates as true because 4+1=5 and there's no other way to arrive a value of 5. In contrast, if we AND B_SHOW_CART which has a value of 2 and the $buttons value of 5, the expression evaluates as false because 2 isn't part of the sum 4+1=5. (If you want to understand the detail of how this works, you need to be familiar with binary arithmetic.)

The winestoreTemplate::showButtons( ) method outputs the buttons as separate forms in the HTML page. For example, the code for the empty cart in our previous example outputs the following:

<td>   <form action="/wda2-winestore/cart/emptycart.php" method="GET">     <input type="submit" name="empty" value="Empty Cart">   </form> </td>

When the user clicks on the button, the form submits, and the script that's requested is the cart/emptycart.php script. The action attribute of the form element is set in the code to the constant S_EMPTYCART. As we show later, this is a constant that's defined in the includes/winestore.inc file and its value is cart/emptycart.php.

16.3.3 The winestoreFormTemplate Class

The winestoreFormTemplate class is an extension of the winestoreTemplate class with the specific purpose of displaying a data entry form that supports pre-filled inputs, error reporting, and several different types of widget. Unlike the winestoreTemplate class, the body of the page inserted into the placeholder PAGE_BODY is always the templates/details.tpl template shown in Example 16-3.

Example 16-3. The templates/details.tpl template for displaying a form
<!-- BEGIN inputform --> <form method="{METHOD}" action="{S_VALIDATE}"> <h1>{FORMHEADING}</h1> <b>{INSTRUCTIONS}  Fields shown in <font color="red">red</font>    are mandatory.</b> <p> <table> <col span="1" align="right"> <!-- BEGIN widget --> <!-- BEGIN select -->    <tr>       <td><font color="red">{SELECTTEXT}</font></td>       <td><select name="{SELECTNAME}"> <!-- BEGIN option -->       <option{SELECTED} value="{OPTIONVALUE}">{OPTIONTEXT} <!-- END option -->       </select></td>    </tr> <!-- END select --> <!-- BEGIN mandatoryinput -->    <tr>       <td><font color="red">{MINPUTTEXT}</font>       </td>       <td> <!-- BEGIN mandatoryerror -->       <font color="red">{MERRORTEXT}</font><br> <!-- END mandatoryerror -->          <input type="text" name="{MINPUTNAME}"            value="{MINPUTVALUE}" size={MINPUTSIZE}>       </td>   </tr> <!-- END mandatoryinput --> <!-- BEGIN optionalinput -->    <tr>       <td>{OINPUTTEXT}       </td>       <td> <!-- BEGIN optionalerror -->       <font color="red">{OERRORTEXT}</font><br> <!-- END optionalerror -->          <input type="text" name="{OINPUTNAME}"            value="{OINPUTVALUE}" size={OINPUTSIZE}>       </td>   </tr> <!-- END optionalinput --> <!-- BEGIN passwordinput -->    <tr>       <td><font color="red">{PINPUTTEXT}</font>       </td>       <td> <!-- BEGIN passworderror -->       <font color="red">{PERRORTEXT}</font><br> <!-- END passworderror -->          <input type="password" name="{PINPUTNAME}"            value="{PINPUTVALUE}" size={PINPUTSIZE}>       </td>   </tr> <!-- END passwordinput --> <!-- END widget --> <tr>    <td><input type="submit" value="Submit"></td> </tr> </table> </form> <!-- END inputform -->

Consider an example that uses this class. Suppose we want to create a form with a widget for the user to enter their password; again, this is just an example to illustrate how to use the class, and it isn't part of the online winestore application. Here's a short code fragment that's stored in the file passwordform.php that does the whole job:

<?php require_once "../includes/template.inc"; set_error_handler("customHandler"); // Takes form heading, instructions, action,  // session array storing previously-entered values, // and session array storing error messages // as parameters $template = new winestoreFormTemplate("Enter Password",                 "Please enter your password.",                 "check.php, "variables", "errors"); // Create the widget $template->passwordWidget("password", "Password:", 8); // Add buttons and messages, and show the page $template->showWinestore(NO_CART, B_HOME); ?>

The output of the code fragment is shown in Figure 16-4 in a Mozilla browser.

Figure 16-4. Output of the winestoreFormTemplate class example shown in a Mozilla browser
figs/wda2_1604.gif


16.3.3.1 How the class works

The code for the class is shown in Example 16-4. It's stored in the file includes/template.inc , along with the winestoreTemplate class from Example 16-2. The constructor takes several parameters including headings and instructions, the target action script when the form is submitted, two session array names that store data and error messages from previously failed validation attempts, and optional parameters to set the form method and page title.

Example 16-4. The second half of the includes/template.inc file that shows the winestoreFormTemplate class
<?php // An extension of winestoreTemplate for pages that contain a form class winestoreFormTemplate extends winestoreTemplate {    // The formVars array associated with this page of widgets    var $formVars = null;    // The errors array associated with this page of widgets    var $formErrors = null;    // Class constructor    // Parameters:    // (1) Heading in <h1> above the <form>    // (2) Instructions in <b> above the <form>    // (3) <form action=""> value    // (4) formVars $_SESSION array name for storing widget values    // (5) formErrors $_SESSION array name for storing widget errors    // (6) [optional] form method type    // (7) [optional] <title> for the page    function winestoreFormTemplate($formHeading, $instructions,                                   $action, $formVars, $formErrors,                                   $method = "POST", $pageTitle = P_TITLE)    {       $this->template = $this->winestoreTemplate(T_DETAILS, $pageTitle);       // Set up the <form> headings and target       $this->setVariable("FORMHEADING", $formHeading);       $this->setVariable("INSTRUCTIONS", $instructions);       $this->setVariable("S_VALIDATE", $action);       $this->setVariable("METHOD", $method);       // Save formVars and formErrors       $this->formVars = $formVars;       $this->formErrors = $formErrors;    }    // Produces a mandatory <form> widget    // Parameters are:    // (1) The HTML widget name and matching table attribute name    // (2) The text to show next to the widget    // (3) The size of the widget    function mandatoryWidget($name, $text, $size)    {       // Are there any errors to show for this widget?       // If so, show them above the widget       if (isset($_SESSION["{$this->formErrors}"]["{$name}"]))       {          $this->setCurrentBlock("mandatoryerror");          $this->setVariable("MERRORTEXT",            $_SESSION["{$this->formErrors}"]["{$name}"]);          $this->parseCurrentBlock("mandatoryerror");       }       // Setup the widget       $this->setCurrentBlock("mandatoryinput");       $this->setVariable("MINPUTNAME", "{$name}");       if (isset($_SESSION["{$this->formVars}"]["{$name}"]))          $this->setVariable("MINPUTVALUE",            $_SESSION["{$this->formVars}"]["{$name}"]);       $this->setVariable("MINPUTTEXT", "{$text}");       $this->setVariable("MINPUTSIZE", $size);       $this->parseCurrentBlock("mandatoryinput");       $this->setCurrentBlock("widget");       $this->parseCurrentBlock("widget");    }    // Produces an optional <form> widget    // Parameters are:    // (1) The HTML widget name and matching table attribute name    // (2) The text to show next to the widget    // (3) The size of the widget    function optionalWidget($name, $text, $size)    {       // Are there any errors to show for this widget?       // If so, show them above the widget       if (isset($_SESSION["{$this->formErrors}"]["{$name}"]))       {          $this->setCurrentBlock("optionalerror");          $this->setVariable("OERRORTEXT",            $_SESSION["{$this->formErrors}"]["{$name}"]);          $this->parseCurrentBlock("optionalerror");       }       // Setup the widget       $this->setCurrentBlock("optionalinput");       $this->setVariable("OINPUTNAME", "{$name}");       if (isset($_SESSION["{$this->formVars}"]["{$name}"]))          $this->setVariable("OINPUTVALUE",            $_SESSION["{$this->formVars}"]["{$name}"]);       $this->setVariable("OINPUTTEXT", "{$text}");       $this->setVariable("OINPUTSIZE", $size);       $this->parseCurrentBlock("optionalinput");       $this->setCurrentBlock("widget");       $this->parseCurrentBlock("widget");    }    // Produces a password <form> widget    // Parameters are:    // (1) The HTML widget name and matching table attribute name    // (2) The text to show next to the widget    // (3) The size of the widget    function passwordWidget($name, $text, $size)    {       // Are there any errors to show for this widget?       // If so, show them above the widget       if (isset($_SESSION["{$this->formErrors}"]["{$name}"]))       {          $this->setCurrentBlock("passworderror");          $this->setVariable("PERRORTEXT",            $_SESSION["{$this->formErrors}"]["{$name}"]);          $this->parseCurrentBlock("passworderror");       }       // Setup the widget       $this->setCurrentBlock("passwordinput");       $this->setVariable("PINPUTNAME", "{$name}");       if (isset($_SESSION["{$this->formVars}"]["{$name}"]))          $this->setVariable("PINPUTVALUE",            $_SESSION["{$this->formVars}"]["{$name}"]);       $this->setVariable("PINPUTTEXT", "{$text}");       $this->setVariable("PINPUTSIZE", $size);       $this->parseCurrentBlock("passwordinput");       $this->setCurrentBlock("widget");       $this->parseCurrentBlock("widget");    }    // Produces a <select> <form> widget.    // Unlike others, this doesn't support error display    // Parameters are:    // (1) The table attribute that fills the <option> value. Also used as    //     the <select> name    // (2) The text to show next to the widget    // (3) The table attribute that is displayed with the <option>    // (3) The PEAR DB Result to get the data from    function selectWidget($name, $text, $optiontext, $data)    {       while ($row = $data->fetchRow(DB_FETCHMODE_ASSOC))       {          $this->setCurrentBlock("option");          $this->setVariable("OPTIONTEXT", $row["{$optiontext}"]);          $this->setVariable("OPTIONVALUE", $row["{$name}"]);          if (isset($_SESSION["{$this->formVars}"]["{$name}"]) &&              $_SESSION["{$this->formVars}"]["{$name}"] == $row["{$name}"])             $this->setVariable("SELECTED", " selected");          $this->parseCurrentBlock("option");       }       $this->setCurrentBlock("select");       $this->setVariable("SELECTNAME", "{$name}");       $this->setVariable("SELECTTEXT", "{$text}");       $this->parseCurrentBlock("select");       $this->setCurrentBlock("widget");       $this->parseCurrentBlock("widget");    } } ?>

The code can produce as many widget blocks using the template as are required, and each block can contain one of the four data entry widgets that are supported. The four template fragments that create form widgets each have a similar structure and are used to create mandatoryinput widgets (input elements of type="text" with red-colored labels), optionalinput widgets (with normal, black text), passwordinput widgets (which are shown with red-colored labels because they're always mandatory), and selectinput drop-down select lists. With the exception of the select lists, the widgets can display error messages in a red font. All of the widgets can display previously-entered data.

For example, a mandatoryinput widget is created with the following template fragment:

<!-- BEGIN mandatoryinput -->    <tr>       <td><font color="red">{MINPUTTEXT}</font>       </td>       <td> <!-- BEGIN mandatoryerror -->       <font color="red">{MERRORTEXT}</font><br> <!-- END mandatoryerror -->          <input type="text" name="{MINPUTNAME}"            value="{MINPUTVALUE}" size={MINPUTSIZE}>       </td>   </tr> <!-- END mandatoryinput -->

The code in the winestoreFormTemplate::mandatoryWidget( ) method populates the template fragment by setting the text to display into the placeholder MINPUTTEXT, the name of the input into MINPUTNAME, and the size of the input into MINPUTSIZE. These three values are passed as parameters. We've prefixed the mandatory widget placeholders with an M to indicate they're associated with a mandatory widget; the password widget placeholders are prefixed with a P, and optional ones with an O.

The value shown in the widget and any error message are displayed from session arrays that are created during the validation process; this is the same approach as advocated in Chapter 10. If the session variable that stores previously-entered data isn't empty, the previously-entered data is shown in the MINPUTVALUE placeholder. If an error has occurred in previous validation, the session array variable that stores error messages is used to display an error in MERRORTEXT.

To show how these session arrays might be populated during validation, let's continue our previous example of the simple password page; again, this script isn't part of the winestore, it's just used as a simple illustration in this section. Consider the script check.php that's requested when the form is submitted:

<?php // Register and clear an error array - just in case! $_SESSION["errors"] = array( ); // Set up a session array for the POST variables $_SESSION["variables"] = array( ); // Validate password if ($_POST["password"] == "password") {   $_SESSION["errors"]["password"] = "Password too obvious!";   $_SESSION["variables"]["password"] = $_POST["password"]; } // Now the script has finished the validation, // check if there were any errors if (count($_SESSION["errors"]) > 0) {     // There are errors.  Relocate back to the password form     header("Location: passwordform.php");     exit; } // Everything is ok...  // Empty the session arrays unset($_SESSION["errors"]); unset($_SESSION["variables"]); // go to receipt header("Location: ok.php"); ?>

The script is straightforward: it creates two session arrays to store form values and possible error messages. If an error occurs, the value in the widget is stored in one session array and an error message in the other, and the name of the input widget is used as the element name in both arrays. These two session array names are then supplied as parameters to the winestoreFormTemplate::winestoreFormTemplate( ) constructor method.

The $_SESSION["error"] array has data in it only if an error occurs, so its size is checked to see if an error has occurred. If so, the script redirects to the form (where the session data is displayed). If not, the session arrays are emptied and the script redirects to the receipt page.

We've omitted untainting the data to keep the example short. We do this properly in the winestore scripts.

16.3.4 Database Parameters

The database parameters are stored in their own include file includes/db.inc . This file has the same content as the db.inc file explained in Chapter 6:

<?php    $hostname = "127.0.0.1";    $databasename = "winestore";    $username = "fred";    $password = "shhh"; ?>

The $username and $password must be modified to match your local settings as explained in the installation instructions in Appendix A to Appendix C. These parameters are used to create the data source name (DSN) used to connect to the database server with PEAR DB. The $dsn global variable is defined in the includes/winestore.inc file discussed later in this chapter.

16.3.5 Validation

The validation functions are stored in the file includes/validate.inc , which is shown in Example 16-5. Almost all of these functions are discussed in Chapter 9, and the remainder are simple combinations of the fundamental techniques that are described there.

Example 16-5. The includes/validate.inc functions for form validation
<?php // General-purpose validation functions // Test if a mandatory field is empty function checkMandatory($field, $errorString, $errors, $formVars) {    if (!isset($_SESSION["{$formVars}"]["{$field}"]) ||        empty($_SESSION["{$formVars}"]["{$field}"]))    {       $_SESSION["{$errors}"]["{$field}"] =          "The {$errorString} field cannot be blank.";       return false;    }    return true; } // Test if a field is less than a min or greater than a max length function checkMinAndMaxLength($field, $minlength, $maxlength,                          $errorString, $errors, $formVars) {    if (isset($_SESSION["{$formVars}"]["{$field}"]) &&       (strlen($_SESSION["{$formVars}"]["{$field}"]) < $minlength ||        strlen($_SESSION["{$formVars}"]["{$field}"]) > $maxlength))    {       $_SESSION["{$errors}"]["{$field}"] =          "The {$errorString} field must be greater than or equal to" .          "{$minlength} and less than or equal to {$maxlength} " .          "characters in length.";       return false;    }    return true; } // Simple zipcode validator -- there's a better one in Chapter 9! function checkZipcode($field, $errorString, $errors, $formVars) {    if (isset($_SESSION["{$formVars}"]["{$field}"]) &&        !ereg("^([0-9]{4,5})$", $_SESSION["{$formVars}"]["{$field}"]))    {       $_SESSION["{$errors}"]["{$field}"] =          "The zipcode must be 4 or 5 digits in length";       return false;    }    return true; } // Check a phone number function checkPhone($field, $errorString, $errors, $formVars) {    $validPhoneExpr = "^([0-9]{2,3}[ ]?)?[0-9]{4}[ ]?[0-9]{4}$";    if (isset($_SESSION["{$formVars}"]["{$field}"]) &&        !ereg($validPhoneExpr, $_SESSION["{$formVars}"]["{$field}"]))    {       $_SESSION["{$errors}"]["{$field}"] =          "The {$field} field must be 8 digits in length, " .          "with an optional 2 or 3 digit area code";       return false;    }    return true; } // Check a birth date and that the user is 18+ years function checkDateAndAdult($field, $errorString, $errors, $formVars) {    if (!ereg("^([0-9]{2})/([0-9]{2})/([0-9]{4})$",              $_SESSION["{$formVars}"]["{$field}"], $parts))    {       $_SESSION["{$errors}"]["{$field}"] =          "The date of birth is not a valid date " .          "in the format DD/MM/YYYY";       return false;    }    if (!checkdate($parts[2],$parts[1],$parts[3]))    {       $_SESSION["{$errors}"]["{$field}"] =          "The date of birth is invalid. Please " .          "check that the month is between 1 and 12, " .          "and the day is valid for that month.";       return false;    }    if (intval($parts[3]) < 1902 ||        intval($parts[3]) > intval(date("Y")))    {       $_SESSION["{$errors}"]["{$field}"] =          "You must be alive to use this service.";       return false;    }    $dob = mktime(0, 0, 0, $parts[2], $parts[1], $parts[3]);    // Check whether the user is 18 years old    // See Chapter 9 for an MS Windows version    if ((float)$dob > (float)strtotime("-18years"))    {       $_SESSION["{$errors}"]["{$field}"] =          "You must be 18+ years of age to use this service";       return false;    }    return true; } // Check an email address function emailCheck($field, $errorString, $errors, $formVars) {    // Check syntax    $validEmailExpr =  "^[0-9a-z~!#$%&_-]([.]?[0-9a-z~!#$%&_-])*" .                       "@[0-9a-z~!#$%&_-]([.]?[0-9a-z~!#$%&_-])*$";    if (!eregi($validEmailExpr, $_SESSION["{$formVars}"]["{$field}"]))    {       $_SESSION["{$errors}"]["{$field}"] =          "The email must be in the name@domain format.";       return false;    }    // See Chapter 7 for an MS Windows version    if (function_exists("getmxrr") &&        function_exists("gethostbyname"))    {      // Extract the domain of the email address      $maildomain =         substr(strstr($_SESSION["{$formVars}"]["{$field}"], '@'), 1);      if (!(getmxrr($maildomain, $temp) ||            gethostbyname($maildomain) != $maildomain))      {        $_SESSION["{$errors}"]["{$field}"] =           "The email domain does not exist.";        return false;      }    }    return true; } // Check a credit card using Luhn's algorithm function checkCard($field, $errors, $formVars) {   if (!ereg("^[0-9 ]*$", $_SESSION["{$formVars}"]["{$field}"]))   {     $_SESSION["{$errors}"]["{$field}"] =       "Card number must contain only digits and spaces.";     return false;   }   // Remove spaces   $_SESSION["{$formVars}"]["{$field}"] = ereg_replace('[ ]', '', $_ SESSION["{$formVars}"]["{$field}"]);   // Check first four digits   $firstFour = intval(substr($_SESSION["{$formVars}"]["{$field}"], 0, 4));   $type = "";   $length = 0;   if ($firstFour >= 8000 && $firstFour <= 8999)   {     // Try: 8000 0000 0000 1001     $type = "SurchargeCard";     $length = 16;   }   if (empty($type))   {     $_SESSION["{$errors}"]["{$field}"] =       "Please check your card details.";     return false;   }   if (strlen($_SESSION["{$formVars}"]["{$field}"]) != $length)   {     $_SESSION["{$errors}"]["{$field}"] =       "Card number must contain {$length} digits.";     return false;   }   $check = 0;  // Add up every 2nd digit, beginning at the right end   for($x=$length-1;$x>=0;$x-=2)     $check += intval(substr($_SESSION["{$formVars}"]["{$field}"], $x, 1));   // Add up every 2nd digit doubled, beginning at the right end - 1.   // Subtract 9 where doubled value is greater than 10   for($x=$length-2;$x>=0;$x-=2)   {     $double = intval(substr($_SESSION["{$formVars}"]["{$field}"], $x, 1)) * 2;     if ($double >= 10)       $check += $double - 9;     else       $check += $double;   }   // Is $check not a multiple of 10?   if ($check % 10 != 0)   {     $_SESSION["{$errors}"]["{$field}"] =       "Credit card invalid. Please check number.";     return false;   }   return true; } // Check a credit card expiry date function checkExpiry($field, $errors, $formVars) {    if (!ereg("^([0-9]{2})/([0-9]{2})$",              $_SESSION["{$formVars}"]["{$field}"], $parts))    {       $_SESSION["{$errors}"]["{$field}"] =          "The expiry date is not a valid date " .          "in the format MM/YY";       return false;    }    // Check the month    if (!is_numeric($parts[1]) ||        intval($parts[1]) < 1 ||        intval($parts[1]) > 12)    {       $_SESSION["{$errors}"]["{$field}"] =          "The month is invalid.";       return false;    }    // Check the date    if (!is_numeric($parts[2]) ||        // Year has passed?        intval($parts[2]) < intval(date("y")) ||         // This year, but the month has passed?        (intval($parts[2]) == intval(date("y")) &&         intval($parts[1]) < intval(date("n"))) ||        // More than 10 years in the future?        intval($parts[2]) > (intval(date("y")) + 10))    {       $_SESSION["{$errors}"]["{$field}"] =          "The date is invalid.";       return false;    }        return true; }    ?>

16.3.6 Custom Error Handler

Example 16-6 shows our implementation of a custom error handler that's used with the winestore. The code is almost identical to that presented in Chapter 12, with three exceptions: first, you can choose the error file that's used for logging; second, you can set whether to log events to a file or screen; and, third, when the error requires that the application is stopped, the script cleans up session variables to log the user out and empty their cart. The logging is managed by three constants that are set in the includes/winestore.inc that we describe later.

Example 16-6. The includes/customHandler.inc error handler
<?php require_once "winestore.inc"; // Back trace an error function backTrace($context) {    $calls = "";    // Get a backtrace of the function calls    $trace = debug_backtrace( );    $calls = "\nBacktrace:";    // Start at 2 -- ignore this function (0) and the customHandler( ) (1)    for($x=2; $x < count($trace); $x++)    {      $callNo = $x - 2;      $calls .= "\n  {$callNo}: {$trace[$x]["function"]} ";      $calls .= "(line {$trace[$x]["line"]} in {$trace[$x]["file"]})";    }    $calls .= "\nVariables in {$trace[2]["function"]} ( ):";    // Use the $context to get variable information for the function    // with the error    foreach($context as $name => $value)    {      if (!empty($value))        $calls .= "\n  {$name} is {$value}";      else        $calls .= "\n  {$name} is NULL";    }    return ($calls); } // Custom error handler function -- reproduced from Chapter 12 function customHandler($number, $string, $file, $line, $context) {   $error = "";   switch ($number)   {      case E_USER_ERROR:        $error .= "\nERROR on line {$line} in {$file}.\n";        $stop = true;        break;      case E_WARNING:      case E_USER_WARNING:        $error .= "\nWARNING on line {$line} in {$file}.\n";        $stop = false;        break;      case E_NOTICE:      case E_USER_NOTICE:        $error .= "\nNOTICE on line {$line} in {$file}.\n";        $stop = false;        break;      default:        $error .= "UNHANDLED ERROR on line {$line} in {$file}.\n";        $stop = false;   }   $error .= "Error: \"{$string}\" (error #{$number}).";   $error .= backTrace($context);   $error .= "\nClient IP: {$_SERVER["REMOTE_ADDR"]}";   $prepend = "\n[PHP Error " . date("YmdHis") . "]";   $error = ereg_replace("\n", $prepend, $error);   if (SCREEN_ERRORS)      print "<pre>{$error}</pre>";   if (FILE_ERRORS)      error_log($error, 3, ERROR_FILE);   if ($stop == true)   {     if (isset($_SESSION["order_no"]))        unset($_SESSION["order_no"]);    if (isset($_SESSION["loginUsername"]))       unset($_SESSION["loginUsername"]);    if (isset($_SESSION["loginIP"]))       unset($_SESSION["loginIP"]);     die( );   } } ?>

16.3.7 General-Purpose Functions

The includes/winestore.inc file shown in Example 16-7 stores the common constants and functions used throughout the winestore application. It is included in almost all scripts and in the other include files.

The constants at the beginning of the script are designed so that you can flexibly change the directories in which the scripts or templates are stored, and rename the files, without making changes in more than one place. Our aim is to make it easy for you to extract one or more modules that you want to reuse in another application.

If you've followed our installation instructions in Appendix A to Appendix C, you should find that the main directory settings don't need to be changed. Directory setting are those prefixed with D_ at the beginning of the file. The installation path D_INSTALL_PATH is set to the same value as the Apache DocumentRoot setting and D_WEB_PATH is set to the directory wda2-winestore that's been created in the document root. All other directory, script, and template locations are prefixed by the D_INSTALL_PATH and D_WEB_PATH definitions. The list of locations of scripts can be found earlier in this chapter in Table 16-1.

The buttons definitions (which are prefixed with B_) are described earlier in our template discussion. The cart icon settings NO_CART and SHOW_CART control whether the cart icon is hidden or shown, respectively. The constant SEARCH_ROWS defines how many search results are presented per page, to support functionality discussed in Chapter 20. The constants ERROR_FILE, FILE_ERRORS, and SCREEN_ERRORS control how errors are logged as discussed in the previous section. The string $dsn is the data source name that's used to create a PEAR DB connection in all winestore scripts.

Example 16-7. The includes/winestore.inc file
<?php require_once 'db.inc'; require_once 'customHandler.inc'; // Choose or adjust one of the following // NOTE: do not add a trailing slash // define("D_INSTALL_PATH", "c:/progra~1/easyph~1/www");  define("D_INSTALL_PATH", "/Library/WebServer/Documents"); //define("D_INSTALL_PATH", "/usr/local/apache2/htdocs"); // Paths -- for these, add trailing slash define("D_WEB_PATH", "/wda2-winestore/"); define("D_CART", D_WEB_PATH . "cart/"); define("D_CARTIMAGES", D_CART . "images/"); define("D_CUSTOMER", D_WEB_PATH . "customer/"); define("D_AUTH", D_WEB_PATH . "auth/"); define("D_ORDER", D_WEB_PATH . "order/"); define("D_SEARCH", D_WEB_PATH . "search/"); define("D_TEMPLATES", D_INSTALL_PATH . D_WEB_PATH . "templates/"); // No slash at beginning // S - scripts define("S_MAIN", D_WEB_PATH . "index.php"); define("S_ADDTOCART", D_CART . "addtocart.php"); define("S_EMPTYCART", D_CART . "emptycart.php"); define("S_SHOWCART", D_CART . "showcart.php"); define("S_UPDATECART", D_CART . "updatecart.php"); define("S_ORDER_1", D_ORDER . "order-step1.php"); define("S_ORDER_2", D_ORDER . "order-step2.php"); define("S_ORDER_3", D_ORDER . "order-step3.php"); define("S_ORDER_4", D_ORDER . "order-step4.php"); define("S_ORDERRECEIPT", D_ORDER . "receipt.php"); define("S_SEARCH", D_SEARCH . "search.php"); define("S_SEARCHFORM", D_SEARCH . "searchform.php"); define("S_DETAILS", D_CUSTOMER . "details.php"); define("S_VALIDATE", D_CUSTOMER . "validate.php"); define("S_CUSTRECEIPT", D_CUSTOMER . "receipt.php"); define("S_LOGOUT", D_AUTH . "logout.php"); define("S_LOGIN", D_AUTH . "login.php"); define("S_LOGINCHECK", D_AUTH . "logincheck.php"); define("S_PASSWORD", D_AUTH . "password.php"); define("S_CHANGEPASSWORD", D_AUTH . "changepassword.php"); define("S_PASSWORDRECEIPT", D_AUTH . "receipt.php"); // T - templates define("T_SKELETON", "winestore.tpl"); define("T_HOME", "index.tpl"); define("T_SHOWCART", "showcart.tpl"); define("T_DETAILS", "details.tpl"); define("T_CUSTRECEIPT", "custreceipt.tpl"); define("T_LOGIN", "login.tpl"); define("T_PASSWORD", "password.tpl"); define("T_PASSWORDRECEIPT", "passwordreceipt.tpl"); define("T_EMAIL", "email.tpl"); define("T_ORDERRECEIPT", "orderreceipt.tpl"); define("T_SEARCH", "search.tpl"); define("T_SOURCE", "source.tpl"); // I - images define("I_CART_OFF", D_CARTIMAGES . "cart_off.jpg"); define("I_CART_ON", D_CARTIMAGES . "cart_on.jpg"); // B - Buttons define("B_EMPTY_CART", 1); define("B_SHOW_CART", 2); define("B_UPDATE_CART", 4); define("B_PURCHASE", 8); define("B_SEARCH", 16); define("B_HOME", 32); define("B_DETAILS", 64); define("B_LOGINLOGOUT", 128); define("B_PASSWORD", 256); define("B_ALL", 511); // Show the cart icon? define("NO_CART", 1); define("SHOW_ALL", 2); // Search rows per page define("SEARCH_ROWS", 12); // Custom error handler controls // File to log errors to define("ERROR_FILE", "/tmp/php_error_log"); // Save errors to a file? define("FILE_ERRORS", true); // Show errors to the screen? define("SCREEN_ERRORS", true); // The database connection string $dsn = "mysql://{$username}:{$password}@{$hostname}/{$databasename}"; // Untaint user data function pearclean($array, $index, $maxlength, $connection) {   if (isset($array["{$index}"]))   {     $input = trim(substr($array["{$index}"], 0, $maxlength));     $input = mysql_real_escape_string($input);     return ($input);   }   return NULL; } // Find the cust_id using the user_name function getCust_id($user_name, $connection = null) {    global $dsn;    // If a connection parameter is not passed, then    // use our own connection    if (!isset($connection))    {       $connection = DB::connect($dsn, false);       if (DB::isError($connection))         trigger_error($connection->getMessage( ), E_USER_ERROR);    }    $query = "SELECT cust_id FROM users WHERE              user_name = '{$user_name}'";    $result = $connection->query($query);    if (DB::isError($result))       trigger_error($result->getMessage( ), E_USER_ERROR);    $row = $result->fetchRow(DB_FETCHMODE_ASSOC);    return($row["cust_id"]); } // Show the user the details of one wine in their cart function showWine($wineId, $connection = null) {    global $dsn;    $wineQuery = "SELECT year, winery_name, wine_name                  FROM winery, wine                  WHERE wine.winery_id = winery.winery_id                  AND wine.wine_id = {$wineId}";    // If a connection parameter is not passed, then    // use our own connection to avoid any locking problems    if (!isset($connection))    {       $connection = DB::connect($dsn, false);       if (DB::isError($connection))         trigger_error($connection->getMessage( ), E_USER_ERROR);    }    $result = $connection->query($wineQuery);    if (DB::isError($result))       trigger_error($result->getMessage( ), E_USER_ERROR);    $row = $result->fetchRow(DB_FETCHMODE_ASSOC);    // Print the wine details    $output = "{$row["year"]} {$row["winery_name"]} {$row["wine_name"]}";    // Print the varieties for this wine    $output .= showVarieties($connection, $wineId);    return $output; } // Find the varieties for a wineID function showVarieties($connection, $wineID) {    // Find the varieties of the current wine,    // and order them by id    $query = "SELECT gv.variety              FROM grape_variety gv, wine_variety wv, wine w              WHERE w.wine_id = wv.wine_id              AND wv.variety_id = gv.variety_id              AND w.wine_id = {$wineID}              ORDER BY wv.id";    $result = $connection->query($query);    if (DB::isError($result))       trigger_error($result->getMessage( ), E_USER_ERROR);    $varieties = "";    // Retrieve and print the varieties    while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC))       $varieties .= " {$row["variety"]}";    return $varieties; } // Find the cheapest bottle price for a wineID function showPricing($connection, $wineID) {    // Find the price of the cheapest inventory    $query = "SELECT min(cost) FROM inventory              WHERE wine_id = {$wineID}";    $result = $connection->query($query);    if (DB::isError($result))       trigger_error($result->getMessage( ), E_USER_ERROR);    // Retrieve the oldest price    $row = $result->fetchRow(DB_FETCHMODE_ASSOC);    $price = $row["min(cost)"];    return $price; } // Lookup the country_id in the countries lookup table // and return the country name function showCountry($country_id, $connection) {   $query = "SELECT country FROM countries WHERE             country_id = {$country_id}";   $result = $connection->query($query);   if (DB::isError($result))      trigger_error($result->getMessage( ), E_USER_ERROR);   $countryRow = $result->fetchRow(DB_FETCHMODE_ASSOC);   return($countryRow["country"]); } // Lookup the title in the titles lookup table // and return the title string function showTitle($title_id, $connection) {   $query = "SELECT title FROM titles WHERE             title_id = {$title_id}";   $result = $connection->query($query);   if (DB::isError($result))      trigger_error($result->getMessage( ), E_USER_ERROR);   $titleRow = $result->fetchRow(DB_FETCHMODE_ASSOC);   return($titleRow["title"]); } ?>

The includes/winestore.inc file also stores several functions that are used throughout the winestore. The function pearclean( ) untaints user data and is a variation of the mysqlclean( ) function discussed in Chapter 6. The function getCust_id( ) takes as a parameter the username of a logged-in user (and an optional open database connection), and returns their unique cust_id identifier by querying the users table; this function is used throughout the winestore in, for example, customer detail changes and the ordering process.

The getCust_id( ) function (and the showWine( ) function that we discuss next) has an optional database connection for three reasons. First, if you've already got a connection handle, there's usually no reason to open another and so it's useful to be able to pass it as a parameter. Second, if you don't have a connection open, the function can open one itself. Third, if you do have a connection open but you don't want to use it because you've locked some tables, then if don't pass a parameter to the function it'll open a new, distinct connection and avoid any locking problems.

The functions showWine( ) , showVarieties( ) , and showPricing( ) are utilities to retrieve details about a specific wine that's identified by a wine_id. The showWine( ) function returns a string that includes the vintage, wine name, and winery for the wine. The showVarieties( ) function returns a string that lists the grape varieties of the wine, and the showPricing( ) function returns the cheapest price in the inventory for the wine.

The showCountry( ) and showTitle( ) functions are utilities that return a string from a lookup table. The showCountry( ) function takes as a parameter a country identifier and returns a country name from the countries table. The showTitle( ) function does the same thing for titles from the titles table. Both are used in the customer processes discussed in the next chapter.



Web Database Application with PHP and MySQL
Web Database Applications with PHP & MySQL, 2nd Edition
ISBN: 0596005431
EAN: 2147483647
Year: 2003
Pages: 176

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net