Приглашаем посетить
Мандельштам (mandelshtam.lit-info.ru)

Web Store

#!/usr/local/bin/perl

# Name: Web Store
# Authors: Selena Sol and Gunther Birznieks
# Version: 1.0
# Last Modified: 01/09/96
#
# Copyright Info: This application was written by Selena Sol (selena@eff.org, 
#	http://www.eff.org/~erict) and Gunther Birznieks
#	(birzniek@hlsun.redcross.org) having been inspired by countless
#	other Perl authors.  Feel free to copy, cite, reference, sample,
#	borrow, resell or plagiarize the contents.  However, if you don't
#	mind, please let Selena know where it goes so that we can at least
#	watch and take part in the development of the memes. Information
#	wants to be free, support public domain freware.  Donations are
#	appreciated and will be spent on further upgrades and other public
#	domain scripts.

		# First, Perl is told to bypass its own buffer so that the
                # information generated by this script will be sent
		# immediately to the browser.

$| = 1;

		# Then, the http header is sent to the browser.  This is
		# done early for two reasons.  
		# 
		# Firstly, it will be easier to debug the script while
		# making modifications or customizing because we will be
		# able to see exactly what the script is doing.
		# 
		# Secondly, the http header is sent out early so that the
		# browser will not "time out" in case the script takes a
		# long time to complete its work. 

print "Content-type: text/html\n\n";

		# Next we will execute a few subroutines which will define
		# the environment in which the script will operate.
		#
		# First we will require the web_store.setup file so that
		# we will be able to read in global variables.  Notice
		# that in the distribution, we have six setup files by
		# default.  We'll use frames.javascript as out basic
		# example though.
		#
		# Secondary supporting files are also read in using
		# require_supporting_libraries which is used to require
		# the supporting files needed by this script. Notice
		# that we are going to pass the current filename as well
		# as the current line number to the 
		# require_supporting_libraries subroutine.  It will use
		# these values to generate useful error messages in case
		# it is unable to read in the files requested.
		# 
		# Note: Here is where we read in all of the global
		# variables and definitions included in the setup files
		#
		# web_store.setup.* defines many global variables
		# 	for this script relative to the local server and
		#	installation.
		#
		# $sc_cgi_lib_path is the location of cgi-lib.pl which is
		#	used to parse incoming form data.
		#
		# $sc_html_setup_file_path is the location of
		# 	web_store_html_lib.pl which is used to define
		#	various customizable HTML interface headers,
		#	footers and pages.
		#
		# $sc_mail_lib_path is the location of mail-lib.pl which is
		#	used to mail non-encrypted mail to the admin
		#	about usage of the script.
		#

&require_supporting_libraries (__FILE__, __LINE__,
      "./Library/web_store.setup.frames.javascript");

&require_supporting_libraries (__FILE__, __LINE__, 
			      "$sc_cgi_lib_path",
			      "$sc_html_setup_file_path", 
			      "$sc_mail_lib_path");

		# Next we read and parse the incoming form data.
		# read_and_parse_form_data is a very short subroutine
		# which simply uses the ReadParse subroutine in cgi-lib.pl
		# to parse the incoming form data into the associative
		# array, %form_data.

&read_and_parse_form_data;

		# Once we have parsed the incoming form data,
		# we can assign the values of administrative variables to
		# regularized scalars, local to this script.
		#
		# $page will contain the path location of any pages which
		# this script is required to display.  This may be the
		# store frontpage, order form or any number of product or
		# category pages used for store navigation.
		#
		# $search_request is the value of the button used when a
		# customer submits search terms used to generate a dynamic
		# custom product page.
		#
		# $cart_id is the id number of the customer's unique cart
		# containing all of the items they have ordered so far.
		# The specifics of cart generation and maintenance are
		# covered in greater depth in the next section.
		#
		# $sc_cart_path is the actual path of the shopping cart
		# combining both $sc_user_carts_directory_path and
		# $cart_id
		#
		# These three variables are crucial state variables which
		# must be passed as form data from every instance of this
		# script to the next.	
	
$page = $form_data{'page'};
$search_request = $form_data{'search_request_button'};
$cart_id = $form_data{'cart_id'};
$sc_cart_path = "$sc_user_carts_directory_path/$cart_id.cart";

		# Finally we submit the incoming form data to some
		# security checks. error_check_form_data is a subroutine
		# which checks the just-parsed incoming form data to make sure 
		# that the script is only being used to display proper
		# pages (typically .html, .shtml, or .htm).
		#
		# This is an important security precaution. Later in this
		# script, we are going to use a variable called "page" to
		# communicate which page in our store we want to display
		# to the client.
		#
		# The danger is that a client might "fake" a request to
		# the script by editing the page variable in the HTML or
		# in the encoded URL. 
		#
		# For example, they might reassign page from, say,
		# "vowel.html" to "../../../etc/passwd"!  As you can
		# imagine, this could end up displaying your password file
		# to the browser window.  Thus, we need to make sure that
		# only appropriate files can be displayed by the store.

&error_check_form_data;

		# What is the purpose of a unique cart?  Well, simply,
		# every customer who is using the application must be
		# assigned a unique cart which will contain their specific
		# shopping list.  
		#
		# These carts are actually short flatfile text databases
		# stored by default in the User_carts subdirectory in the
		# format "somerandomnumber.cart". These files contain
		# information about which items the client has ordered and
		# how many of each item they ordered.
		#
		# Once a client enters the store, they are assigned their
		# own unique cart.  For the rest of their stay, the script
		# will make sure that it matches clients with their carts
		# no matter which page they go to.
		#
		# It does this by continually passing the location of the
		# cart ($cart_id) along as either hidden form data or URL
		# encoded information depending on if the customer uses a 
		# submit button or a hyperlink to navigate through the
		# store. Thus, as long as the customer follows the path
		# provided by the application, she will never lose her
		# cart.
		#
		# Thus, before anything else, the script must check to see 
		# if the client has already received a unique shopping
		# cart. If so, it will be coming in as form data and have
		# been just assigned to $cart_id.  If the script has not
		# received a shopping cart id number as form data
		# ($cart_id eq ""), however, it means that the client has
		# not yet received a unique shopping cart.
		#
		# If this is the case, the script must assign them
		# one.  However, as a matter of good housekeeping, it will
		# first take a second to delete old carts that have been
		# abandoned using the delete_old_carts subroutine
		# documented later in this script. Then, it will assign
		# the client their own fresh new cart using
		# assign_a_unique_shopping_cart_id also discussed later.

if ($cart_id eq "")
  {
  &delete_old_carts;
  &assign_a_unique_shopping_cart_id;
  }

		# Now that the script has created the entire environment
		# in which it must operate, it is time to provide
		# the logic for it to determine what it should do.
		#
		# The logic is broken down into a series of "if" tests.
		#
		# Specifically, the script checks the values of incoming
		# administrative form variables (mainly supplied from
		# the SUBMIT buttons on dynamically generated HTML forms)
		# and will perform its operations depending on whether
		# those administrative variables have values associated
		# with them or not.
		#
		# The basic format for such an "if" test follows the
		# syntax:
		#
		# if (the value of some submit button is not equal to nothing)
		#   {
		#   process that type of request;
		#   exit;
		#   }
		#
		# For example, consider the second case in which the
		# customer has clicked on the "Add to Cart" submit
		# button denoted with the NAME value of "add_to_cart_button". 
		#
		# elsif ($form_data{'add_to_cart_button'} ne "")
		#  {
		#  &add_to_the_cart;
		#  exit;
		#  }
		#
		# Because the submit button will have some value
		# like "Add this item to my Cart", when the script reaches
		# this line, it will answer true to the test.  
		#
		# Since the customer can only click on one submit button
		# at a time, we can be assured that only one operation
		# will answer true.  
		#
		# The beauty of using the not equal (ne) test is that
		# regardless of what the submit button actually says
		# (it might say "Add a weiner dog to the chopping block")
		# the if test will still be satisfied if they have clicked
		# the button, since whatever the VALUE is, it will
		# certainly not be equal to "nothing". Of course, this
		# assumes that you do not rename the NAME argument of the
		# submit buttons.  If you do so, you must harmonize the
		# variable you use on the input forms, with the variables
		# used here to test.
		#
		# Similarly, if you wish to have graphical submit buttons
		# instead of the ugly default buttons supplied by the
		# browser, you will have to modify the if tests so that
		# they follow the standard image map test:
		#
		# if ($form_data{'some_button.x'} ne "")
		#  {
		#  &do some subroutine;
		#  exit;
		#  }
		#
		# where the HTML code looks like the following:
		#
		# <INPUT TYPE = "image" NAME = "some_button"
		#	 SRC = "/images/010/button.gif" BORDER = "0">
		#
		# Thus, if the button actually has an X-dimension value
		# (any x-dimension value), it means that the button had
		# been clicked.
		#
		# Finally, note that every if test is concluded with an
		# exit statement.  This is because once the script is done
		# executing the routine specified in the submit button, it
		# is done with its work and should exit immediately.
		#
		# Get used to the idea that this script is "self-referencing".
		# The application itself contains many mini-routines
		# which all refer back to the routine community.  Every
		# instance of the script need only execute maybe 1/8th of
		# the routines in the whole file, but in the lifetime of
		# the application, most, if not all, routines are
		# executed.
		#
		# Okay, so now let's look at each of the routines which
		# this applicaiton must execute.
		#
		# 1. Adding an Item to the Shopping Cart - One
		# request that the script may have to handle is that of
		# adding an item to a shopping cart. Once the client has
		# decided to purchase an item, she will have added a
		# quantity to the text box and hit the "add this item"
		# submit button. So we must be prepared to add items to
		# the client's cart.  Additions are handled with the
		# add_to_the_cart subroutine discussed later.
		#
		# 2. Displaying the Client's Cart with Cart Manipulation
		# Options - On the other hand, the user may have already
		# been adding items, realized she went over budget and
		# decided to reduce the quantities of some of the items
		# she chose or even delete them altogether from her cart.
		#
		# The first thing we need to do is send her an HTML form
		# with which she can choose whether to delete or modify
		# as well as send her a table depicting the current
		# contents of the shopping cart. This is all done using
		# the display_cart_contents subroutine at the end of this
		# file.
		#
		# 3. Displaying the Change Quantity Form - Yet another
		# function that this script may be asked to perform is
		# modifying the quantities of some of the items in the
		# client's cart. If the client has asked to make a
		# quantity modification, the script must give her a form
		# so that she can specify the changes she wants made.
		#
		# The form is fairly simple.  We will use the same basic
		# table presentation that we used in the
		# display_cart_contents subroutine, except that we will
		# add another column of text input fields used to submit
		# a new quantity for every row in the cart.  These text
		# input fields however, will use as there NAME argument,
		# the unique cart row number for every row.  Consider the
		# following cell definition:
		#
		# <TD><INPUT TYPE = "text" NAME = "219" SIZE ="3"></TD>
		#		
		# Thus, when the client submits a quantity change, they
		# will be submitting a cart_row_number (219) associated
		# with a quantity value (the value submitted in the text
		# field). 
		#
		# We'll use the cart row number to figure out exactly
		# which item in the cart should be modified.
		#
		# 4. Changing the Quanity of Items in the Cart - Once the
		# client has typed in some quantity changes and submitted
		# the information back to this script, we must make the
		# modifications to the database.  This is done with the
		# modify_quantity_of_items_in_cart subroutine discussed
		# below.
		#
		# 5. Displaying the Delete Item Form - Perhaps instead,
		# the client asked to delete an item rather than modify
		# the quantity.  If this is the case, the script must 
		#  display a form very similar to the one for
		# modification.  The only difference is that we will use
		# checkboxes for each item instead of text boxes because
		# in the case of delete, the user need only select which
		# items to delete rather than to also specify a quantity.
		# As in the case of modifcation, the script associates the
		# NAME argument of the checkboxes with the cart row number
		# of the item they represent. Thus, the syntax will
		# resemble the following:
		# 
		#      <TD><INPUT TYPE = "checkbox" NAME = "220"></TD>
		#
		# where 220 is the cart row number of some element which
		# can be deleted.  We will handle the display of the
		# delete item form using the output_delete_item_form
		# subroutine discussed later.
		#
		# 6. Deleting Items From the Cart - Once the client
		# submits some items to delete, the script must also
		# be able to delete them from the cart.  This is done with
		# the delete_from_cart subroutine discussed later.
		#
		# 7. Displaying the Order Form - Further, the script must
		# be able to display the order form for the client if that
		# is what they want to see.  The script uses the
		# display_page subroutine which will be discussed later to
		# display a pre-designed order form.  Note that the
		# handling of the order form will not be done by this
		# script.  Instead, the order form will reference one
		# final script which may be located in a separate,
		# "secure" directory (if one exists).  In the case of
		# secure ordering, we do not want a self-referential link
		# because we do not want the entire script being run from
		# the secure directoryt.   This would be inefficient.  The
		# only time we want to utilize the secure directory is if
		# we are processing the order.  Thus, the ordering process
		# has been broken out into its own mobile script.
		#
		# 8. Submitting the Order - Once the user fills out the
		# order form she may submit the order for final
		# processing.  Final processing involves calculating
		# shipping logic like (shipping  method, tax rates,
		# discounts, etc), sending the order to the order
		# processing administrator and letting the customer know
		# that all was completed successfully.  All orders are
		# processed by the process_order_form subroutine which is
		# designed to handle all of these chores.
		#
		# However, there is one catch to order processing: Secure
		# servers.  Many stores have secure server functionality
		# in which specific directories are designed to handle
		# encrypted communication between server and browser.
		# (typically https setups).  In this case, the cgi script
		# handling the order processing must be physically located
		# inside the secure directory.
		#
		# If this is your situation, then you must make a mirror
		# copy of the application directories and place them all
		# inside the secure area.  Then, you will set
		# $sc_order_script_url in the setup file equal to the
		# secured mirror of the script.  Then, the script will
		# dynamically refernce the secured location instead of the
		# insecure location for order processing.  In actuality,
		# only the order processing routine will be executed in
		# the secure directory, but we copy the whole script there
		# for simplicity's sake.
		# 
		# If you are not running a secure server, you may just set
		# $sc_order_script_url equla to web_store.cgi and
		# continue the regular self-referencing behavior.
		#
		# 9. Displaying Products, Categories and Misc. Pages - 
		# If the script is getting in a value for page or for
		# product, it means that it is being asked to navigate
		# through the store.
		#
		# The page variable is used to locate a page which the
		# store should display to the user.  In the case of an
		# HTML-based Web Store, the page value will be used to
		# point to both pages with products as well as pages with
		# "lists" of products.  Also in the case of the HTML-based
		# store, there will be no need for the product variable
		# since the product variable is specific to the
		# database-based store which must be able to interpret
		# between a "list of products" type page and an actual
		# product page.  This is because when the database-based
		# version creates a page to view, it needs to generate it
		# on the fly.  Thus, it searches for the product in the
		# database.  If it needs to display a "list of products"
		# type page with sub links to actual product pages within a
		# similar group,  it should not go to the database.
		# Instead, it actually needs to display a list page just
		# as the HTML-based store would do.
		#
		# As we've said, the product value will be used by the
		# database-based shopping cart to cull out the list of
		# products which the customer is interested in seeing.
		# Think of this as a sort've hard-coded search of the
		# database where the admin may hard code a category to
		# search for in the URL string which requests a product
		# page view.
		#
		# Consider the following hyperlinks as examples.
		#
		# web_store.cgi?page=Letters.html&cart_id=98.123
		# web_store.cgi?page=Numbers.html&cart_id=8708496.3559
		# web_store.cgi?product=Numbers&cart_id=2196655.5107
		#
		# The first case could be used in either the HTML or
		# Database-based version.  The script would display the
		# HTML page "Letters.html" which would be a "list of
		# products" type page.  In our distribution example page,
		# Letters.html contains links to both Vowels.html
		# and Consonants.html
		#
		# The second URL would be used for an HTML-based store.
		# It would cause this script to display the pre-designed
		# product page, Numbers.html.
		#
		# Finally, the last line would be used for a
		# database-based cart system and would cause this script
		# to search through the database for all items with
		# "Numbers" in the category field (by default, this is the
		# second field in data.file)
		#
		# Thus, there are two ways that products can be displayed
		# with this script.  
		#
		# The first way is for the store administrator to create a
		# delimited data file with all the data to be displayed
		# incorporated in database rows.  The contents of these
		# rows will be displayed according to the format defined
		# in the $sc_product_page_row variable in
		# web_store_html_lib.pl.  But this will be discussed in
		# greater detail later.
		# 
		# The second way is for the admin to create HTML
		# pages directly with the same data already incorporated
		# into some desired interface.
		#
		# The admin specifies which method she will use by setting
		# the variable $sc_use_html_product_pages in the setup file.
		# If this variable is set to yes, it means that the script
		# should simply output a predesigned HTML product page.
		# Anything else, and it will expect a database.
		#
		# The display_products_for_sale subroutine discussed
		# later does just that. However, there is
		# one catch to the presentation of an HTML page.  If the
		# client is doing a keyword search, we'll have to generate
		# a list of pages on which their keyword was founds using
		# the html-search subroutine located in
		# web_store_html_search.pl
		#
		# 10. Display The Frontpage - Finally, if all else has
		# failed, it means that we are simply being asked to
		# display the store frontpage, for no other routines
		# remain.
		#
		# To display the store front page, we will access the
		# output_frontpage subroutine discussed later.
		#
		# Okay, so those are all the cases.  Now let's go through
		# them one by one as code.
		#
		# First though, we need to set up a flag to see
		# if the user has any values inside any of the query
		# fields.
		#

$are_any_query_fields_filled_in = "no";
foreach $query_field (@sc_db_query_criteria) {
  @criteria = split(/\|/, $query_field);
  if ($form_data{$criteria[0]} ne "") {
    $are_any_query_fields_filled_in = "yes";
  }
}

if ($form_data{'add_to_cart_button'} ne "")
  {
  &add_to_the_cart;
  exit;
  }

elsif ($form_data{'modify_cart_button'} ne "")
  {
  &display_cart_contents;
  exit;
  }

elsif ($form_data{'change_quantity_button'} ne "")
  {
  &output_modify_quantity_form;
  exit;
  }

elsif ($form_data{'submit_change_quantity_button'} ne "")
  {
  &modify_quantity_of_items_in_cart;
  exit;
  }

elsif ($form_data{'delete_item_button'} ne "")
  {
  &output_delete_item_form;
  exit;
  }

elsif ($form_data{'submit_deletion_button'} ne "")
  {   
  &delete_from_cart;
  exit;
  }

elsif ($form_data{'order_form_button'} ne "")
  {
  &require_supporting_libraries (__FILE__, __LINE__, 
                              "$sc_order_lib_path"); 
  &display_order_form;
  exit;
  }

elsif ($form_data{'submit_order_form_button'} ne "")
  {
  &require_supporting_libraries (__FILE__, __LINE__, 
                              "$sc_order_lib_path"); 
  &process_order_form;
  exit;
  }

elsif (($page ne "" || $form_data{'search_request_button'} ne ""
		    || $form_data{'continue_shopping_button'}
                    || $are_any_query_fields_filled_in =~ /yes/i) &&
	($form_data{'return_to_frontpage_button'} eq "")) 
  {
  &display_products_for_sale;
  exit;
  }

else
  {
  &output_frontpage;
  exit;
  }

		# Well that's it.  That is the end of the program!  Well,
		# not exactly.  That is just the end of the main body of
		# logic.  From here on out we will define the logic of the
		# subroutines called in the "if" tests above.  

#######################################################################
#                       Require Supporting Libraries.		      #
#######################################################################

		# require_supporting_libraries is used to read in some of
		# the supporting files that this script will take
		# advantage of.
		#
		# require_supporting_libraries takes a list of arguments
		# beginning with the current filename, the current line
		# number and continuing with the list of files which must
		# be required using the following syntax:
		#
		# &require_supporting_libraries (__FILE__, __LINE__,
		#				"file1", "file2",
		#				"file3"...);
		#
		# Note: __FILE__ and __LINE__ are special Perl variables
		# which contain the current filename and line number
		# respectively.  We'll continually use these two variables
		# throughout the rest of this script in order to generate
		# useful error messages.

sub require_supporting_libraries
  {

		# The incoming file and line arguments are split into
		# the local variables $file and $line while the file list
		# is assigned to the local list array @require_files.
		#
		# $require_file which will just be a temporary holder
		# variable for our foreach processing is also defined as a
		# local variable.

  local ($file, $line, @require_files) = @_;
  local ($require_file);

		# Next, the script checks to see if every file in the
		# @require_files list array exists (-e) and is readable by
		# it (-r). If so, the script goes ahead and requires it.

  foreach $require_file (@require_files)
    {
    if (-e "$require_file" && -r "$require_file")
      {
      require "$require_file";
      }

		# If not, the scripts sends back an error message that
		# will help the admin isolate the problem with the script.

    else
      {
      print "I am sorry but I was unable to require $require_file at line
  	    $line in $file.  Would you please make sure that you have the
	    path correct and that the permissions are set so that I have
	    read access?  Thank you.";
      exit;
      }
    } # End of foreach $require_file (@require_files)
  } # End of sub require_supporting_libraries

#######################################################################
#                     Read and Parse Form Data.			      #
#######################################################################

		# read_and_parse_form_data is a short subroutine 
		# responsible for calling the ReadParse subroutine in
		# cgi-lib.pl to parse the incoming form data.  The script 
		# also tells cgi-lib to prepare that information in the
		# associative array named %form_data which we will be able
		# to use for the rest of this script.
		#
		# read_and_parse_form_data takes no arguments and is
		# called with the following syntax:
		#
		# &read_and_parse_form_data;

sub read_and_parse_form_data
  {
  &ReadParse(*form_data);
  }

#######################################################################
#                     Error Check Form Data.                          #   
####################################################################### 

		# error_check_form_data is responsible for checking to
		# make sure that only authorized pages are viewable using
		# this application. It takes no arguments and is called
		# with the following syntax:
		#
		# &error_check_form_data;
		#
		# The routine simply checks to make sure that if
		# the page variable extension is not one that is defined
		# in the setup file as an appropriate extension like .html
		# or .htm, or there is no page being requestd (ie: the
		# store front is being displayed) it will send a warning
		# to the user, append the error log, and exit.
		#
		# @acceptable_file_extensions_to_display is an array of
		# acceptable file extensions defined in the setup file.
		# To be more or less restrictive, just modify this list.
		#
		# Specifically, for each extension defined in the setup
		# file, if the value of the page variable coming in from
		# the form ($page) is like the extension (/$file_extension/) 
		# or there is no value for page (eq ""), we will set
		# $valid_extension equal to yes.  
		
sub error_check_form_data
  {
  foreach $file_extension (@acceptable_file_extensions_to_display)
    {
    if ($page =~ /$file_extension/ || $page eq "")
      {
      $valid_extension = "yes";
      }
    }

		# Next, the script checks to see if $valid_extension has
		# been set to "yes".
		#
		# If the value for page satisfied any of the extensions 
		# in @acceptable_file_extensions_to_display, the script
		# will set $valid_extension equal to yes. If the value 
		# is set to yes, the subroutine will go on with it's work.
		# Otherwise it will exit with a warning and write to the
		# eror log if appropriate
		#
		# Notice that we pass three parameters to the
		# update_error_log subroutine which will be discussed
		# later. The subroutine gets a warning, the
		# name of the file, and the line number of the error.
		#
		# $sc_page_load_security_warning is a variable set in
		# web_store.setup.  If you want to give a more or less
		# informative error message, you are welcome to change the
		# text there.

  if ($valid_extension ne "yes")
    {
    print "$sc_page_load_security_warning";
    &update_error_log("PAGE LOAD WARNING", __FILE__, __LINE__);
    exit;
    }        
  }

#######################################################################
#                        Delete Old Carts. 	                      #
#######################################################################

		# delete_old_carts is a subroutine which is used to prune
		# the carts directory, cleaning out all the old carts
		# after some time interval defined in the setup file.  It
		# takes no argumnetes and is called with the following
		# syntax:
		#
		# &delete_old_carts;

sub delete_old_carts
  {

		# The subroutine begins by grabbing a listing of all of
		# the client created shoppping carts in the User_carts
		# directory.
		#
		# It then opens the directory and reads the contents using
		# grep to grab every file with the extension .cart. Then
		# it closes the directory.  
		#
		# If the script has any trouble opening the directory,
		# it will output an error message using the
		# file_open_error subroutine discussed later.  To the
		# subroutine, it will pass the name of the file which had
		# trouble, as well as the current routine in the script
		# having trouble , the filename and the current line
		# number.

  opendir (USER_CARTS, "$sc_user_carts_directory_path") ||
           &file_open_error("$sc_user_carts_directory_path", 
	  		    "Delete Old Carts", __FILE__, __LINE__);
  @carts = grep(/\.cart/,readdir(USER_CARTS));
  closedir (USER_CARTS);
      
		# Now, for every cart in the directory, delete the cart if
		# it is older than half a day.  The -M file test returns
		# the number of days since the file was last modified.
		# Since the result is in terms of days, if the value is
		# greater than the value of $sc_number_days_keep_old_carts
		# set in web_store.setup, we'll delete the file.

  foreach $cart (@carts)
    {
    if (-M "$sc_user_carts_directory_path/$cart" > $sc_number_days_keep_old_carts)
      {
      unlink("$sc_user_carts_directory_path/$cart");
      }
    }
  } # End of sub delete_old_carts

#######################################################################
#                        Assign a Shopping Cart.                      #   
#######################################################################  

		# assign_a_unique_shopping_cart_id is a subroutine used to
		# assign a unique cart id to every new clinet.  It takes
		# no argumnets and is called with the following syntax:
		#
		# &assign_a_unique_shopping_cart_id;

sub assign_a_unique_shopping_cart_id
  {

		# First we will check to see if the admin has asked us to
		# log all new clients.  If so, we will get the current
		# date using the get_date subroutine discussed later, open the 
		# access log file for appending, and print to the access
		# log file all of the environment variable values as well
		# as the current date and time.  
		#
		# However, we will protect ourselves from multiple,
		# simultaneous writes to the access log by using the
		# lockfile routine documented at the end of this file,
		# passing it the name of a temporary lock file to use.
		#
		# Remember that there may be multimple simultaneous
		# executions of this script because there may be many
		# people shopping all at once.  It would not do if one
		# customer was able to overwrite the information of
		# another customer if they accidentally wanted to acccess
		# the log file at the same exact time.

    if ($sc_shall_i_log_accesses eq "yes")
      {
      $date = &get_date;
      &get_file_lock("$sc_access_log_path.lockfile");
      open (ACCESS_LOG, ">>$sc_access_log_path");

		# Using the keys function, the script grabs all the
		# keys of the %ENV associative array and assigns them as
		# elements of @env_keys.  It then creates a new row for
		# the access log which will be a pipe delimited list of
		# the date as well as all the environment variables and
		# their values.

      @env_keys = keys(%ENV);
 
      $new_access = "$date\|";
      foreach $env_key (@env_keys)
        {
        $new_access .= "$ENV{$env_key}\|";
        }

		# The script then takes off the final pipe, adds the new
		# access to the log file, closes the log file and removes
		# the lock file.

      chop $new_access;
      print ACCESS_LOG "$new_access\n";
      close (ACCESS_LOG);
      &release_file_lock("$sc_access_log_path.lockfile");
      }

		# Now that the new access is recorded, the script assigns 
		# the user their own unique shopping cart.  To do so,
		# it generates a random (rand) 8 digit (100000000)
		# integer (int) and then appends to that string the current
		# process id ($$). However, the srand function is seeded
		# with the time and the current process id in order to
		# produce a more random random number.  $sc_cart_path is
		# also defined now that we have a unique cart id number.

    srand (time|$$);
    $cart_id = int(rand(10000000));
    $cart_id .= ".$$";
    $sc_cart_path = "$sc_user_carts_directory_path/${cart_id}.cart";

		# However, before we can be absolutely sure that we have
		# created a unique cart, the script must check the existing
		# list of carts to make sure that there is not one with
		# the same value.
		#
		# It does this by checking to see if a cart with the
		# randomly generated ID number already exists in the Carts
		# directory.  If one does exit (-e), the script grabs
		# another random number using the same routine as
		# above and checks again.  
		#
		# Using the $cart_count variable, the script executes this
		# algorithm three times.  If it does not succeede in finding
		# a unique cart id number, the script assumes that there is
		# something seriously wrong with the randomizing routine
		# and exits, warning the user on the web and the admin
		# using the update_error_log subroutine discussed later.

    $cart_count = 0;

    while (-e "$sc_cart_path")
      {
      if ($cart_count == 3)
        {
        print "$sc_randomizer_error_message";
        &update_error_log("COULD NOT CREATE UNIQUE CART ID", __FILE__,
   			  __LINE__);
        exit;
        }

      srand (time|$$);
      $cart_id = int(rand(10000000));
      $cart_id .= ".$$";    
      $cart_count++;

      } # End of while (-e $sc_cart_path)
       
		# Now that we have generated a truly unique id
		# number for the new client's cart, the script may go
		# ahead and create it in the User_carts sub-directory.  
		#
		# If there is a problem opening the new cart, we'll output
		# an error message with the file_open_error subroutine
		# discussed later.

    open (CART, ">$sc_cart_path") ||
  	  &file_open_error("$sc_cart_path",
                           "Assign a Shopping Cart", __FILE__, __LINE__);
  }

#######################################################################   
#                       Output Frontpage.                             #
#######################################################################  

		# output_frontpage is used to display the frontpage of the
		# store.  It takes no argumnets and is accessed with the
		# following syntax:
		#
		# &output_frontpage;
		#
		# The subroutine simply utilizes the display_page
		# subroutine which is discussed later to output the
		# frontpage file, the location of which, is defined
		# in web_store.setup.  display_page takes four arguments:
		# the cart path, the routine calling it, the current
		# filename and the current line number.

sub output_frontpage
  {
  &display_page("$sc_store_front_path", "Output Frontpage", __FILE__,
		__LINE__);
  }

#######################################################################
#                    Add to Shopping Cart                             #
#######################################################################

		# The add_to_the_cart subroutine is used to add items to
		# the customer's unique cart.  It is called with no
		# arguments with the following syntax:
		#
		# &add_to_the_cart;

sub add_to_the_cart
  {

		# The script must first figure out what the client has
		# ordered.
		#
		# It begins by using the %form_data associative array
		# given to it by cgi-lib.pl.  It takes all of the keys
		# of the form_data associative array and drops them into 
		# the @items_ordered array.
		#  
		# Note: An associative array key is like a variable name
		# whereas an associative array value is the
		# value associated with that variable name. The
		# benefit of an associative array is that you can have
		# many of these key/value pairs in one array.
		# Conveniently enough, you'll notice that input fields on
		# HTML forms will have associated NAMES and VALUES
		# corresponding to associative array KEYS and VALUES.  
		# 
		# Since each of the text boxes in which the client could
		# enter quantities were associated with the database id
		# number of the item that they accompany, (as defined
		# in the display_page routine at the end of this
		# script), the HTML should read 
		#
		#         <INPUT TYPE = "text" NAME = "1234"> 
		#
		# for the item with database id number 1234 and 
		#				
		#         <INPUT TYPE = "text" NAME = "5678"> 
		#
		# for item 5678.  
		#
		# If the client orders 2 of 1234 and 9 of 5678, then
		# @incoming_data will be a list of 1234 and 5678 such that
		# 1234 is associated with 2 in %form_data associative
		# array and 5678 is associated with 9.  The script uses 
		# the keys function to pull out just the keys.  Thus,
		# @items_ordered would be a list like (1234, 5678, ...).

  @items_ordered = keys (%form_data);

		# Next it begins going through the list of items ordered
		# one by one.

  foreach $item (@items_ordered)
    {

		# However, there are some incoming items that don't need
		# to be processed. Specifically, we do not care about cart_id,
		# page, keywords, add_to_cart, or whatever incoming
		# administrative variables exist because these are all
		# values set internally by this script. They will be
		# coming in as form data just like the client-defined
		# data, and we will need them for other things, just not
		# to fill up the user's cart. In order to bypass all of
		# these administrartive variables, we use a standard
		# method for denoting incoming items.  All incoming items
		# are prefixed with the tag "item-".  When the script sees
		# this tag, it knows that it is seeing an item to be added
		# to the cart.
		#
		# Similarly, items which are actually options info are
		# denoted with the "option" keyword.  We will also accept
		# those for further processing.
		#
		# And fo course, we will not need to worry about any items
		# which have empty values.  If the shopper did not enter a
		# quantity, then we won't add it to the cart.

    if (($item =~ /^item-/i ||
        $item =~ /^option/i) &&
        $form_data{$item} ne "")
      {

		# Once the script has determined that the current element
		# ($item) of @items_ordered is indeeed a non-admin item,
		# it must separate out the items that have been ordered
		# from the options which modify those items.  If $item
		# begins with the keyword "option", which we set
		# specifically in the HTML file, the script will add
		# (push) that item to the array called @options.  However,
		# before we make the check, we must strip the "item-"
		# keyword off the item so that we have the actual row
		# number for comparison.

        $item =~ s/^item-//i;

        if ($item =~ /^option/i)
          {
          push (@options, $item);
          }

		# On the other hand, if it is not an option, the script adds 
		# it to the array @items_ordered_with_options, but adds
		# both the item and its value as a single array element.
		#
		# The value will be a quantity and the item will be
		# something like "item-0001|12.98|The letter A" as defined in
		# the HTML file.  Once we extract the initial "item-"
		# tag from the string using regular expressions ($item =~
		# s/^item-//i;), the resulting string would be something
		# like the following:
		#
		#           2|0001|12.98|The letter A 
		#
		# where 2 is the quantity.
		#
		# Firstly, it must be a digit ($form_data{$item} =~ /\D/).
		# That is, we do not want the clients trying to enter
		# values like "a", "-2", ".5" or "1/2".  They might be
		# able to play havok on the ordering system and a sneaky
		# client may even gain a discount because you were not
		# reading the order forms carefully.  
		#
		# Secondly, the script will dissallow any zeros
		# ($form_data{$item} == 0).  In both cases the client will
		# be sent to the subroutine bad_order_note located in
		# web_store_html_lib.pl.

        else
          {
          if (($form_data{"item-$item"} =~ /\D/) ||
              ($form_data{"item-$item"} == 0))
            {
	    &bad_order_note;
            }
          else
            {
            $quantity = $form_data{"item-$item"};
            push (@items_ordered_with_options, "$quantity\|$item\|");
	    }
 	  }
        } # End of if ($item ne "$variable" && $form_data{$item} ne "")
    } #End of foreach $item (@items_ordered)


		# Now the script goes through the array  
		# @items_ordered_with_options one item at a time in order
		# to modify any item which has had options applied to it.
		# Recall that we just built the @options array with all
		# the options for all the items ordered.  Now the script
		# will need to figure out which options in @options belong
		# to which items in @items_ordered_with_options.

  foreach $item_ordered_with_options (@items_ordered_with_options)  
    {

		# First, clear out a few variables that we are going to
		# use for each item. 
		#
		# $options will be used to keep track of all of the
		# options selected for any given item. 
		#
		# $option_subtotal will be used to determine the total
		# cost of each option.
		#
		# $option_grand_total will be used to calculate the
		# total cost of all ordered options.  
		#
		# $item_grand_total will be used to calculate the total
		# cost of the item ordered factoring in quantity and
		# options.

    $options = "";
    $option_subtotal = "";
    $option_grand_total = "";
    $item_grand_total = "";

		# Now split out the $item_ordered_with_options into it's
		# fields.  Note that we have defined the index location of
		# some important fields in web_store.setup.  Specifically,
		# the script must know the index of quantity, item_id and
		# item_price within the array.  It will need these values
		# in particular for further calculations.  Also, the
		# script will change all occurances of "~qq~" to a double
		# quote (") character, "~gt~" to a greater than sign (>)
		# and "~lt~" to a less than sign (<).  The reason that
		# this must be done is so that any double quote, greater
		# than, or less than characters used in URLK strings can
		# be stuffed safely into the cart and passed as part of
		# the NAME argumnet in the "add item" form.  Consider the
		# following item name which must include an image tag.
		#
		# <INPUT TYPE = "text" 
		#	 NAME = "item-0010|Vowels|15.98|The letter A|~lt~IMG SRC = ~qq~Html//images/010/a.jpg~qq~ ALIGN = ~qq~left~qq~~gt~"
		#
		# Notice that the URL must be edited. If it were not, how
		# would the browser understand how to interpret the form
		# tag?  The form tag uses the double quote, greater
                # than, and less than characters in its own processing.

     $item_ordered_with_options =~ s/~qq~/\"/g;
     $item_ordered_with_options =~ s/~gt~/\>/g;
     $item_ordered_with_options =~ s/~lt~/\</g;

     @cart_row = split (/\|/, $item_ordered_with_options);
     $item_quantity = $cart_row[$sc_cart_index_of_quantity];
     $item_id_number = $cart_row[$sc_cart_index_of_item_id];
     $item_price = $cart_row[$sc_cart_index_of_price];

		# Then for every option in @options, the script splits up
		# each option into it's fields.
		#
		# Once it does both splits, the script can compare the name
		# of the item with the name associated with the option.
		# If they are the same, it knows that this is an option
		# which was meant to enhance this item.

    foreach $option (@options)
      {
      ($option_marker, $option_number, $option_item_number) = split
	    (/\|/, $option);

		# If the script finds a match, it records the option
		# information contained in the $option variable.

      if ($option_item_number eq "$item_id_number")
	{

		# Since it must apply this option to this item, the script
		# splits out the value associated with the option and
		# appends it to $options.  Once it has gone through all of
		# the options, using .=, the script will have one big string
		# containing all the options so that it can print them
		# out. Note that in the form on which the client chooses
		# options, each option is denoted with the form 
		#
		#            NAME = "a|b|c" VALUE = "d|e"
		#
		# where 
		# 
		# a is the option marker "option" 
		# b is the option number (you might have multiple options
		#	which all modify the same item.  Option number
		#	identifies each option uniquely) 
		# c is the option item number (the unique item id number
		#	which the option modifies)
		# d is the option name (the descriptive name of the
		#	option)
		# e is the option price. 
		# 
		# For example, consider this option from the default
		# Vowels.html file which modifies item number 0001:
		# 
		#      <INPUT TYPE = "radio" NAME = "option|2|0001"
                #             VALUE = "Red|0.00" CHECKED>Red<BR>
		#
		# This is the second option modifying item number 0001.
		# When displayed in the display cart sscreen, it will read
		# "Red 0.00, and will not affect the cost of the item.
		
        ($option_name, $option_price) = split (/\|/,$form_data{$option});
        $options .= "$option_name $option_price,";

		# But the script must also calculate the cost changes with
		# options. To do so, it will take the current value of
		# $option_grand_total and add to it the value of the
		# current option.  It will then format the result to
		# two decimal places using the format_price subroutine
		# discussed later and assign the new result to
		# $option_grand_total

  	$unformatted_option_grand_total = $option_grand_total + $option_price;
        $option_grand_total = &format_price($unformatted_option_grand_total);

  	} # End of if ($option_item_number eq "$item_id_number")
      } # End of foreach $option (@options)

		# Next, the script takes off the last comma in options.
		# Look a few lines up, you'll see that a comma is added to
		# the end of each option.  Well the last option does not
		# need that last comma.

    chop $options;

		# Now, the script adds a space after each comma so the
		# display looks nicer.

    $options =~ s/,/, /g;

		# Next, the counter subroutine which is discussed later,
		# is called and sent the location of the counter file
		# defined in the setup file as well as the file name and
		# current line number.
		#
		# This routine will return one variable called
		# $item_number which the script can use to identify a
		# shopping cart item absolutely.  This must be done so
		# that when we modify and delete from the cart, we will
		# know exactly which item to affect. We cannot rely simply
		# on the unique database id number because a client may
		# purchase two of the same item but with different
		# options. Unless there is a separate, unique cart row id
		# number, how would the script know which to delete if the
		# client asked to delete one of the two.

    $item_number = &counter ($sc_counter_file_path, __FILE__, __LINE__);

		# Finally, the script makes the last price calculations
		# and appends every ordered item to $cart_row
		#
		# A completed cart row might look like the following:
		# 2|0001|Vowels|15.98|Letter A|Times New Roman 0.00|15.98|161

    $unformatted_item_grand_total = $item_price + $option_grand_total;
    $item_grand_total = &format_price("$unformatted_item_grand_total");

    foreach $field (@cart_row)
      {
      $cart_row .= "$field\|";
      }

    $cart_row .= "$options\|$item_grand_total\|$item_number\n";

    } # End of foreach $item_ordered_with_options.....

		# When it is done appending all the items to $cart_row,
		# the script opens the user's shopping cart and adds the
		# new items. If there is a problem opening the file, it
		# will call file_open_error subroutine to handle the error
		# reporting.

  open (CART, ">>$sc_cart_path") || 
      &file_open_error("$sc_cart_path",
                       "Add to Shopping Cart", __FILE__, __LINE__);
  print CART "$cart_row";
  close (CART);

		# Then, the script sends the client back to a previous 
		# page.  There are two pages that the customer can be sent
		# of course, the last product page they were on or the
		# page which displays the customer's cart.  Which page the
		# customer is sent depends on the value of
		# $sc_should_i_display_cart_after_purchase which is defined
		# in web_store.setup.  If the customer should be sent to
		# the display cart page, the script calls
		# display_cart_contents, otherwise it calls display_page
		# if this is an HTML-based cart or 
		# create_html_page_from_db if this is a database-based
		# cart.

  if ($sc_use_html_product_pages eq "yes")
    {
    if ($sc_should_i_display_cart_after_purchase eq "yes")
      {
      &display_cart_contents;      
      }
    else
      {
      &display_page("$sc_html_product_directory_path/$page",
                    "Display Products for Sale");
      }
    }
  else
    {
    if ($sc_should_i_display_cart_after_purchase eq "yes")
      {  
      &display_cart_contents;
      }

    elsif ($are_any_query_fields_filled_in =~ /yes/i) 
      {
      $page = "";
      &display_products_for_sale;
      }

    else
      {
      &create_html_page_from_db;
      }
    }
  } 

#######################################################################
#                  Output Modify Quantity Form                        #
#######################################################################

		# output_modify_quantity_form is the subroutine
		# responsible for displaying the form which customers can
		# use to modify the quantity of items in their cart.  It
		# is called with no argumnets with the following syntax:
		#
		# &output_modify_quantity_form;

sub output_modify_quantity_form
  {

		# The subroutine begins by outputting the HTML header
		# using standard_page_header, adds the modify form using
		# display_cart_table and finishes off the HTML page with
		# modify_form_footer. All of these subrotuines are
		# discussed in web_store_html_lib.pl

  &standard_page_header("Change Quantity");
  &display_cart_table("changequantity");
  &modify_form_footer;
  }

#######################################################################
#                Modify Quantity of Items in the Cart                 #
#######################################################################

		# The modify_quantity_of_items_in_cart subroutine is
		# responsible for making quantity modifications in the
		# customer's cart.  It takes no arguments and as called
		# with the following syntax:
		#
		# &modify_quantity_of_items_in_cart;

sub modify_quantity_of_items_in_cart
  {

		# First, the script gathers the keys as it did for the
		# add_to_cart routine previously, checking to make
		# sure the customer entered a positive integer (not
		# fractional and not less than one).

  @incoming_data = keys (%form_data);
  foreach $key (@incoming_data)
    {
    if ((($key =~ /[\d]/) && ($form_data{$key} =~ /\D/)) ||
        $form_data{$key} eq "0")
      {
      &update_error_log("BAD QUANTITY CHANGE", __FILE__, __LINE__);
      &bad_order_note;
      }

		# Just as the script did in the add to cart routine
		# previuosly, it will create an array (@modify_items) of
		# valid keys.

    unless ($key =~ /[\D]/ && $form_data{$key} =~ /[\D]/)
      {
      if ($form_data{$key} ne "")
        {
        push (@modify_items, $key);
        }
      }
    } # End of foreach $key (@incoming_data)

		# Then, the script must open up the client's cart and go
		# through it line by line.  File open problems are
		# handled by file_open_error as usual.

  open (CART, "$sc_cart_path") || 
    &file_open_error("$sc_cart_path",
                     "Modify Quantity of Items in the Cart", __FILE__,
		     __LINE__);

		# As the script goes through the cart, it will split each
		# row into its database fields placing them as elements in
		# @database_row.  It will then grab the unique cart row
		# number and subsequently replace it in the array.
		#
		# The script needs this number to check the current line
		# against the list of items to be modified. Recall that
		# this list will be made up of all the cart items which
		# are being modified.
		#
		# The script also grabs the current quantity of that row.
		# Since it is not yet sure if it wants the current
		# quantity, it will hold off on adding it back to the
		# array.  Finally, the script chops the newline character
		# off the cart row number.

  while (<CART>)
    {
    @database_row = split (/\|/, $_);
    $cart_row_number = pop (@database_row);
    push (@database_row, $cart_row_number);
    $old_quantity = shift (@database_row);

    chop $cart_row_number;

		# Next, the script checks to see if the item number
		# submitted as form data is equal to the number of the
		# current database row.
 
    foreach $item (@modify_items)
      {
      if ($item eq $cart_row_number)
        {

		# If so, it means that the script must change the quantity
		# of this item.  It will append this row to the
		# $shopper_row variable and begin creating the modified
		# row.  That is, it will replace the old quantity with the
		# quantity submitted by the client ($form_data{$item}).
		# Recall that $old_quantity has already been shifted off
		# the array.

        $shopper_row .= "$form_data{$item}\|";

		# Now the script adds the rest of the database row to
		# $shopper_row and sets two flag variables.
		#
		# $quantity_modified lets us know that the current row
		# has had a quantity modification for each iteration of
		# the while loop.  

        foreach $field (@database_row)
          {
          $shopper_row .= "$field\|";
          }

        $quantity_modified = "yes";
        chop $shopper_row; # Get rid of last pipe symbol but not the
			   # newline character

        } # End of if ($item eq $cart_row_number)
      } # End of foreach $item (@modify_items)

		# If the script gets this far and $quantity_modified has
		# not been set to "yes", it knows that the above routine
		# was skipped because the item number submitted from the
		# form was not equal to the curent database id number. 
		#
		# Thus, it knows that the current row is not having its
		# quantity changed and can be added to $shopper_row as is.
		# Remember, we want to add the old rows as well as the new
		# modified ones.

      if ($quantity_modified ne "yes")
        {
        $shopper_row .= $_;
        }

		# Now the script clears out the quantity_modified variable
		# so that next time around it will have a fresh test.

      $quantity_modified = "";

    } # End of while (<CART>)
  close (CART);

		# At this point, the script has gone all the way through
		# the cart.  It has added all of the items without
		# quantity modifications as they were, and has added all
		# the items with quantity modifications but made the
		# modifications.
		# 
		# The entire cart is contained in the $shopper_row
		# variable.  
		#
		# The actual cart still has the old values, however.  So
		# to change the cart completely the script must overwrite
		# the old cart with the new information and send the
		# client back to the view cart screen with the
		# display_cart_contents subroutine which will be discussed
		# later. Notice the use of the write operator (>) instead
		# of the append operator (>>).

  open (CART, ">$sc_cart_path") || 
     &file_open_error("$sc_cart_path",
                      "Modify Quantity of Items in the Cart", __FILE__,
		      __LINE__);

  print CART "$shopper_row";
  close (CART);

  &display_cart_contents;
  } # End of if ($form_data{'submit_change_quantity'} ne "")

#######################################################################
#                 Output Delete Item Form                             #
#######################################################################

		# The output_delete_item_form subroutine is responsible
		# for displaying the HTML form which the customer can use
		# to delete items from their cart.  It takes no arguments
		# and is called with the following syntax:
		#
		# &output_delete_item_form;

sub output_delete_item_form
  {

		# As it did when it printed the modification form, the
		# script uses several subroutines in web_store_html_lib.pl
		# to generate the header, body and footer of the delete
		# form.

  &standard_page_header("Delete Item");
  &display_cart_table("delete");
  &delete_form_footer;
  } # End of if ($form_data{'delete_item'} ne "")

#######################################################################
#                 Delete Item From Cart                               #
#######################################################################

		# The job of delete_from_cart is to take a set of items
		# submitted by the user for deletion and actually delete
		# them from the customer's cart.  The subroutine takes no
		# arguments and is called with the following syntax:
		#
		# &delete_from_cart;

sub delete_from_cart
  {

		# As with the modification routines, the script first 
		# checks for valid entries. This time though it only needs
		# to make sure that it filters out the extra form
		# keys rather than make sure that it has a positive
		# integer value as well because unlike with a text entry,
		# clients have less ability to enter bad values with
		# checkbox submit fields.

  @incoming_data = keys (%form_data);
  foreach $key (@incoming_data)
    {

		# We still want to make sure that the key is a cart row
		# number though and that it has a value associated with
		# it. If it is actually an item which the user has asked to
		# delete, the script will add it to the delete_items
		# array.

    unless ($key =~ /[\D]/)
      {
      if ($form_data{$key} ne "")
        {
        push (@delete_items, $key);
        }
      } # End of unless ($key =~ /[\D]/...
    } # End of foreach $key (@incoming_data)

		# Once the script has gone through all the incomming form
		# data and collected the list of all items to be deleted,
		# it opens up the cart and gets the $cart_row_number,
		# $db_id_number, and $old_quantity as it did in the
		# modification routines previously.

  open (CART, "$sc_cart_path") || 
     &file_open_error("$sc_cart_path", 
                      "Delete Item From Cart", __FILE__, __LINE__);

  while (<CART>)
    {
    @database_row = split (/\|/, $_);
    $cart_row_number = pop (@database_row);
    $db_id_number = pop (@database_row);
    push (@database_row, $db_id_number);
    push (@database_row, $cart_row_number);
    chop $cart_row_number;
    $old_quantity = shift (@database_row);

		# Unlike modification however, for deletion all we need to
		# do is check to see if the current database row matches
		# any submitted item for deletion.  If it does not match
		# the script adds it to $shopper_row.  If it is equal,
		# it does not. Thus, all the rows will be added to
		# $shopper_row except for the ones that should be deleted.

    $delete_item = "";
    foreach $item (@delete_items)
      {
      if ($item eq $cart_row_number)
        {
        $delete_item = "yes";
        }
      } # End of foreach $item (@add_items)
    if ($delete_item ne "yes")
      {
      $shopper_row .= $_;
      }
    } # End of while (<CART>)
  close (CART);

		# Then, as it did for modification, the scipt overwrites
		# the old cart with the new information and
		# sends the client back to the view cart page with the
		# display_cart_contents subroutine which will be discussed
		# later.

  open (CART, ">$sc_cart_path") || 
      &file_open_error("$sc_cart_path", 
                      "Delete Item From Cart", __FILE__, __LINE__);

  print CART "$shopper_row";
  close (CART);

  &display_cart_contents;
  } # End of if ($form_data{'submit_deletion'} ne "") 

#######################################################################
#                    Display Products for Sale                        #
#######################################################################

		# display_products_for_sale is used to generate
		# dynamically the "product pages" that the client will
		# want to browse through.  There are two cases within it
		# however.  
		#
		# Firstly, if the store is an HTML-based store, this
		# routine will either display the requested page
		# or, in the case of a search, perform a search on all the
		# pages in the store for the submitted keyowrd.
		#
		# Secondly, if this is a database-based store, the script
		# will use the create_html_page_from_db to output the
		# product page requested or to perform the search on the
		# database.
		#
		# The subroutine takes no arguments and is called with the
		# following syntax:
		#
		# &display_products_for_sale;

sub display_products_for_sale
  {

		# The script first determines which type of store this is.
		# If it turns out to be an HTML-based store, the script
		# will check to see if the current request is a keyword
		# search or simply a request to display a page.  If it is
		# a keyword search, the script will require the html
		# search library and use the html_search subroutine with
		# in it to perform the search.

  if ($sc_use_html_product_pages eq "yes")
    {
    if ($form_data{'search_request_button'} ne "")
      {
      &standard_page_header("Search Results");
      require "$sc_html_search_routines_library_path";
      &html_search;
      &html_search_page_footer;
      exit;
      }

		# If the store is HTML-based and there is no current
		# keyword however, the script simply displays the page as
		# requested with display_page which will be discussed
		# shortly.
	 
    &display_page("$sc_html_product_directory_path/$page",
  	  	  "Display Products for Sale", __FILE__, __LINE__);
    }

		# On the other hand, if $sc_use_html_product_pages was set to
		# no, it means that the admin wants the script to generate
		# HTML product pages on the fly using the format string
		# and the raw database rows.  The script will do so
		# using the create_html_page_from_db subroutine which will
		# be discussed next.

  else
    {
    &create_html_page_from_db;
    }
  }

#######################################################################   
#                   create_html_page_from_db Subroutine               #  
#######################################################################   

		# create_html_page_from_db is used to genererate the
		# navigational interface for database-base stores.  It is
		# used to create both product pages and "list of products"
		# pages.  The subroutine takes no arguments and is called
		# with the following syntax:
		#
		# &create_html_page_from_db;

sub create_html_page_from_db
  {

		# First, the script defines a few working variables which
		# will remain local to this subroutine.

  local (@database_rows, @database_fields, @item_ids, @display_fields);
  local ($total_row_count, $id_index, $display_index);
  local ($row, $field, $empty, $option_tag, $option_location, $output);

		# Next the script checks to see if there is actually a
		# page which must be displayed.  If there is a value for
		# the page variable incoming as form data, (ie: list of 
		# product page) the script will simply display that page
		# with the display_page subroutine and exit.

  if ($page ne "" && $form_data{'search_request_button'} eq "" &&
      $form_data{'continue_shopping_button'} eq "")
    {
    &display_page("$sc_html_product_directory_path/$form_data{'page'}",   
                  "Display Products for Sale", __FILE__, __LINE__);
    exit;
    }
		
		# If there is no page value, then the script knows that it
		# must generate a dynamic product page using the value of
		# the product form variable to query the database.
		#
		# First, the script uses the product_page_header
		# subroutine in order to dynamically generate the product
		# page header.  We'll pass to the subroutine the value of
		# the page we have been asked to display so that it can
		# display something useful in the <TITLE></TITLE> area.
		#
		# The product_page_header subroutine is located in
		# web_store_html_lib.pl and $sc_product_display_title is
		# defined in the setup file.

  &product_page_header($sc_product_display_title);

    if ($form_data{'add_to_cart_button'} ne "" &&
	$sc_shall_i_let_client_know_item_added eq "yes")
      {
      print "$sc_item_ordered_message";
      }

		# Next the database is querried for rows containing the
		# value of the incoming product variable in the correct
		# category as defined in web_store.setup.  The script uses
		# the submit_query subroutine in web_store_db_lib.pl
		# passing to it a reference to the list array 
		# database_rows.  
		# 
		# submit_query returns a descriptive status message  
		# if there was a problem and a total row count
		# for diagnosing if the maximum rows returned
                # variable was exceeded.

  if (!($sc_db_lib_was_loaded =~ /yes/i)) {
    &require_supporting_libraries (__FILE__, __LINE__, 
                                  "$sc_db_lib_path"); 
  }

  ($status,$total_row_count) = &submit_query(*database_rows);

		# Now that the script has the database rows to be
		# displayed, it will display them.
		#
		# Firstly, the script goes through each database row
		# contained in @database_rows splitting it into it's
		# fields.
		#
		# For the most part, in order to display the database
		# rows, the script will simply need to take each field
		# from the database row and substitute it for a %s in the
		# format string defined in web_store.setup.  
		#
		# However, in the case of options which will modify a
		# product, the script must grab the code from an options
		# file.
		#
		# The special way that options are denoted in the database
		# are by using the format %%OPTION%%option.html in the
		# data file.  This string includes two important bits of
		# information.  
		#
		# Firstly, it begins with %%OPTION%%.  This is a flag
		# which will let the script know that it needs to deal
		# with this database field as if it were an option.  When
		# it sees the flag, it will then look to the bit after the
		# flag to see which file it should load. Thus, in this
		# example, the script would load the file option.html for
		# display.
		#
		# Why go through all the trouble?  Well basically, we need
		# to create a system which will handle large chunks of
		# HTML code within the database that are very likely to be
		# similar.  If there are options on product pages, it is
		# likely that they are going to be repeated fairly
		# often.  For example, every item in a database might have
		# an option like tape, cd or lp.  By creating one
		# options.html file, we could easily put all the code into
		# one shared location and not need to worry about typing
		# it in for every single database entry.

foreach $row (@database_rows)
  {
  @database_fields = split (/\|/, $row);
  foreach $field (@database_fields)
    {

		# For every field in every database row, the script simply
		# checks to see if it begins (^) with %%OPTION%%.  If so,
		# it splits out the string into three strings, one
		# empty, one equal to OPTION and one equal to the location
		# of the option to be used.  Then the script resets the
		# field to null because it is about to overwrite it.

    if ($field =~ /^%%OPTION%%/)
      {
      ($empty, $option_tag, $option_location) = split (/%%/, $field);
      $field = "";

		# The option file is then opened and read.  Next, every
		# line of the option file is appended to the $field
		# variable and the file is closed again.  However, the
		# current product id number is substituted for the
		# %%PRODUCT_ID%% flag

      open (OPTION_FILE, "$sc_options_directory_path/$option_location") ||
           &file_open_error ("$sc_options_directory_path/$option_location",
			     "Display Products for Sale", __FILE__,
			     __LINE__);

      while (<OPTION_FILE>)
        {
        s/%%PRODUCT_ID%%/$database_fields[$sc_db_index_of_product_id]/g;
        $field .= $_;
        }
      close (OPTION_FILE);
      } # End of if ($field =~ /^%%OPTION%%/)
    } # End of foreach $field (@database_fields)

		# Finally, the database fields (including the option field
		# which has been recreated) are stuffed into the format
		# string, $sc_product_display_row and the entire formatted
		# string is printed to the browser along with the footer.
		#
		# First, however, we must format the fields correctly.
		# Initially, @display_fields is created which contains the
		# values of every field to be displayed, including a
		# formatted price field.

  @display_fields = ();
  @temp_fields = @database_fields;
  foreach $display_index (@sc_db_index_for_display) 
    {
    if ($display_index == $sc_db_index_of_price)
      {  
      $temp_fields[$sc_db_index_of_price] =
        &display_price($temp_fields[$sc_db_index_of_price]);
      }
    push(@display_fields, $temp_fields[$display_index]);
    }

		# Then, the elements of the NAME field are created so that
		# customers will be able to specify an item to purchase.
		# We are careful to substitute double quote marks ("), and
		# greater and less than signs (>,<) for the tags ~qq~,
		# ~gt~, and ~lt~. The reason that this must be done is so
		# that any double quote, greater than, or less than
		# characters used in URL strings can be stuffed safely
		# into the cart and passed as part of the NAME argumnet in
		# the "add item" form.  Consider the following item name
		# which must include an image tag.
		#
		# <INPUT TYPE = "text" 
		#	 NAME = "item-0010|Vowels|15.98|The letter A|~lt~IMG SRC = ~qq~Html//images/010/a.jpg~qq~ ALIGN = ~qq~left~qq~~gt~"
		#
		# Notice that the URL must be edited. If it were not, how
		# would the browser understand how to interpret the form
		# tag?  The form tag uses the double quote, greater
                # than, and less than characters in its own processing.

  @item_ids = ();
  foreach $id_index (@sc_db_index_for_defining_item_id) 
    {
    $database_fields[$id_index] =~ s/\"/~qq~/g;
    $database_fields[$id_index] =~ s/\>/~gt~/g;
    $database_fields[$id_index] =~ s/\</~lt~/g;
    push(@item_ids, $database_fields[$id_index]);
    }

		# Finally, $sc_product_display_row is created with the two
		# arrays using printf to apply the formatting.
		# 


  printf ($sc_product_display_row, 
          join("\|",@item_ids), 
          @display_fields);
  } # End of foreach $row (@database_rows)

  &product_page_footer($status,$total_row_count);
  exit;
  }

#######################################################################   
#                   display_cart_contents Subroutine                  #  
#######################################################################   

		# display_cart_contents is used to display the current
		# contents of the customer's cart.  It takes no arguments
		# and is called with the following syntax:
		#
		# &display_cart_contents;

sub display_cart_contents
  {

		# The subroutine begins by defining some working variables
		# as local to the subroutine.

  local (@cart_fields);
  local ($field, $cart_id_number, $quantity, $display_number,
	 $unformatted_subtotal, $subtotal, $unformatted_grand_total,
	 $grand_total);
    
		# Next, as when we created the modification and deletion
		# forms for cart manipulation, we will use the routines in
		# web_store_html_lib.pl to generate the header, body and
		# footer of the cart page.  However, unlike with the
		# modification and deletion forms, we will not need an
		# extra table cell for the checkbox or text field.  Thus,
		# we will not pass anything to display_cart_table.  We
		# will simply get a table representing the current
		# contents of the customer's cart.

  &standard_page_header("View/Modify Cart");    
  &display_cart_table("");
  &cart_footer;
  exit;
  } # End of sub display_cart_contents

#######################################################################
#                    file_open_error Subroutine                       #
#######################################################################

		# If there is a problem opening a file or a directory, it
		# is useful for the script to output some information
		# pertaining to what problem has occurred.  This
		# subroutine is used to generate those error messages.
		# 
		# file_open_error takes four arguments: the file or
		# directory which failed, the section in the code in which
		# the call was made, the current file name and
		# line number, and is called with the following syntax:
		#
		# &file_open_error("file.name", "ROUTINE", __FILE__,
		#		   __LINE__);

sub file_open_error
  {

		# The subroutine simply uses the update_error_log
		# subroutine discussed later to modify the error log and
		# then uses CgiDie in cgi-lib.pl to gracefully exit the
		# application with a useful debugging error message sent
		# to the browser window.

  local ($bad_file, $script_section, $this_file, $line_number) = @_;
  &update_error_log("FILE OPEN ERROR-$bad_file", $this_file, $line_number);
  &CgiDie ("I am sorry, but I was not able to access $bad_file in the
 	$script_section routine of $this_file at line number $line_number.
	Would you please make sure the path is correctly defined in
	web_store.setup and that the permissions are correct.")
  }


#######################################################################
#                     display_page Subroutine                         #
#######################################################################

		# display_page is used to filter HTML pages through the
		# script and display them to the browser window. 
		#
                # display_page takes  four arguments: the file or      
                # directory which failed, the section in the code in which
                # the erroneous call was made, the current file name and
                # line number, and is called with the following syntax:
                #
                # &file_open_error("file.name", "ROUTINE", __FILE__,
                #                  __LINE__);
                #
                # (notice the two special Perl variables __FILE__, which
                # equals the current filename, and __LINE__ which equals
                # the current line number).

sub display_page
  {
  local ($page, $routine, $file, $line) = @_;

		# the subroutine begins by opening the requested file for
		# reading, exiting with file_open_error if there is a
		# problem as usual.

  open (PAGE, "$page") ||
        &file_open_error("$page", "$routine", $file, $line);
		# It then reads in the file one line at a time.  However,
		# on every line it looks for special tag sequences which
		# it knows it must modify in order to maintain the state
		# information necessary for the workings of this script.
		# Specifically, every form must include a page and a
		# cart_id value and every url hyperlink must have a
		# cart_id value added to it.
		#
		# Raw administratively pre-designed HTML pages must
		# include the follwoing tag lines if they are to filter
		# properly and pass along this necesary state information.
		#
		# All forms must include two hidden field lines with the
		# "tags" tobe substituted for imbedded as follows:
		#
		# <INPUT TYPE = "hidden" NAME = "cart_id" VALUE = "%%cart_id%%">
		# <INPUT TYPE = "hidden" NAME = "page" VALUE = "%%page%%">
		#
		# When the script reads in these lines, it will see the
		# tags "%%cart_id%%" and"%%page%%" and substitute them for
		# the actual page and cart_id values which came in as form
		# data.
		#
		# Similarly it might see the following URL reference:
		#
		# <A HREF = "web_store.cgi?page=Letters.html&cart_id=">
		#
		# In this case, it will see the cartid= tag and
		# substitute in the correct and complete
		# "cartid=some_number".

  while (<PAGE>)
    {  
    s/cart_id=/cart_id=$cart_id/g;
    s/%%cart_id%%/$cart_id/g;
    s/%%page%%/$form_data{'page'}/g;

		# Next, it checks to see if the add_to_cart_button button
		# has been clicked.  if so, it means that we have just
		# added an item and are returning to the display of the
		# product page.  In this case, we will sneak in an  addition
		# confirmation message right after the <FORM> tag line.

    if ($form_data{'add_to_cart_button'} ne "" &&
        $sc_shall_i_let_client_know_item_added eq "yes")
      {
      if ($_ =~ /<FORM/)
        {
        print "$_";
        print "$sc_item_ordered_message";
        }
      }

		# If it is any other line, simply print it out to the
		# browser window.  Once we have gone through all of the
		# lines in the file, the HTML will be complete and
		# filtered.

    print $_;
    }
  
  close (PAGE);
  } # End of sub display_page

#################################################################
#                  counter Subroutine                           #
#################################################################

		# counter is used to keep track of unique cart database id
		# numbers so that every item in every cart will be
		# uniquely identifiable.
		#
		# The subroutine takes three arguments, the name of the
		# counter file, the current filename and the current line
		# number and is called with the following syntax:
		#
		# &counter ($sc_counter_file_path, __FILE__, __LINE__);
		#
		# Where $sc_counter_file_path is defined in
		# web_store.setup

sub counter
  {

		# First, the subroutine assigns to the local variable
		# $counter_file, the filename that we passed to this
		# subroutine from the main script.  It also defines $file,
		# $line and $item_number as local. 
        
  local($counter_file, $file, $line) = @_; 
  local ($item_number);

		# Next, the script checks to see if the counter file
		# exists. If it does not, then it attempts to create it.

  if (!(-e $counter_file)) 
    {
    open(COUNTER_FILE, ">$counter_file") ||
        &file_open_error("$counter_file", "Counter", $file, $line);
    print COUNTER_FILE "1\n";
    close(COUNTER_FILE);
    }

		# Next, the script opens the counter file.  If the
		# counter file cannot be opened, however, 
		# &file_open_error is called as usual.

  open (COUNTER_FILE, "$counter_file") || 
       &file_open_error("$counter_file", "Counter", $file, $line);
    
		# Then, the script checks to see what number the counter
		# is currently on and assign that value to $item_number.

  while (<COUNTER_FILE>)
    {
    $item_number = "$_";
    }
  close (COUNTER_FILE);
        
		# It then adds one to that number, changes the counter
		# file to reflect the incrementation, returns the number to
		# the main script, and closes the counter file.
     
  $item_number += 1;
  open (NOTE, ">$counter_file") || 
      &file_open_error("$counter_file", "Counter", $file, $line); 
  print NOTE "$item_number\n";
  close (NOTE);
  return $item_number;
  } # End of sub counter

#################################################################
#                  update_error_log Subroutine                  #
#################################################################

		# update_error_log is used to append to the error log if
		# there has been a process executing this script and/or
		# email the admin. 
		#
		# The subroutine takes three arguments, the type of error,
		# the current filename and current line number and is
		# called with the following syntax:
		#
		# &update_error_log("WARNING", __FILE__, __LINE__);

sub update_error_log
  {
	
		# The subroutine begins by assigning the incoming
		# argumnets to local variables and defining some other
		# local variables to use during its work.
		#
		# $type_of_error will be a text string explaining what
		# kind of error is being logged.
		#
		# $file_name is the current filename of this script.
		#
		# $line_number is the line number on which the error
		# occurred.  Note that it is essential that the line
		# number, stored in __LINE__ be passed through all levels
		# of subroutines so that the line number value will truly
		# represent the line number of the error and not the
		# line number of some subroutine for error handling.

  local ($type_of_error, $file_name, $line_number) = @_;
  local ($log_entry, $email_body, $variable, @env_vars);

		# The list of the HTTP environment variables are culled
		# into the @env_vars list array and get_date is used to
		# assign the current date to $date

  @env_vars = keys(%ENV);
  $date = &get_date;

		# Now, if the admin has instructed the script to log
		# errors by setting $sc_shall_i_log_errors in
		# web_store.setup, the script will create an error log
		# entry.

  if ($sc_shall_i_log_errors eq "yes")
    {

		# First, the new log entry row is created as a pipe
		# delimited list beginning with the error type, filename,
		# line number and current date.

    $log_entry = "$type_of_error\|FILE=$file_name\|LINE=$line_number\|";
    $log_entry .= "DATE=$date\|";

		# Then the error log file is opened securely by using the
		# lock file routines in get_file_lock discussed later.

    &get_file_lock("$sc_error_log_path.lockfile");
    open (ERROR_LOG, ">>$sc_error_log_path") || &CgiDie ("The Error Log
						could not be opened");

		# Now, the script adds to the log entry row, the values
		# associated with all of the HTTP environment variables
		# and prints the whole row to the log file which it then
		# closes and opens for use by other instances of this
		# script by removing the lock file.

    foreach $variable (@env_vars)
      {
      $log_entry .= "$ENV{$variable}\|";
      }  

    print ERROR_LOG "$log_entry\n";
    close (ERROR_LOG);  
    &release_file_lock("$sc_error_log_path.lockfile");

    } # End of if ($sc_shall_i_log_errors eq "yes")

		# Next, the script checks to see if the admin has
		# instructed it to also send an email error notification
		# to the admin by setting the $sc_shall_i_email_if_error
		# in web_store.setup.
		#
		# If so, it prepares an email with the same info contained
		# in the log file row and mails it to the admin using the
		# send_mail routine in mail-lib.pl.  Note that a common
		# sourse of email errors lies in the admin not setting the
		# correct path for sendmail in mail-lib.pl on line 42.
		# Make sure that you set this variable there if you are
		# not receiving your mail and you are using the sendmail
		# version of the mail-lib package.
  if ($sc_shall_i_email_if_error eq "yes")
    {
    $email_body = "$type_of_error\n\n";
    $email_body .= "FILE = $file_name\n";
    $email_body .= "LINE = $line_number\n";
    $email_body .= "DATE=$date\|"; 
    foreach $variable (@env_vars)
      {
      $email_body .= "$variable = $ENV{$variable}\n";
      }  
    &send_mail("$sc_admin_email", "$sc_admin_email", "Web Store Error",
  	       "$email_body");
    } # End of if ($sc_shall_i_email_if_error eq "yes")

  }

#################################################################
#                      get_date Subroutine                      #   
#################################################################

		# get_date is used to get the current date and time and
		# format it into a readable form.  The subroutine takes no
		# arguments and is called with the following syntax:
		#
		# $date = &get_date;
		#
		# It will return the value of the current date, so you
		# must assign it to a variable in the calling routine if
		# you are going to use the value.

sub get_date
  {

		# The subroutine begins by defining some local working
		# variables

  local ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst,$date);
  local (@days, @months); 
 
  @days = ('Sunday','Monday','Tuesday','Wednesday','Thursday',
           'Friday','Saturday');
  @months = ('January','February','March','April','May','June','July',
             'August','September','October','November','December');

		# Next, it uses the localtime command to get the current
		# time, from the value returned by the time
		# command, splitting it into variables.

  ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

		# Then the script formats the variables and assign them to
		# the final $date variable.  Note that $sc_current_century
		# is defined in web_store.setup.  Since the 20th centruy
		# is really 1900-1999, we'll need to subtract 1 from this
		# value in order to format the year correctly.

  if ($hour < 10) 
    { 
    $hour = "0$hour"; 
    }
  if ($min < 10) 
    { 
    $min = "0$min"; 
    }
  if ($sec < 10) 
    { $sec = "0$sec"; 
    }
  $year = ($sc_current_century-1) . "$year";
  $date = "$days[$wday], $months[$mon] $mday, $year at $hour\:$min\:$sec";
  return $date;
  }       

#################################################################
#                     display_price Subroutine                  #
#################################################################

		# display_price is used to format the price string so that
		# the store can take into account differing methods for
		# displaying prices. For example, some countries use
		# "$xxx.yyy".  Others may use "xx.yy UNIT".  This
		# subroutine will  use the $sc_money_symbol_placement and
		# the $sc_money_symbol variables defined in
		# web_store.setup to format the entire price string for
		# display.  The subroutine takes one argument, the price
		# to be formatted, and is called with the following
		# syntax:
		#
		# $price = &display_price(xx.yy);
		#
		# Where xx.yy is some number like 23.99.
		#
		# Note that the main routine calling this subroutine must
		# prepare a variable for the returned formatted price to
		# be assigned to.

sub display_price
  {
  local ($price) = @_;
  local ($format_price);
        
  if ($sc_money_symbol_placement eq "front")
    {
    $format_price = "$sc_money_symbol $price";
    }
  else
    {
    $format_price = "$price $sc_money_symbol";
    }

  return $format_price;
  }

#######################################################################
#                            get_file_lock                            #
#######################################################################

		# get_file_lock is a subroutine used to create a lockfile.
		# Lockfiles are used to make sure that no more than one
		# instance of the script can modify a file at one time.  A
		# lock file is vital to the integrity of your data.
		# Imagine what would happen if two or three people
		# were using the same script to modify a shared file (like
		# the error log) and each accessed the file at the same
		# time.  At best, the data entered by some of the users
		# would be lost.  Worse, the conflicting demands could
		# possibly result in the corruption of the file.
		#
		# Thus, it is crucial to provide a way to monitor and
		# control access to the file.  This is the goal of the
		# lock file routines.  When an instance of this script
		# tries to  access a shared file, it must first check for
		# the existence of a lock file by using the file lock
		# checks in get_file_lock.
		#
		# If get_file_lock determines that there is an existing
		# lock file, it instructs the instance that called it to
		# wait until the lock file disappears.  The script then
		# waits and checks back after some time interval.  If the
		# lock file still remains, it continues to wait until some 
		# point at which the admin has given it permissios to just
		# overwrite the file because some other error must have
		# occurred.
		#
		# If, on the other hand, the lock file has dissappeared,
		# the script asks get_file_lock to create a new lock file
		# and then goes ahead and edits the file.
		#
		# The subroutine takes one argumnet, the name to use for
		# the lock file and is called with the following syntax:
		#
		# &get_file_lock("file.name");

sub get_file_lock 
  {
  local ($lock_file) = @_;
  local ($endtime);
  $endtime = 20;
  $endtime = time + $endtime;

		# We set endtime to wait 20 seconds.  If the lockfile has
		# not been removed by then, there must be some other
		# problem with the file system.  Perhaps an instance of
		# the script crashed and never could delete the lock file.
    
  while (-e $lock_file && time < $endtime) 
    {
    sleep(1);
    }

  open(LOCK_FILE, ">$lock_file") || &CgiDie ("I could not open the lock
				     file");

		# Note: If flock is available on your system, feel free to
		# use it.  flock is an even safer method of locking your
		# file because it locks it at the system level.  The above
		# routine is "pretty good" and it will server for most
		# systems.  But if youare lucky enough to have a server 
		# with flock routines built in, go ahead and uncomment
		# the next line and comment the one above.

# flock(LOCK_FILE, 2); # 2 exclusively locks the file

  } 

#######################################################################
#                            release_file_lock                        #
#######################################################################

		# release_file_lock is the partner of get_file_lock.  When
		# an instance of this script is done using the file it
		# needs to manipulate, it calls release_file_lock to
		# delete the lock file that it put in place so that other
		# instances of the script can get to the shared file.  It
		# takes one argument, the name of the lock file, and is
		# called with the following syntax:
		#
                # &release_file_lock("file.name");
    
sub release_file_lock 
  {
  local ($lock_file) = @_;
    
# flock(LOCK_FILE, 8); # 8 unlocks the file

		# As we mentioned in the discussion of get_file_lock,
		# flock is a superior file locking system.  If your system
		# has it, go ahead and use it instead of the hand rolled
		# version here.  Uncomment the above line and comment the
		# two that follow.

  close(LOCK_FILE);
  unlink($lock_file);
  } 

#######################################################################
#                            format_price                             #
#######################################################################

		# format_price is used to format prices to two decimal
		# places. It takes one argumnet, the price to be formatted
		# and is called with the following syntax:
		#
		# $price =&format_price(xxx.yyyyy);
		#
		# Notice that the main calling routine must assign the
		# returned formatted price to some variable for its own
		# use.
		#
		# Also notice that this routine takes a value even if it
		# is longer than two decimal places and formats it with
		# rounding.  Thus, you can utilize price calculations such
		# as 12.99 * 7.985 (where 7.985 might be some tax value.
  
sub format_price
  {

		# The incoming price is set to a local variables and a few
		# wroking local variables are defined.

  local ($unformatted_price) = @_;
  local ($formatted_price);

		# The script then uses the rounding method in EXCEL. If
		# the 3rd decimal place is > 4, then we round the 2nd
		# decimal place up 1. Otherwise, we leave the number
		# alone.  Notice that we will use the substr function to
		# pull off the last value in the three decimal place
		# number and compare it using the EXCEL logic.
		#
		# Basically, the routine uses the rounding rules of
		# sprintf.

		# The unformatted_price is rounded to 
		# to two decimal places and returned to the calling
		# routine.
  $formatted_price = sprintf ("%.2f", $unformatted_price);
  return $formatted_price;
  }