#!/usr/bin/perl
# Name: Selena Sol's Electronic Outlet (HTML Version)
# Version: 3.0a
# Last Modified: 10-23-96
# Copyright Info: This application was written by Selena Sol
# (selena@eff.org, http://www.eff.org/~erict) 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 me know where it goes so that I 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.
#######################################################################
# Print http Header. #
#######################################################################
# First tell Perl to bypass the buffer so that information generated by
# the CGI will be sent immediately to the browser. The line $! = 1; does
# this. Then, print out the http header so that we will easily be able to
# debug and so that the browser will not time us out.
$| = 1;
print "Content-type: text/html\n\n";
#######################################################################
# Require Libraries. #
#######################################################################
# Now let's require some of the supporting files that this script will
# take advantage of. The web_store.setup defines many global variables
# for this script relative to the local server. cgi-lib.pl is used to
# parse incoming form data. cgi-lib.sol is used to manipulate the counter
# file. And mail-lib.pl is used to send mail to the store administrator
# when someone places an order.
require "./html_web_store.setup";
require "$cgi_lib_pl";
require "$cgi_lib_sol";
require "$mail_lib_pl";
#######################################################################
# Gather Form Data. #
#######################################################################
# Use cgi-lib.pl to parse the incoming form data and tell cgi-lib to
# prepare that information in the associative array %form_data
&ReadParse(*form_data);
# However, we need to be careful about one possible security hole. 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 in this, 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 mught reassign page from, say, vowel.html to
# ../../../etc/passwd! As you can imagine, this would end up displaying
# your password file to the browser window. Thus, we need to make sure
# that only HTML pages can be displayed by the store.
#
# The following routine simply check to make sure that if the page
# variable is not either .htm or .html, it will exit and send a warning.
if (($form_data{'page'} =~ /html$/ || $form_data{'page'} =~ /htm$/) ||
($form_data{'page'} eq ""))
{
# Do nothing...things are cool...go on with the script.
}
else
{
print "I am sorry, but you may only use this program to view HTML
pages. If you absolutely MUST view non-HTML pages within your
store, you must modify lines 67-71, but be careful, doing so may
make your system vulnerable to hackers. The current value of
page is: \"$form_data{'page'}\"
(NOTE: If there is nothing
between the quote marks and you are not a hacker, you have not
assigned yourself a valid page which means your HTML coding is
probably wrong)";
exit;
}
#######################################################################
# Assign a Shopping Cart. #
#######################################################################
# Next, let's clean up our carts directory. There is no need to keep
# old carts around after the clients are long gone. So we will grab a
# listing of all of the client created shoppping carts in the
# Client_carts directory. We will first open the directory which has the
# carts, then we will read that directories contents and use grep to grab
# every file with the extension .cart. Then we will close the directory.
opendir (USER_CARTS, "$cart_directory") || &open_error($cart_directory);
@carts = grep(/\.cart/,readdir(USER_CARTS));
closedir (USER_CARTS);
# Now, for every cart in the directory...
foreach $cart (@carts)
{
# If it is older than half a day, delete it
if (-M "$cart_directory/$cart" > .5)
{
unlink("$cart_directory/$cart");
}
}
# Now we can add the new users cart.
# If we have not received a shopping cart id number as form data, it means
# that the clieent has not yet received a unique shopping cart.
# So, assign the client a unique shopping cart file that they will use
# while they are browsing the store. First we'll generate a random
# (srand + rand) 8 digit (100000000) integer (int) and then add to that
# the current process id ($$). We will append the process id to the end of
# the integer by using .= instead of =
if ($form_data{'cart_id'} eq "")
{
srand (time|$$);
$cart_id = int(rand(10000000));
$cart_id .= ".$$";
# Now create a new cart for the shopper.
open (CART, ">$cart_directory/$cart_id.cart") || &CgiDie("I am sorry, I
was not able to create the users cart in the assign a shopping
cart routine. The value I have for the cart is
$cart_directory/$cart_id.cart. Would you please make sure the
path and permissions are correct.");
}
# If there was a shopping cart id number coming in as form data, let us
# simply rename the variable to $cart_id so that we have a standardized
# variable name. Note, that once the client has recieved their own cart,
# that cart number MUST be passed from screen to screen as url encoded
# information or as a hidden form variable so that the clkient does not
# lose their cart.
else
{
$cart_id = "$form_data{'cart_id'}";
}
#######################################################################
# Output Frontpage. #
#######################################################################
# Now that they have received a cart, it is time to display to them the
# first page of the store. We will assume that the site administrator has
# created a frontpage and set that locatioon equal to $frontpage_file in
# the store specific setup file (or default).
# There are two cases in which we would want to display the frontpage.
# firstly, if the user has specifically asked to "return to the frontpage"
# and secondly, if no category has been assigned. That is, the main
# purpose of the frontpage is to query the client for which category they
# are interested in browsing (like which aisle they want to go down).
# The main purpose of the frontpage is to present all these options and to
# create hyperlinks which will define the categories.
if ($form_data{'page'} eq "" ||
$form_data{'return_to_frontpage'} ne "")
{
# So, if we have been asked for the frontpage, let's open it up and
# display it line by line.
open (FRONTPAGE, "$frontpage_file") || &CgiDie ("I am sorry, but I was
not able to load $frontpage_file in the Output Frontpage routine.
Would you please make sure the path and permissions are
correct.");
while ()
{
# However, we are going to want to do a couple of things to the page.
# Firstly, we are going to need to make sure that the clients unique cart
# id gets passed from the frontpage to any successive pages. The way we
# do this is by url encoding. We are going to have the user click on a
# link that says .
# There are two things of note in that line. Firstly, we will be defining
# a category in those links...this is essential. Secondly, we will be
# adding a half done variable, userid. This userid= is really just a tag
# which this script will be looking for. Anytime (/g) it sees that, it
# will substitute (s/) occurances of cart_id= with cart_id= the actual cart id
# of the user.
# Since we do not know ahead of time what the cart id is going to be, we
# cannot actually hard code it in to the HTML. Thus, we create the
# cart_id= tag so that this script can finish off the job on the fly.
# We'll also tag on the current store setup file since we are not passing
# it as a hidden variable.
s/cart_id=/cart_id=$cart_id/g;
print "$_";
}
# Close out the frontpage file and quit...it is time to let the client
# decide which category she wants to browse.
close (FRONTPAGE);
exit;
} # End of if ($form_data{'page_to_view'} eq "")
#######################################################################
# Add to Shopping Cart #
#######################################################################
# Once the client has decided to puirchase an item from one of the
# categories, they will have added a quantity to the text box and hit
# submit. (The displaying category routines are at the tend of this
# script) So it is time to add a new item to their cart.
if ($form_data{'add_to_cart'} ne "")
{
# Begin by using the %form_data array given to us by cgi-lib.pl. We'll
# take all of the keys of the form_data associative array and drop them in
# the @incoming data array. A key is like a variable name whereas a
# value is the value associated with that variable name. Since each of
# the text boxes which the client could enter quantities were associated
# with the database id number of the item that they accompany, (as defined
# in the displaying category view at the end of this script), the HTML
# should read for the item with
# database id number 1234 and 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 and 5678 is associated with 9.
@items_ordered = keys (%form_data);
# Next we will begin going through the list of incoming items one by one.
foreach $item (@items_ordered)
{
# However, there are some incoming items that we won't care about.
# Specifically, we do not care about cart_id, page, or add_to_cart because
# these are all administartive values set internally by this script.
# They will be coming in as form data...and we will need them for other
# things, just not to fill up the user's cart. Similarly, 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 ne "cart_id" &&
$item ne "page" &&
$item ne "add_to_cart" &&
$form_data{$item} ne "")
{
# Then we must separate out the items that have been ordered from the
# options which modify those items. if $item begins with the word option,
# which we set specifically in the HTML file, we will add (push) that item
# to the array (list) called @options
if ($item =~ /^option/i)
{
push (@options, $item);
}
# On the other hand, if it is not an option, let's add it to the array
# @items_ordered_with_options, but let's add both the item and its value.
# The value wil be a quantity and the item will be something like
# 0001|12.98|The letter A as defined in the HTML file.
else
{
push (@items_ordered_with_options, "$form_data{$item}\|$item\|");
}
} #End of if ($item ne "cart_id"....
} #End of foreach $item (@items_ordered)
# Now go through the array @items_ordered_with_options one item at a time.
foreach $item_ordered_with_options (@items_ordered_with_options)
{
# 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.
($item_quantity, $item_id_number, $item_price, $item_description) =
split (/\|/, $item_ordered_with_options);
# Then for every option in @options, split up each option into it's
# fields.
foreach $option (@options)
{
($option_marker, $option_number, $option_item_number) = split
(/\|/, $option);
# Once we have done both splits, we can compare the name of the item with
# the name associated with the option. If they are the same, we know that
# this is an option which was meant to enhance this item.
if ($option_item_number eq "$item_id_number")
{
# Since we need to apply this option to this item, let's split out the
# value associated with the option. And append it to $options. Once we
# have gone through all of the options, using .=, we will have one big
# string containing all the options so that we can print them out.
($option_name, $option_price) = split (/\|/,$form_data{$option});
$options .= "$option_name $option_price,";
# But we must also calculate the cost changes with options.
$unformatted_option_grand_total = $option_grand_total +
$option_price;
$option_grand_total = sprintf ("%.2f\n",
$unformatted_option_grand_total);
}
}
# Take off the last comma in options. Look 6 lines up...you se that a
# comma is added to the end of each option...well the last option does not
# need that last comma.
chop $options;
# Now add a space after each comma so the display looks nicer.
$options =~ s/,/, /g;
# Now use the subroutine counter in cgi-lib.sol sending it the location of
# the counter file defined in the setup file. This routine will return
# one variable called $item_number which we can use to identify a shopping
# cart item absolutely. This must be done so that when we modify and
# delete we will know exactly which item to affect.
&GetFileLock("$counter_file.lock");
&counter ($counter_file);
&ReleaseFileLock("$counter_file.lock");
# Finally, do the last price calculations and append every ordered item
# to $cart_row
$unformatted_item_grand_total = $item_price + $option_grand_total;
$item_grand_total = sprintf ("%.2f\n",
$unformatted_item_grand_total);
chop $item_grand_total;
$cart_row .= "$item_quantity\|$item_id_number\|$item_price\|$item_description\|$options\|$item_grand_total\|$item_number\n";
}
# When we are done appending all the items to $cart_row, open the users
# shopping cart and add the new items.
open (CART, ">>$cart_directory/$form_data{'cart_id'}.cart") || &CgiDie
("I am sorry, but I was unable to open the user cart at the end of
the Add to Shopping Cart routine. The value I have for the cart is
$cart_directory/$form_data{'cart_id'}.cart. Would you please
make sure it has a valid path and permissions.");
print CART "$cart_row";
close (CART);
# Now send the client back to the page from whenced they ordered with the
# display_items_for_sale subroutine at the end of this script.
&display_items_for_sale;
exit;
}
#######################################################################
# Output View/Modify Cart Screen #
#######################################################################
# 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 delte or modify. (or maybe she just wants to view her current
# cart items...let's be optimistic!)
if ($form_data{'modify_cart'} ne "")
{
&display_cart_contents;
}
#######################################################################
# Output Modify Quantity Form #
#######################################################################
# If the client has asked to make a quantity modification, we will need to
# give her a form so that she can specify the changes she wants made.
if ($form_data{'change_quantity'} ne "")
{
# Output the header as we just did above.
# Note: We will use the qq! method of printing as discussed in the Output
# Frontpage routine.
&page_header("Change Quantity");
print qq!
New Quantity !;
# Build the table headers..
foreach $field (@display_fields)
{
print qq!$field \n!;
}
print qq!Quantity \nSubtotal \n \n!;
# Open up the client's cart and begin going through it line by line...
open (CART, "$cart_directory/$form_data{'cart_id'}.cart") || &CgiDie
("I am sorry, but I was unable to open the user cart in the Output
Modify Quantity Form routine. The value I have for the cart is
$cart_directory/$form_data{'cart_id'}.cart. Would you please
check the path and the permissions.");
while ()
{
@cart_fields = split (/\|/, $_);
# This time, however, we are going to pop out both the cart specific db
# number as well as the store specific db number and then push them back
# onto the array once we have assigned the values to our variables.
# Make sure to chop off thenewline character for $cart_row_number too
# Then grab the quantity while you are at it, but we won't need to
# unshift that back into the array.
$cart_row_number = pop(@cart_fields);
$db_number = pop(@cart_fields);
$quantity = shift(@cart_fields);
push (@cart_fields, $db_number);
push (@cart_fields, $cart_row_number);
chop $cart_row_number;
# Now begin creating the table based form which the client will be able to
# use to submit quantity changes. First, create a textbox so they can
# enteer a new quantity. In this case, we'll assign the $cart_row_number
# to the name in the input field so that we will later be able to remember
# which item was associated with the quantity change.
# Note: We will use the qq! method of printing as discussed in the Output
# Frontpage routine.
print qq!\n!;
print qq! \n!;
# Then create the table rows and footer as we did above.
foreach $display_number (@display_numbers)
{
if ($cart_fields[$display_number] eq "")
{
$cart_fields[$display_number] = "- ";
}
if ($display_number eq "$price_number")
{
print qq! \$$cart_fields[$display_number] \n!;
}
else
{
print qq!$cart_fields[$display_number] \n!;
}
} # End of foreach $display_number (@display_numbers)
print qq!$quantity \n!;
$unformatted_subtotal =
($quantity*$cart_fields[$price_number]);
$subtotal = sprintf ("%.2f\n", $unformatted_subtotal);
$unformatted_grand_total = $grand_total + $subtotal;
$grand_total = sprintf ("%.2f\n",
$unformatted_grand_total);
print qq!\$$subtotal \n!;
print qq!\n!;
} # End of while ()
close (CART);
print qq!
Pre-shipping Grand Total = \$$grand_total