After an order has been processed, the winestore application confirms the shipping of the wines through both an email and an HTML receipt. The order/order-step4.php script sends the user an email. In turn, the order/order-step4.php script redirects to the order/receipt.php script which produces the HTML receipt.
The HTML receipt can be visited again at a later time by bookmarking the URL. As it carries out no database updates, it doesn't suffer from the reload problem described in Chapter 6. The receipt functionality is separated into two scripts so that returning to the HTML receipt doesn't cause an additional email receipt to be sent to the customer.
Example 19-4 shows the order/order-step4.php script that sends an email receipt to the user. The function send_confirmation_email( ) creates the email body, destination address, subject, and additional headers of an email message, and then sends that email message. To do so, it uses the template that's shown in Example 19-5 and stored in the file templates/email.tpl.
<?php // This script sends the user a confirmation email for their order // and then redirects to an HTML receipt version // By default, this script uses PEAR's Mail package. // To use PHP's internal mail( ) function instead, change "true" to "false" // in the following line define("USE_PEAR", true); require_once "DB.php"; require_once "HTML/Template/ITX.php"; require_once "../includes/winestore.inc"; require_once "../includes/authenticate.inc"; // Use the PEAR Mail package if USE_PEAR is defined if (USE_PEAR == true) require_once "Mail.php"; set_error_handler("customHandler"); // Send the user an email that summarises their purchase function send_confirmation_email($custID, $orderID, $connection) { $template = new HTML_Template_ITX(D_TEMPLATES); $template->loadTemplatefile(T_EMAIL, true, true); // Find customer information $query = "SELECT * FROM customer, users WHERE customer.cust_id = {$custID} AND users.cust_id = customer.cust_id"; $result = $connection->query($query); if (DB::isError($result)) trigger_error($result->getMessage( ), E_USER_ERROR); $row = $result->fetchRow(DB_FETCHMODE_ASSOC); // Start by setting up the "To:" email address $to = "{$row["firstname"]} {$row["surname"]} <{$row["user_name"]}>"; // Now setup all the customer fields $template->setVariable("TITLE", showTitle($row["title_id"], $connection)); $template->setVariable("SURNAME", $row["surname"]); $template->setVariable("CUST_ID", $custID); $template->setVariable("ORDER_ID", $orderID); $template->setVariable("FIRSTNAME", $row["firstname"]); $template->setVariable("INITIAL", $row["initial"]); $template->setVariable("ADDRESS", $row["address"]); $template->setVariable("CITY", $row["city"]); $template->setVariable("STATE", $row["state"]); $template->setVariable("COUNTRY", showCountry($row["country_id"], $connection)); $template->setVariable("ZIPCODE", $row["zipcode"]); $orderTotalPrice = 0; // list the particulars of each item in the order $query = "SELECT i.qty, w.wine_name, i.price, w.wine_id, w.year, wi.winery_name FROM items i, wine w, winery wi WHERE i.cust_id = {$custID} AND i.order_id = {$orderID} AND i.wine_id = w.wine_id AND w.winery_id = wi.winery_id ORDER BY item_id"; $result = $connection->query($query); if (DB::isError($result)) trigger_error($result->getMessage( ), E_USER_ERROR); // Add each item to the email while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) { // Work out the cost of this line item $itemsPrice = $row["qty"] * $row["price"]; $orderTotalPrice += $itemsPrice; $wineDetail = showWine($row["wine_id"], $connection); $template->setCurrentBlock("row"); $template->setVariable("QTY", str_pad($row["qty"],9)); $template->setVariable("WINE", str_pad(substr($wineDetail, 0, 53), 55)); $template->setVariable("PRICE", str_pad(sprintf("$%4.2f" , $row["price"]), 11)); $template->setVariable("TOTAL", str_pad(sprintf("$%4.2f", $itemsPrice), 12)); $template->parseCurrentBlock("row"); } $template->setCurrentBlock("items"); $template->setVariable("ORDER_TOTAL", sprintf("$%4.2f\n", $orderTotalPrice)); $template->parseCurrentBlock("items"); $template->setCurrentBlock( ); $template->parseCurrentBlock( ); $out = $template->get( ); if (USE_PEAR == false) { // -------------------------------------------- // The internal PHP mail( ) function is used only if USE_PEAR is false // Now, setup the "Subject:" line $subject = "Hugh and Dave's Online Wines: Order Confirmation"; // And, last (before we build the email), set up some mail headers $headers = "From: Hugh and Dave's Online Wines " . "<help@webdatabasebook.com>\r\n"; $headers .= "X-Sender: <help@webdatabasebook.com>\r\n"; $headers .= "X-Mailer: PHP\r\n"; $headers .= "Return-Path: <help@webdatabasebook.com>\r\n"; // Send the email! mail($to, $subject, $out, $headers); // -------------------------------------------- } else { // -------------------------------------------- // Use the PEAR Mail package and SMTP since USE_PEAR is true // Now, setup the "Subject:" line $headers["Subject"] = "Hugh and Dave's Online Wines: Order Confirmation"; // And, last (before we build the email), set up some mail headers $headers["From"] = "Hugh and Dave's Online Wines " . "<help@webdatabasebook.com>"; $headers["X-Sender"] = "<help@webdatabasebook.com>"; $headers["X-Mailer"] = "PHP"; $headers["Return-Path"] = "<help@webdatabasebook.com>"; $smtpMail =& Mail::factory("smtp"); $smtpMail->send($to, $headers, $out); // -------------------------------------------- } } // ---------- session_start( ); // Connect to a authenticated session sessionAuthenticate(S_SHOWCART); // Check the correct parameters have been passed if (!isset($_GET["cust_id"]) || !isset($_GET["order_id"])) { $_SESSION["message"] = "Incorrect parameters to order-step4.php"; header("Location: " . S_SHOWCART); exit; } // Check this customer matches the $cust_id $connection = DB::connect($dsn, true); if (DB::isError($connection)) trigger_error($connection->getMessage( ), E_USER_ERROR); $cust_id = pearclean($_GET, "cust_id", 5, $connection); $order_id = pearclean($_GET, "order_id", 5, $connection); $real_cust_id = getCust_id($_SESSION["loginUsername"]); if ($cust_id != $real_cust_id) $_SESSION["message"] = "You can only view your own receipts!"; header("Location: " . S_HOME); exit; } // Send the user a confirmation email send_confirmation_email($cust_id, $order_id, $connection); // Redirect to a receipt page (this can't be the receipt page, // since the reload problem would cause extra emails). header("Location: " . S_ORDERRECEIPT . "?cust_id={$cust_id}&order_id={$order_id}"); ?>
Dear {TITLE} {SURNAME}, Thank you for placing an order at Hugh and Dave's Online Wines. This email is best viewed in a fixed-width font such as Courier. Your fictional order (reference # {CUST_ID} - {ORDER_ID}) has been dispatched. Please quote this number in any correspondence. If it existed, the order would have been shipped to: {TITLE} {FIRSTNAME} {INITIAL} {SURNAME} {ADDRESS} {CITY} {STATE} {COUNTRY} {ZIPCODE} We have billed your fictional SurchargeCard credit card. Your fictional order contains: <!-- BEGIN items --> Quantity Wine Unit Price Total <!-- BEGIN row --> {QTY}{WINE}{PRICE}{TOTAL} <!-- END row --> Total of this order: {ORDER_TOTAL} <!-- END items --> Thank you for shopping at Hugh and Dave's Online Wines! Kind Regards, Hugh and Dave, http://www.webdatabasebook.com/
The destination $to address is created using the firstname, surname, and user_name (which is the email address) attributes of the customer that are retrieved from the customer and users tables. For example, with a surname value of Smith, a firstname of Michael, and a user_name of mike@webdatabasebook.com, it has the following format:
Michael Smith <mike@webdatabasebook.com>
The additional email headers are static and always have the following format:
From: "Hugh and Dave's Online Wines" <help@webdatabasebook.com> X-Sender: <help@webdatabasebook.com> X-Mailer: PHP Return-Path: <help@webdatabasebook.com>
The subject of the email is always:
Hugh and Dave's Online Wines: Order Confirmation
The body of the message is created by querying and retrieving the details of the customer, orders, and items tables and setting variables in the template. The following is an example of the body of a confirmation email:
Dear Dr Smith, Thank you for placing an order at Hugh and Dave's Online Wines. This email is best viewed in a fixed-width font such as Courier. Your fictional order (reference # 651 - 12) has been dispatched. Please quote this number in any correspondence. If it existed, the order would be shipped to: Dr Michael Smith 12 Hotham St. Collingwood Victoria 3066 Australia We have billed your fictional SurchargeCard credit card. Your fictional order contains: Quantity Wine Unit Price Total 12 1999 Smith's Chardonnay $22.25 $267.00 Total: $267.00 Thank you for shopping at Hugh and Dave's Online Wines! Kind Regards, Hugh and Dave, http://www.webdatabasebook.com/
In our template examples so far, the HTML_Template_IT::show( ) method is used to output data to the browser. However, in this case, we don't want to output the data to the browser and so we use the HTML_Template_IT::get( ) method instead. This method returns the template as a string after all placeholder replacements have been made, and we assign the return value to the variable $out. The variable is used as a parameter to the mailing method. The HTML_Template_IT::get( ) method is described in Chapter 7.
The email itself is sent using the PEAR Mail package with the following fragment:
$smtpMail =& Mail::factory("smtp"); $smtpMail->send($to, $headers, $out);
The Mail class supports three different ways of sending email, and we use a Simple Mail Transfer Protocol (SMTP) server in this example to send the email. The class is discussed in the next section.
The body of the script also checks that the user is logged in, that the correct parameters of a cust_id and an order_id have been provided, and that the user viewing the receipt is the owner of the receipt. If any of the checks fail, the user is redirected so that an error message can be displayed.
The PEAR Mail package is used to send the order confirmation receipt by connecting to an SMTP server. PEAR Mail works well, but requires that you install the Mail package by following the package installation instructions in Chapter 7. If you don't want to use the PEAR Mail package, then change the define( ) at beginning of the file order/order-step4.php shown in Example 19-4 to:
define("USE_PEAR", false);
When PEAR Mail isn't used, the PHP library function mail( ) is used instead. Without additional configuration, mail( ) only works on Unix platforms.
After installation, using PEAR's Mail package requires only two simple steps: first, create an instance of a mailer backend; and, second, send the email. There are three choices of backends:
This is most generic approach, as it doesn't rely on having specific software installed on the web server, and is portable across all PHP platforms. The disadvantage is that it requires an SMTP server, and these sometimes require username and password credentials, or can be configured in a non-standard way.
Without additional configuration, this only works on Unix platforms. Under Microsoft Windows, it needs to be configured to use an SMTP-based approach.
This only works on Unix platforms. (It also supports other mail sending programs that have a wrapper that makes them look like sendmail).
We create the backend in our code using:
$smtpMail =& Mail::factory("smtp");
However, the Mail::factory( ) method for the SMTP mailer also supports an optional second array parameter $params that can contain the following associative elements:
The host name of the SMTP server. It's set to localhost by default.
The port number of the SMTP server. Set to 25 by default.
Whether to use SMTP authentication. Set to false by default.
The username to use for authentication. No default value.
The password to use for authentication. No default value.
In general, the defaults work for most Unix installations. If you don't have a mailer installed on your server or you're using Microsoft Windows, then try setting $params["host"] to your ISP's mail server. For example, if you were a customer of bigpond.com, you would use:
$params["host"] = "mail.bigpond.com"; $smtpMail =& Mail::factory("smtp", $params);
If you want to use PHP's internal mail( ) function with PEAR Mail, then use:
$smtpMail =& Mail::factory("mail"); $smtpMail->send($to, $headers, $out);
It has no additional parameters.
If you want to use the Unix sendmail backend, then use:
$smtpMail =& Mail::factory("sendmail"); $smtpMail->send($to, $headers, $out);
The factory( ) method for the sendmail mailer also supports the optional second array parameter $params that can contain the following associative elements:
The path to the sendmail program. By default it's /usr/bin/sendmail.
Additional arguments to the sendmail program. There's none by default.
Example 19-6 shows the order/receipt.php script that confirms the shipping of an order using HTML. The script has an identical structure to order/order-step4.php and executes the same queries. The only difference is that the script outputs HTML rather than creating a template that's emailed to the customer. The template that's used with the script is shown in Example 19-7; as with other templates in the winestore, the winestoreTemplate class that's explained in Chapter 16 is used to display the page.
<?php // This script shows the user an HTML receipt require_once "DB.php"; require_once "../includes/template.inc"; require_once "../includes/winestore.inc"; require_once "../includes/authenticate.inc"; set_error_handler("customHandler"); function show_HTML_receipt($custID, $orderID, $connection) { $template = new winestoreTemplate(T_ORDERRECEIPT); // Find customer information $query = "SELECT * FROM customer, users WHERE customer.cust_id = {$custID} AND users.cust_id = customer.cust_id"; $result = $connection->query($query); if (DB::isError($result)) trigger_error($result->getMessage( ), E_USER_ERROR); $row = $result->fetchRow(DB_FETCHMODE_ASSOC); // Now setup all the customer fields $template->setVariable("CUSTTITLE", showTitle($row["title_id"], $connection)); $template->setVariable("SURNAME", $row["surname"]); $template->setVariable("CUST_ID", $custID); $template->setVariable("ORDER_ID", $orderID); $template->setVariable("FIRSTNAME", $row["firstname"]); $template->setVariable("INITIAL", $row["initial"]); $template->setVariable("ADDRESS", $row["address"]); $template->setVariable("CITY", $row["city"]); $template->setVariable("STATE", $row["state"]); $template->setVariable("COUNTRY", showCountry($row["country_id"], $connection)); $template->setVariable("ZIPCODE", $row["zipcode"]); $orderTotalPrice = 0; // list the particulars of each item in the order $query = "SELECT i.qty, w.wine_name, i.price, w.wine_id, w.year, wi.winery_name FROM items i, wine w, winery wi WHERE i.cust_id = {$custID} AND i.order_id = {$orderID} AND i.wine_id = w.wine_id AND w.winery_id = wi.winery_id ORDER BY item_id"; $result = $connection->query($query); if (DB::isError($result)) trigger_error($result->getMessage( ), E_USER_ERROR); // Add each item to the page while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) { // Work out the cost of this line item $itemsPrice = $row["qty"] * $row["price"]; $orderTotalPrice += $itemsPrice; $wineDetail = showWine($row["wine_id"], $connection); $template->setCurrentBlock("row"); $template->setVariable("QTY", $row["qty"]); $template->setVariable("WINE", $wineDetail); $template->setVariable("PRICE", sprintf("$%4.2f" , $row["price"]), 11); $template->setVariable("TOTAL", sprintf("$%4.2f", $itemsPrice)); $template->parseCurrentBlock("row"); } $template->setCurrentBlock("items"); $template->setVariable("ORDER_TOTAL", sprintf("$%4.2f\n", $orderTotalPrice)); $template->parseCurrentBlock("items"); $template->setCurrentBlock( ); $template->showWinestore(NO_CART, B_HOME); } // ---------- session_start( ); // Connect to a authenticated session sessionAuthenticate(S_SHOWCART); // Check the correct parameters have been passed if (!isset($_GET["cust_id"]) || !isset($_GET["order_id"])) { $_SESSION["message"] = "Incorrect parameters to order-step4.php"; header("Location: " . S_SHOWCART); exit; } // Check this customer matches the $cust_id $connection = DB::connect($dsn, true); if (DB::isError($connection)) trigger_error($connection->getMessage( ), E_USER_ERROR); $cust_id = pearclean($_GET, "cust_id", 5, $connection); $order_id = pearclean($_GET, "order_id", 5, $connection); $real_cust_id = getCust_id($_SESSION["loginUsername"]); if ($cust_id != $real_cust_id) { $_SESSION["message"] = "You can only view your own receipts!"; header("Location: " . S_HOME); exit; } // Show the confirmation HTML page show_HTML_receipt($cust_id, $order_id, $connection); ?>
<h1>Your order (reference # {CUST_ID} - {ORDER_ID}) has been dispatched</h1> Thank you {CUSTTITLE} {SURNAME}, your order has been completed and dispatched. Your order reference number is {CUST_ID} - {ORDER_ID}. Please quote this number in any correspondence. <br> <p>If it existed, the order would have been shipped to: <br><b> {CUSTTITLE} {FIRSTNAME} {INITIAL} {SURNAME} <br> {ADDRESS} <br>{CITY} {STATE} <br>{COUNTRY} {ZIPCODE} </b> <br> <br> <p>We have billed your fictional credit card. <!-- BEGIN items --> <table border=0 width=70% cellpadding=0 cellspacing=5> <tr> <td><b>Quantity</b></td> <td><b>Wine</b></td> <td align="right"><b>Unit Price</b></td> <td align="right"><b>Total</b></td> </tr> <!-- BEGIN row --> <tr> <td>{QTY}</td> <td>{WINE}</td> <td align="right">{PRICE}</td> <td align="right">{TOTAL}</td> </tr> <!-- END row --> <tr></tr> <tr> <td colspan=2 align="left"><i><b>Total of this order</b></td> <td></td> <td align="right"><b><i>{ORDER_TOTAL}</b></td> </tr> </table> <!-- END items --> <p><i>An email confirmation has been sent to you. Thank you for shopping at Hugh and Dave's Online Wines.</i>