Web Database Applications With Php And Mysql (2nd Edition) [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Web Database Applications With Php And Mysql (2nd Edition) [Electronic resources] - نسخه متنی

David Lane, Hugh E. Williams

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید








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=" onMouseOut="cart.src=''"
onMouseOver="cart.src=''">
<img src=" vspace=0 border=0
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="http://www.w3.org/Icons/valid-html401" height="31" width="88"
align="right" border="0" ></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

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


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.




/ 176