Приглашаем посетить
Грибоедов (griboedov.lit-info.ru)

Web Shop

#!/usr/local/bin/perl

############################################################################
#                                                                          #
# WebShop                           Version 1.5                            #
# Written by Matthew Wright         mattw@worldwidemart.com                #
# Created 7/12/96                   Last Modified 5/2/97                   #
#                                                                          #
# Copyright 1997 Craig Patchett & Matthew Wright.  All Rights Reserved.    #
# This program is part of The CGI/Perl Cookbook from John Wiley & Sons.    #
# License to use this program or install it on a server (in original or    #
# modified form) is granted only to those who have purchased a copy of The #
# CGI/Perl Cookbook. (This notice must remain as part of the source code.) #
#                                                                          #
############################################################################


############################################################################
# Define configuration constants                                           #
############################################################################

# $CONFIG_DIR is the full path to the root directory under which 
# configuration files must be stored. It should end with a directory
# delimiter

$CONFIG_DIR = '/home/webshop/';

# $WEBSHOP_CGI_URL is the URL of this CGI script.

$WEBSHOP_CGI_URL = 'http://www.domain.com/cgi-bin/webshop.cgi';

# $WEB_SERVER is the host name of your web server.  If the name of your web 
# server is host.xxx, set this to 'host.xxx'.

$WEB_SERVER = 'domain.com';

# $SMTP server is the server name of your SMTP server.  For example, if 
# your service provider is host.net, try setting this to host.net or
# smtp.host.net

$SMTP_SERVER = 'smtp.domain.com';

# This is the directory for lock files to be stored.  It is recomended that
# this be set to /tmp if you are on a Unix system or some place outside of
# the web server space.

$LOCK_DIR = '/tmp/';

# This is the maximum amount of time the CGI script should wait before it
# returns an error that it could not lock the file.

$MAX_WAIT = 5;

# $REQUIRE_DIR is the directory in which all of your required files are
# placed.  On most systems, if ou leave the required files in the same
# directory as the CGI script, you can leave this variable blank.  
# Otherwise, if you move the required files to another directory, specify
# the full path here.

$REQUIRE_DIR = 'require';


############################################################################
# Get Required subroutines which need to be included.                      #
############################################################################

# Push the $REQUIRED_DIR onto the @INC array for include file directories.

push(@INC, $REQUIRE_DIR) if $REQUIRE_DIR;

# Require necessary routines for this script to run.  Look for the under 
# eval so as to catch any errors and properly display them to the user.

close(STDERR);
eval <<end_eval;
require 'parsform.pl';
require 'chkemail.pl';
require 'ipconvrt.pl';
require 'locksubs.pl';
require 'sendmail.pl';
require 'template.pl';
require 'ccvalid.pl';
end_eval

# If there were any problems requiring the files, report error

if ($@) {
   &ws_error($@);
}


############################################################################
# Read in the information from the QUERY_STRING, parse the info, and       #
# get the user's configuration file.                                       #
############################################################################

if ($ENV{'QUERY_STRING'}) {

    # Set the REQUEST_METHOD and parse the data.
    
    $ENV{'REQUEST_METHOD'} = "GET";
    &parse_form;

    # Require the configuration file or error.
    
    if ($FORM{'config'} !~ /^$CONFIG_DIR/) { &ws_error('config_file_dir') }
    else { eval "require '$FORM{'config'}'"; if ($@) { &ws_error($@) } }
}
else {
    &ws_error('config_file');
}


############################################################################
# Check for any expired orders in the database file.                       #
############################################################################

# Lock the database file so we can open it and write to it.

if (&lock($DATABASE, $LOCK_DIR, $MAX_WAIT)) {
    &ws_error($Error_Message);
}

# Open the database, read it in, lock it and close it.

if (!(-e $DATABASE)) { open(DATABASE, ">$DATABASE"); close DATABASE }
open(DATABASE, $DATABASE) || &ws_error('open->database', $DATABASE);
@database = <DATABASE>;
close(DATABASE);

# For each record, if the current time is greater than the expire time, set
# expired flag to '1' and don't add it to the @NOT_EXPIRED array.

$current_time = time;
foreach $data_line (@database) {
    ($uid, $expire_time, $order_info) = split(/\|\|/, $data_line);
    if ($current_time < $expire_time) {
        push(@NOT_EXPIRED, $data_line);
    }
    else {
        $expired_flag = 1;
    }
}

# If the expired flag is set

if ($expired_flag == 1) {

    # Open the database and write any records which have not expired.
    
    open(DATABASE, ">$DATABASE") || &ws_error('write->database', $DATABASE);
    foreach $data_line (@NOT_EXPIRED) {
        print DATABASE $data_line;
    }
    close(DATABASE);
}
&unlock($DATABASE, $LOCK_DIR);


############################################################################
# If a UID was attached to the QUERY_STRING, set the $uid variable.  Also, #
# find their information in the database file.                             #
############################################################################

# If uid is available, set it, otherwise create a new user and display the
# intro page

if ($FORM{'uid'}) {
    $uid = $FORM{'uid'};
}
else {
    &new_user;
}

# Open the database and find the user.  Set entry_flag to 1 once we do.

if (&lock($DATABASE, $LOCK_DIR, $MAX_WAIT)) {
    &ws_error($Error_Message);
}

open(DATABASE, $DATABASE) || &ws_error('open->database', $DATABASE);
while(<DATABASE>) {
    if (/^$uid\|\|(.*)/) {
        ($expire_time, $order_info) = split(/\|\|/, $1);
        $entry_flag = 1;
        last;
    }
}
close(DATABASE);
&unlock($DATABASE, $LOCK_DIR);

# If uid wasn't found in database, it has probably expired.  Create 
# new user. 
 
if (!$entry_flag) {
    &new_user;
}


############################################################################
# If the form command is to link to a page, then we get the product info   #
# and parse the output page.                                               #
############################################################################

if ($FORM{'command'} =~ /^link--(.*)/) {
    $product = $1;

    # Get product information.
    
    &get_product_info;

    # Update the shopper's expiration time.
    
    &update_expire_time;

    # Parse the web page if the product's web page exists.
    
    if ($PRODUCT_PATH{$product}) {
        &webshop_parse($PRODUCT_PATH{$product}, 'open->product_page', *STDOUT);
    }

    # Otherwise, return an error.
    
    else {
        &ws_error('product_page_not_defined');
    }
}


############################################################################
# Otherwise, if the command is to purchase an item, add it to the cart and #
# return a review sheet.                                                   #
############################################################################

elsif ($FORM{'command'} =~ /^purchase--(.*)/) {
    $new_product = $1;
    $new_amount = $FORM{'amount'};

    # Get product info.
    
    &get_product_info;

    # If no amount is available or amount is less than one, they must have 
    # forgotten to fill in quantity.
    
    if ($new_amount < 1) {
    
        # Update the shopper's expiration time.
        
        &update_expire_time;

        # Parse the product's web page, since they didn't buy anything.
        
        &webshop_parse($PRODUCT_PATH{$new_product}, 'open->product_page', *STDOUT);
        exit;
    }

    #########################################
    # Add the product to shopping cart.     #
    #########################################

    # Lock the database file so we can open it and write to it.
    
    if (&lock($DATABASE, $LOCK_DIR, $MAX_WAIT)) {
        &ws_error($Error_Message);
    }

    # Open the database, read it in and lock it.
    
    open(DATABASE, $DATABASE) || &ws_error('open->database', $DATABASE);
    @database = <DATABASE>;
    close(DATABASE);

    # Not that we still have lock, open database for writing.
    
    open(DATABASE, ">$DATABASE") || &ws_error('write->database', $DATABASE);

    # Foreach line of database information...
    
    foreach $data_line (@database) {

        # If the database line is for the current UID, create a new 
        # expiration date, add the product to their cart and re-print the 
        # line.
        
        if ($data_line =~ /^$uid\|\|(.*)/) {
            ($expire_time, $order_info) = split(/\|\|/, $1);
            @purchased_products = split(/,/, $order_info);

            # Get new expiration date.
            
            $new_expire_time = time;
            $new_expire_time += (60 * $MAX_MINUTES);

            # Create new purchase information.
            
            foreach $purchased_product (@purchased_products) {
                ($product, $amount) = split(/->/, $purchased_product);
                if ($new_product eq $product) {
                    $amount += $new_amount;
                    $amount_flag = 1;
                }
                if ($new_order_info) {
                    $new_order_info .= ",$product->$amount";
                }
                else {
                    $new_order_info = "$product->$amount";
                }
            }

            # Add the new product to the new purchase information.
            
            if (!$amount_flag && $order_info) {
                $new_order_info = "$order_info,$new_product->$new_amount";
            }
            elsif (!$amount_flag) {
                $new_order_info = "$new_product->$new_amount";
            }

            print DATABASE "$uid||$new_expire_time||$new_order_info\n";
        }

        # Otherwise, just print the database line.
        
        else {
            print DATABASE $data_line;
        }
    }

    # Unlock and close the database.
    
    close(DATABASE);
    &unlock($DATABASE, $LOCK_DIR);

    # Compile the purchases.
    
    &compile_purchase;

    # Show them the review page, with new products added.
    
    &webshop_parse($REVIEW_TEMPLATE, 'open->review_template', *STDOUT);
}

############################################################################
# Otherwise, if the command is to show review page parse the review        #
# template.                                                                # 
############################################################################

elsif ($FORM{'command'} =~ /^review/) {

    # Get product info
    
    &get_product_info;

    # Compile the purchases.
    
    &compile_purchase;

    # Updated Expire Time.
    
    &update_expire_time;

    # Parse Review Template.
    
    &webshop_parse($REVIEW_TEMPLATE, 'open->review_template', *STDOUT);
}


############################################################################
# Otherwise, if command is equal to invoice, parse the invoice template.   # 
############################################################################

elsif ($FORM{'command'} =~ /^invoice/) {

    # Get product info.
    
    &get_product_info;

    # Compile the Purchases
    
    &compile_purchase('INVOICE');

    # Updated Expire Time
    
    &update_expire_time;

    # Parse Invoice Template
    
    &webshop_parse($INVOICE_TEMPLATE, 'open->invoice_template', *STDOUT);
}


############################################################################
# Otherwise, if they ask to empty cart, do so.                             #
############################################################################

elsif ($FORM{'command'} =~ /^empty/) {

    # Lock the database file so we can open it and write to it.
    
    if (&lock($DATABASE, $LOCK_DIR, $MAX_WAIT)) {
        &ws_error($Error_Message);
    }

    # Open the database and read it in.
    
    open(DATABASE, $DATABASE) || &ws_error('open->database', $DATABASE);
    @database = <DATABASE>;
    close(DATABASE);

    # While we still have the lock, open the database for writing.
    
    open(DATABASE, ">$DATABASE") || &ws_error('write->database', $DATABASE);

    # Scan through records in database, erase shopping items for record
    # with current UID
    
    foreach $data_line (@database) {

        if ($data_line =~ /^$uid\|\|/) {
            $expire_time = (time + (60 * $MAX_MINUTES));
            print DATABASE "$uid||$expire_time||\n";
        }
        else {
            print DATABASE $data_line;
        }
    }

    # Unlock and close the database.
    
    close(DATABASE);
    &unlock($DATABASE, $LOCK_DIR);
    
    # Send them back to the intro page
    
    print "Location: $WEBSHOP_CGI_URL?config=$FORM{'config'}&uid=$uid\n\n";
}


############################################################################
# Otherwise, if they ask to checkout, print out the final invoice, send    #
# email messages, remove the user's shopping info and finish up everything #
############################################################################

elsif ($FORM{'command'} =~ /^checkout/) {

    # Get Product Info.
    
    &get_product_info;

    # If there is a credit card available, check it.
    
    if ($FORM{'cc_verify'} eq 'YES') {
        if (&cc_validate($FORM{'cc_type'}, $FORM{'cc'}, 
                         $FORM{'$cc_exp_date'})) {
            &ws_error($Error_Message);
        }
    }

    # Compile Purchases.  Use FINAL flag which will leave links and 
    # such out.
    
    &compile_purchase('FINAL');

    # Send the email to $ORDER_TO, parsing the email_template.
    
    &send_email($ORDER_SUBJECT, $FORM{'email'}, $ORDER_TO, $CC_ORDER, '', 
                $EMAIL_TEMPLATE);

    # If $REPLY_TEMPLATE exists, send a reply message.
    
    if ($REPLY_TEMPLATE) {
        &send_email($REPLY_SUBJECT, $REPLY_FROM, $FORM{'email'}, '', '', 
                    $REPLY_TEMPLATE);
    }

    # Lock the database file so we can open it and write to it.
    
    if (&lock($DATABASE, $LOCK_DIR, $MAX_WAIT)) {
        &ws_error($Error_Message);
    }

    # Open the database, read it in and lock it.
    
    open(DATABASE, $DATABASE) || &ws_error('open->database', $DATABASE);
    @database = <DATABASE>;
    close(DATABASE);

    # While we still have the lock, open the database for writing.
    
    open(DATABASE, ">$DATABASE") || &ws_error('write->database', $DATABASE);

    # Unless the record is for the current UID, print it back to the 
    # database.  If it does correspond to the current UID, erase it, since 
    # this order is finished.
    
    foreach $data_line (@database) {
        unless ($data_line =~ /^$uid\|\|/) {
            print DATABASE $data_line;
        }
    }
    close(DATABASE);
    &unlock($DATABASE, $LOCK_DIR);

    # Parse the final web page.
    
    &webshop_parse($FINAL_TEMPLATE, 'open->final_template', *STDOUT);
}


############################################################################
# Otherwise, no specific valid command, send out main intro page.          #
############################################################################

else {
    &update_expire_time;
    &webshop_parse($INTRO_HTML, 'open->intro_html', *STDOUT);
}


############################################################################
# This routine creates a user ID and adds it to the database, creating a   #
# new shopper.                                                             #
############################################################################

sub new_user {

    # Create an 8-character UID base on IP address and add current time
    # to ensure uniqueness.
    
    $uid = &ip_convert($ENV{'REMOTE_ADDR'}) . time;

    # Get Expiration Time.
    
    $expire_time = time;
    $expire_time += (60 * $MAX_MINUTES);

    # Lock the database file so we can open it and write to it.
    
    if (&lock($DATABASE, $LOCK_DIR, $MAX_WAIT)) {
        &ws_error($Error_Message);
    }

    # Open the database file for appending.
    
    open(DATABASE, ">>$DATABASE") || &ws_error('write->database', $DATABASE);

    # Print New Database Record.
    
    print DATABASE "$uid||$expire_time||\n";

    # Close and Unlock Database File.
    
    close(DATABASE);
    &unlock($DATABASE, $LOCK_DIR);

    # Parse Intro HTML Template and send it out.
    
    &webshop_parse($INTRO_HTML, 'open->intro_html', *STDOUT);

    exit;
}


############################################################################
# Get product information from the $PRODUCT_INFO file and place it into    #
# associative arrays, for use later in the script.                         #
############################################################################

sub get_product_info {

    # Open the product file.
    
    open(PRODUCTS, $PRODUCT_INFO) 
        || &ws_error('open->product_info', $PRODUCT_INFO);
    while ($record= <PRODUCTS>) {
    
        # Read in product info line by line and create associative arrays        
        # for the product information, based on the product ref_name.
        
        local($ref_name, $name, $filename, $price, $shipping) = split(/:/, $record);
        $PRODUCT_NAME{$ref_name} = $name;
        $CONFIG{'PRODUCT_NAME_' . $ref_name} = $name;
        if ($filename =~ /^[\/\\:]/) {
            $PRODUCT_PATH{$ref_name} = $filename;
        }
        else {
            $PRODUCT_PATH{$ref_name} = "$BASE_PRODUCT_PATH$filename";
        }
        $PRODUCT_COST{$ref_name} = $price;
        $CONFIG{'PRODUCT_PRICE_' . $ref_name} = $price;

        $PRODUCT_SHIPPING{$ref_name} = $shipping;
        $CONFIG{'PRODUCT_SHIPPING_' . $ref_name} = $shipping;

        $PRODUCT_URL{$ref_name} = "$WEBSHOP_CGI_URL?config=$FORM{'config'}&uid=$uid&command=link--$ref_name";
        push(@products, $ref_name);
    }
    close(PRODUCTS);
}


############################################################################
# This subroutine changes a user's expire time.  It is used when the       #
# would not normally be updated, for instance when a page is linkedto, etc #
############################################################################

sub update_expire_time {

    # Lock the database file so we can open it and write to it.
    
    if (&lock($DATABASE, $LOCK_DIR, $MAX_WAIT)) {
        &ws_error($Error_Message);
    }

    # Open and read in the database file
    
    open(DATABASE, $DATABASE) || &ws_error('open->database', $DATABASE);
    @database = <DATABASE>;
    close(DATABASE);

    # While we still have the lock open database for writing.
    
    open(DATABASE, ">$DATABASE") || &ws_error('write->database', $DATABASE);

    # Foreach line in the database, if it matches the current UID, print 
    # out a new expire time.  Otherwise, just print the line back to the 
    # database.
    
    foreach $data_line (@database) {
        if ($data_line =~ /^$uid\|\|\d+\|\|(.*)/) {
            $order_info = $1;
            $new_expire_time = (time + ($MAX_MINUTES * 60));
            print DATABASE "$uid||$new_expire_time||$order_info\n";
        }
        else {
            print DATABASE $data_line;
        }
    }

    # Unlock and close the database.
    
    close(DATABASE);
    &unlock($DATABASE, $LOCK_DIR);
}


############################################################################
# This subroutine compiles a purchase table which can then be used in the  #
# review, invoice, final and e-mail templates.                             #
############################################################################

sub compile_purchase {

    # Localize variables and get input.
    
    local($flag) = $_[0];
    local(@purchased_products, $product);

    # If this is not an invoice page we are compiling the purchase for 
    # open the database and read in all of the products.
    
    if ($flag ne 'INVOICE' && $flag ne 'FINAL') {
        if (&lock($DATABASE, $LOCK_DIR, $MAX_WAIT)) {
             &ws_error($Error_Message);
        }

        open(DATABASE, $DATABASE) || &ws_error('open->database', $DATABASE);
        while (<DATABASE>) {
            if (/^$uid\|\|\d+\|\|(.*)/) {
                @purchased_products = split(/,/, $1);
                last;
            }
        }
        close(DATABASE);
        &unlock($DATABASE, $LOCK_DIR);
    }
    
    # Otherwise, read in the products from the form field if this is the 
    # INVOICE.
    
    else {
        foreach $product (@products) {
            if ($FORM{$product} >= 1) {
                push(@purchased_products, "$product->$FORM{$product}");
            }
        }
    }

    # If there are purchased products...
    
    if (@purchased_products) {
    
        # If this isn't the purchase table for the final template, print 
        # form information.
        
        if ($flag ne 'FINAL') {
            $CONFIG{'purchase_table'} = "<form method=GET action=\"$WEBSHOP_CGI_URL\">";
            $CONFIG{'purchase_table'} .= "<input type=hidden name=\"uid\" value=\"$uid\">\n";
            $CONFIG{'purchase_table'} .= "<input type=hidden name=\"config\" value=\"$FORM{'config'}\">\n";
            $CONFIG{'purchase_table'} .= "<pre>\n";
        }
        
        # If this is the invoice page, make a hidden variable to send 
        # user to final page if they click submit.
        
        if ($flag eq 'INVOICE') {
            $CONFIG{'purchase_table'} .= "<input type=hidden name=\"command\" value=\"checkout\">\n";
        }

        # Otherwise, if this isn't the FINAL page, send them to the 
        # INVOICE page.
        
        elsif ($flag ne 'FINAL') {
            $CONFIG{'purchase_table'} .= "<input type=hidden name=\"command\" value=\"invoice\">\n";
        }

        # Print the purchase table headers. 
               
        $CONFIG{'purchase_table'} .= "Qty.   Product                          Cost ea.  Tot. Cost\n";
        $CONFIG{'purchase_table'} .= "------ -------------------------------- --------- ----------\n";

        # Add each purchased product to the list.
        
        foreach $purchased_product (@purchased_products) {
            ($product, $amount) = split(/->/, $purchased_product);
            
            # Make sure the amount is a valid integer
            
            if ($amount =~ /[^0-9.]/) { $amount = 0 }
            $amount = int($amount);
            
            if ($flag eq 'INVOICE' || $flag eq 'FINAL') {
                if ($flag eq 'INVOICE') {
                    $CONFIG{'purchase_table'} .= "<input type=hidden name=\"$product\" value=\"$amount\">";
                }
                $CONFIG{'purchase_table'} .= sprintf("%5s  ", $amount);
            }
            else {
                $CONFIG{'purchase_table'} .= "<input type=text name=\"$product\" value=\"$amount\" size=3>   ";
            }

            # If this isn't the final we are creating this table for, 
            # add links to each product's respective page.
            
            if ($flag ne 'FINAL') {
                $CONFIG{'purchase_table'} .= sprintf("<a href=\"%s\">%-32s</a>", $PRODUCT_URL{$product}, $PRODUCT_NAME{$product});
            }
            else {
                $CONFIG{'purchase_table'} .= sprintf("%-32s", $PRODUCT_NAME{$product});
            }

            # Get the product cost and make sure it takes up nine spaces.
            
            $product_cost = "\$$PRODUCT_COST{$product}";
            $CONFIG{'purchase_table'} .= sprintf("%9s ", $product_cost);

            # Figure the entire product cost, with quantity in mind.
            
            $total_product_cost = ($amount * $PRODUCT_COST{$product});
            $total_cost += $total_product_cost;

            $total_product_cost = sprintf("\$%.2f", $total_product_cost);
            $CONFIG{'purchase_table'} .= sprintf("%10s\n", $total_product_cost);

            $total_shipping += ($PRODUCT_SHIPPING{$product} * $amount);
        }
        $CONFIG{'purchase_table'} .= "------ -------------------------------- --------- ----------\n";
        $CONFIG{'purchase_table'} .= "                                       Sub-Total: ";

        # Find the total cost of things.
        
        $subtotal_price = sprintf("\$%.2f", $total_cost);
        $CONFIG{'purchase_table'} .= sprintf("%9s\n\n", $subtotal_price);

        # Check for shipping rules file and figure additional shipping charges 
        # based on purchase total.

        if (-e $SHIPPING_TABLE) {
            open(SHIPPING, $SHIPPING_TABLE) 
              || &ws_error('open->shipping_table', $SHIPPING_TABLE);
            while (<SHIPPING>) {
                chop if /\n$/;
                local($type, $low, $high, $shipping_amount) = split(/:/);
                if ($low <= $total_cost && $high >= $total_cost 
                  && $FORM{'shipping_type'} eq $type) {
                    if ($flag ne 'FINAL') {
                        $CONFIG{'purchase_table'} .= "<input type=hidden name=\"shipping_type\" value=\"$FORM{'shipping_type'}\">";
                    }

                    if ($shipping_amount =~ /^([0-9.])%$/) {
                        $shipping_percent = ($1 / 100);
                        $total_shipping += ($shipping_percent * $total_cost);
                    }
                    else {
                        $total_shipping += $shipping_amount;
                    }
                }
            }
            close(SHIPPING);
        }
        $CONFIG{'purchase_table'} .= "                                        Shipping: ";

        $shipping_price = $total_shipping;
        $total_shipping = sprintf("%.2f", $total_shipping);
        $CONFIG{'purchase_table'} .= sprintf("%9s\n\n", $total_shipping);

        # Calculate any taxes or display a select box.
        
        if ($FORM{'tax_state'}) {
            if (($FORM{'tax_state'} ne 'N/A') && (-e $TAX_TABLE)) {
                open(TAXES, $TAX_TABLE) 
                  || &ws_error('open->tax_table', $TAX_TABLE);
                while ($tax_line = <TAXES>) {
                    if ($tax_line =~ /^$FORM{'tax_state'}:([0-9.]+)/i) {
                        $tax_percent = ($1 / 100);
                        last;
                    }
                }
                close(TAXES);
                if ($tax_percent) {
                    $tax_price = ($total_cost * $tax_percent);
                    $tax_price += 0.005;    
                    $tax_cost = sprintf("%.2f", $tax_price);
                }
                else {
                    $tax_cost = "0.00";
                    $tax_price = 0;
                }
                $tax_cost = sprintf("%9s", $tax_cost);
            }
            else {
                $tax_cost = "     0.00";
                $tax_price = 0;
            }

            if (length($FORM{'tax_state'}) == 2) {
                $CONFIG{'purchase_table'} .= "                                         $FORM{'tax_state'} Tax: $tax_cost\n";
            }
            else {
                $CONFIG{'purchase_table'} .= "                                             Tax: $tax_cost\n";
            }

            if ($flag ne 'FINAL') {
                $CONFIG{'purchase_table'} .= "<input type=hidden name=\"tax_state\" value=\"$FORM{'tax_state'}\">";
            }
        }
        else {
            $CONFIG{'purchase_table'} .= "                             Calculate Taxes For:    <select name=\"tax_state\">\n";
            if (!(-e $TAX_TABLE && open(TAXES, $TAX_TABLE))) {
            	&ws_error('open->tax_table', $TAX_TABLE);
            }
            @TAXES = <TAXES>;
            close(TAXES);
            $CONFIG{'purchase_table'} .= "<option>N/A\n";
            foreach $line (@TAXES) {
                if ($line =~ /(.*)\:/) {
                    $CONFIG{'purchase_table'} .= "<option>$1\n";
                }
            }
            $CONFIG{'purchase_table'} .= "</select>\n\n";
        }
        $CONFIG{'purchase_table'} .= "                                        --------- ----------\n";
        $CONFIG{'purchase_table'} .= "                                           Total: ";

        # Calculate and print total.
        
        $total_cost += ($tax_price + $shipping_price);
        $total_cost = sprintf("\$%.2f", $total_cost);
        $CONFIG{'purchase_table'} .= sprintf("%9s\n", $total_cost);
        
        if ($flag ne 'FINAL') {
            $CONFIG{'purchase_table'} .= "</pre>\n";
        }
    }

    # If there are no products, print a message saying so.
    
    else {
        $CONFIG{'purchase_table'} .= "No Products Placed in Cart Yet!";
    }
}


############################################################################
# This is a special parsing routine, different from those used in the      #
# other CGI scripts in that it also checks for WebShop specific template   #
# markers and commands.                                                    #
############################################################################

sub webshop_parse {

    # Localize variables.  Get the filename, error message and filehandle.
    
    local($filename, $error, *OUT) = @_;

    # Open the filename template.
    
    open(TEMPLATE, $filename) || &ws_error($error, $filename);

    # Print HTML header.
    
    print "Content-type: text/html\n\n";

    # While we are reading in the template file.
    
    while ($template_line = <TEMPLATE>) {

        # If the template line contains a template marker...
        
        while ($template_line =~ 
                   /(<!--(\w+):\s*"?(.*)"?-->(.*)<!--\/\2-->)/) {

            # Get all of the template marker information into variables.  
            # template_piece is the whole template marker, which will 
            # eventually be replaced, template name is the command, template 
            # value is the item upon which to carry out the command and 
            # template option is the text inside of the marker to link or 
            # highlight with, which can optionally contain a BUTTON: flag.
            
            $marker = $1;
            $marker_name = $2;
            $marker_value = $3;
            $marker_option = $4;

            # If they wish to have the replaced link and information in a 
            # button style format, or if it is a purchase command, which
            # must be in a button format, provide that here.
            
            if ($marker_option =~ /^BUTTON:/i 
                    || $marker_name eq 'purchase') {
                $marker_option =~ s/BUTTON://i;
                $marker_replace = "<form method=GET action=\"$WEBSHOP_CGI_URL\">\n";

                # If the command is to purchase an item, offer a quantity 
                # text box
                
                if ($marker_name eq 'purchase') {
                    $marker_replace .= "Quantity: <input type=text name=\"amount\" size=3> ";
                }

                # Put in hidden UID, config and command fields, 
                
                $marker_replace .= "<input type=hidden name=\"config\" value=\"$FORM{'config'}\">\n";
                $marker_replace .= "<input type=hidden name=\"uid\" value=\"$uid\">\n";
                $marker_replace .= "<input type=hidden name=\"command\" value=\"$marker_name";
                if ($marker_value) {
                    $marker_replace .= "--$marker_value";
                }
                $marker_replace .= "\">\n<input type=submit value=\"$marker_option\"></form>";
            }

            # Otherwise, no need to use button format and hidden fields, 
            # just create URL with QUERY_STRING and link to option text
            
            else {
                $marker_replace = "<a href=\"$WEBSHOP_CGI_URL?config=$FORM{'config'}&uid=$uid&command=$marker_name";
                if ($marker_value) {
                    $marker_replace .= "--$marker_value";
                }
                $marker_replace .= "\">$marker_option</a>";
            }

            $marker =~ s/(\W)/\\\1/g;
            $template_line =~ s/$marker/$marker_replace/;
            $changes++;
        }

        $line_copy = '';
        
        # Search for variables in the current line
        
        while ($template_line =~ /<<([^>]+)>>/) {
            
            # Build up the new line with the section of $line prior to the 
            # variable and the value for $var_name (check %Variables,  
            # %CONFIG, %FORM, then %ENV for match)
            
            ++$changes;
            if ($VAR{$1}) { $line_copy .= $` . $VAR{$1} }
            elsif ($CONFIG{$1}) { $line_copy .= $` . $CONFIG{$1} }
            elsif ($FORM{$1}) { $line_copy .= $` . $FORM{$1} }
            elsif ($ENV{$1}) { $line_copy .= $` . $ENV{$1} }
            else {
                --$changes;
                $line_copy .= $`;
            }
                 
            # Change $line to the section of $line after the variable
            
            $template_line = $';
        }
        
        # Set $line according to whether or not any matches were found
        
        $template_line = 
            $line_copy ? $line_copy . $template_line : $template_line;
        
        # Print line depending on presence of 0: and variables existing   
             
        if (($line_copy && $changes) || !$line_copy) {
            $template_line =~ s/^0://;
            print OUT $template_line;
        }
    }
}


############################################################################
# This error routine catches information sent to it throughout the script  #
# and will display and explain any errors which this script may encounter. #
############################################################################

sub ws_error {

    # Localize and retrieve the error message.
    
    local($error, $error_file) = @_;

    # Print out HTML header.
    
    print "Content-type: text/html\n\n";
    if ($error eq 'config_file') {
        &error_header('WebShop Error: Invalid Config File');
        if ($FORM{'config'}) {
            if ($FORM{'config'} !~ /^$CONFIG_DIR/) {
        print <<"END_CONFIG_ERROR";
The configuration file you specified was in an invalid directory. Check the
path and try again.
</body></html>
END_CONFIG_ERROR
            }
            elsif (-e $FORM{'config'}) {
        print <<"END_CONFIG_ERROR";
The configuration file you specified to have included was found on the 
system, however due to permissions on the file, it could not be included 
in the script.  Please make sure you chmod this file to 644.  Use a 
command similar to:<pre>
    chmod 644 $FORM{'config'}
</pre>
</body></html>
END_CONFIG_ERROR
            }
            else {
                print <<"END_CONFIG_ERROR";   
The configuration file you specified to have included was <b>NOT</b> 
found on this system.  Check the path and filename and try again.
</body></html>
END_CONFIG_ERROR
            }
        }
        else {
            print <<"END_CONFIG_ERROR";
No configuration file was appended to the URL for WebShop.cgi.  Please 
try again and make sure your URL contains a config=/path/to/config_file 
appended to the URL as a QUERY_STRING>  Below is an example:<pre>
    http://www.host.xxx/cgi-bin/WebShop.cgi?config=/path/to/config.txt
</pre>
</body></html>
END_CONFIG_ERROR
        }
    }
    
    # If the problem was with opening the file, print out a header for 
    # specific file that failed, then print a generic response telling the 
    # user if the file exists or is just chmoded incorrectly.  If either 
    # one is the problem give ways to fix it.
    
    elsif ($error  =~ /^open->(.*)/) {
        $file_type = $1;
        if ($file_type eq 'database') {
            &error_header('WebShop Error: Could Not Open Database File');
        }
        elsif ($file_type eq 'product_page') {
            &error_header('WebShop Error: Could Not Open Product Page');
        }
        elsif ($file_type eq 'review_template') {
            &error_header('WebShop Error: Could Not Open Review Template');
        }
        elsif ($file_type eq 'invoice_template') {
            &error_header('WebShop Error: Could Not Open Invoice Template');
        }
        elsif ($file_type eq 'final_template') {
            &error_header('WebShop Error: Could Not Open Final Template');
        }
        elsif ($file_type eq 'intro_html') {
            &error_header('WebShop Error: Could Not Open Intro HTML Page');
        }
        elsif ($file_type eq 'product_info') {
            &error_header('WebShop Error: Could Not Open Product Info File');
        }
        elsif ($file_type eq 'tax_table') {
            &error_header('WebShop Error: Could Not Open Tax Rate File');
        }
        elsif ($file_type eq 'shipping_table') {
            &error_header('WebShop Error: Could Not Open Shipping File');
        }

        print "The file WebShop attempted to open: <i>$error_file</i> \n";
        print "could not be opened for reading.<p>\n";

        if (-e $error_file) {
            print "The file does exist, and needs to simply be chmoded.\n";
            print "Perform the following command on your file:<pre>\n";
            if ($file_type eq 'database') {
                print "chmod 777 $error_file\n";
            }
            else {
                print "chmod 744 $error_file\n";
            }
            print "</pre>\n";
        }
        else {
            print "The file could not be found.  Check your variables and\n";
            print "make sure they point to the correct file.\n";
        }
        print "</body></html>\n";
    }
    elsif ($error =~ /^write->(.*)/) {
        &error_header("WebShop Error: Can't Write to Database File");
        print "The database file could not be opened for writing.<p>\n";
        if (-e $DATABASE) {
            print "The file does exist and simply needs to be chmoded so\n";
            print "it can be written to.  Perform the following command on\n";
            print "this file:<pre>\n";
            print "chmod 777 $DATABASE\n";
            print "<\pre>\n";
        }
        else {
            print "The file could not be found.  Check your \$DATABASE\n";
            print "variable in your configuration file and make sure it\n";
            print "exists.\n";
        }
        print "</body></html>\n";
    }
    elsif ($error eq 'product_page_not_defined') {
        &error_header('Product Page Not Defined');
        print "Check your product database and make sure you have defined\n";
        print "a filename for this product.\n";
        print "</body></html>\n";
    }
    else {
        &error_header($error);
        print "</body></html>\n";
    }
    exit;
}


############################################################################
# This creates the header for an HTML page, when supplied with a title.    #
############################################################################

sub error_header {
    local($title) = $_[0];
    print <<HTML_END;
<html>
 <head>
  <title>$title</title>
 </head>
 <body bgcolor=#FFFFFF text=#000000>
  <center>
   <h1>$title</h1>
  </center>
HTML_END
}