Приглашаем посетить
db_manager
#!/usr/local/bin/perl
# Name: db_manager.cgi
# Version: 5.0
# Last Modified: 01-31-97
#
# Copyright Information: 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 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.
#######################################################################
# Flush the Perl Buffer. #
#######################################################################
# The script begins by telling the Perl interpreter that
# it should continuously flush its buffer so that text
# from this script is sent directly to the Web Browser.
# We do this to streamline debugging and make sure that
# the script operates with the flow we want it to.
$| = 1;
#######################################################################
# Send out the http Header #
#######################################################################
# Next, the script sends out the http header so that we
# can debug more easily and so that the browser won't time
# the script out if it take too long to process.
print "Content-type: text/html\n\n";
#######################################################################
# Read and Parse Form Data #
#######################################################################
# Then, the script loads some supporting files
# using the subroutine, require_supporting_libraries
# documented at the end of this script. cgi-lib.pl is
# used to read and parse form data. auth-lib.pl is used to
# authenticate users of the database management system.
# db-lib.pl is used to search through the database for
# matches to the users search criteria.
&require_supporting_libraries (__FILE__, __LINE__,
"./Library/cgi-lib.pl",
"./Library/auth-lib.pl",
"./Library/db-lib.pl");
# Now that cgi-lib.pl has been loaded, we can take
# advantage of its subroutines. In particular, we will use
# the ReadParse to read the incoming form data. However,
# the subroutine is sent "form_data" as a parameter so
# that the associative array of form keys/values comes
# back with a descriptive name rather than just %in.
#
# In a nutshell, if we have two input fields on our HTML
# form such as the follwoing:
#
# <INPUT TYPE = "hidden" NAME = "id_number" VALUE = "1">
# <INPUT TYPE = "hidden" NAME = "setup_file"
# VALUE = "data.setup">
#
# Then the ReadParse routine will create an associative
# array like:
#
# %form_data = ("id_number", "1",
# "setup_file", "data.setup");
#
# Throught this script, we will be able to access those
# dynamically assigned values when we need them as we
# would for an associtive array which was hard coded into
# the setup file. We access elemetns in an associative
# array using key notation. For example, to assign the
# value of "setup_file" to $sf, we'll use:
#
# $sf = $form_data{'setup_file'}.
#
# In other words, $sf = "data.setup";
&ReadParse(*form_data);
#######################################################################
# Load Supporting Files #
#######################################################################
# Once it has read the incoming form data, the script
# will be able to determine which setup file it should
# use to process the incoming form data.
#
# Perhaps a bit of explanation is in order.
#
# Whenever you run this application, you MUST pass to it
# the name of the setup file which it will use to process
# the management request.
#
# This variable will provide the name of the file which
# this script will use to define all of the customizable
# aspects of its operation. For example, the setup file
# defines what is contained in the database and which
# fields should be displayed to the user.
#
# The reason for this is that this one script can handle
# an infinite amount of databases.
#
# Each database has a corresponding setup file which defines
# how the script performs. The logic (and programming)
# remains the same for all db's. All that changes are
# the variables and subroutines in the setup files. This
# makes it very easy for you to quickly generate diverse
# databases with the one backend.
#
# The script first takes the value of "setup_file"
# coming in from the form (which cgi-lib.pl has already
# parsed into the %form_data associative array) and
# assigns it to the variable $setup_file.
#
# So how do you get this information to the script?
#
# There are two ways to do that. Firstly, you can encode
# the information into the URL if you are executing this
# script directly from a hyperlink.
#
# For example, you might use the following hyperlink to
# direct the script to access address_book.setup:
#
# http://www.you.com/cgi/db_manager.cgi?setup_file=address_book.setup
#
# You can also send this information as a hidden field in
# an HTML form using something like the following.
#
# <INPUT TYPE = "hidden" NAME = "setup_file"
# VALUE = "[NAME OF SETUP FILE]">
#
# For example, the following code would define a setup
# file called address_book.setup:
#
# <INPUT TYPE = "hidden" NAME = "setup_file"
# VALUE = "address_book.setup">
#
# You might also create a select box so that the user can
# choose from a number of databases dynamically:
#
# <SELECT NAME = "setup_file">
# <OPTION VALUE = "address_book.setup">Address Book
# <OPTION VALUE = "customer_list.setup">Customers
# </SELECT>
#
# All these examples assume you placed the setup files in
# the provided subdirectory called Setup_files.
#
# The script uses the subroutine require_supporting_libraries
# documented later in this script to actually load the
# setup file and all of its configuration options.
$setup_file = $form_data{'setup_file'};
&require_supporting_libraries (__FILE__, __LINE__,
"./Setup_/files/010/$setup_file");
#######################################################################
# Authenticate User #
#######################################################################
# Next the script must make sure that if the admin has
# instructed it to authenticate all users through the
# setup file variable $should_i_authenticate, that it
# provides a level of authentication. Authentication is
# handled by the authentication subroutine which is
# documented later in this script.
if ($should_i_authenticate eq "yes")
{
&authentication;
}
#######################################################################
# Perform Management Functions #
#######################################################################
# After the user has been authenticated (if necessary), it
# is time for the script to perform any or all of the
# management functions.
#
# There are 5 general functions that this script must
# provide. The script must be able to 1) add a new item
# to the database, 2) modify an item in the database,
# 3) delete an item in the database, 4) provide the user
# with a view of the database and finally 5) present
# the user with a frontpage to give the user the previous
# four options.
#
# These five general functions are broken down into
# several other steps each. For example, the general step
# of modifying an item in the database can be broken down
# into three sub-steps. 1) The user must choose to
# "Modify an Item" from a submit button on the frontpage
# 2) they must be taken to a screen on which they can
# enter search criteria so that the script can dynamically
# generate a list of matches from which they can then
# choose a specific item to modify and 3) they user must
# be able to choose an item from that list, describe the
# changes she wants made and then submit the changes to
# the database.
#
# When all is said and done, there are 11 general and
# sub-functions that make up the main body of logic of
# this application. We will go over each as they appear
# in the code below.
#
# 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 ne "")
# {
# process that type of request;
# exit;
# }
#
# For example, consider the first case in which the
# customer has clicked on the "Add an Item" submit
# button denoted with the NAME value of
# "add_item_button".
#
# if ($form_data{'add_item_button'} ne "")
# {
# &generic_header("Add an Item to the Database Form");
# &generic_form_header;
# &add_form_header;
# &add_modify_data_entry_form;
# &add_form_footer;
# &generic_form_footer;
# exit;
# }
#
# Because the submit button will have some value
# like "Add this new item", when the script reaches
# this statement block, 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 something
# }
#
# 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,
# 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/11th 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. If the user clicks the "add an item" button on the
# frontpage, they must be presented with an HTML form with
# input fields for each field in the database which they
# have access to define. The user can then fill in the
# input fields and submit the info to be added to the
# database.
#
# The generation of this add form is handled by
# several subroutines which are all defined in the setup
# file. generic_header will print out the basic HTML
# header with the passed parameter placed between the
# <TITLE> and </TITLE> tags. generic_form_header will
# print out the basic <FORM> tag as well as the important
# hidden state fields"setup_file" and "session_file" which
# must appear on ever screen. add_form_header displays
# the header of the add form and
# add_modify_data_entry_form displays the actual input
# fields for the form. add_form_footer and
# generic_form_footer display the page footer information.
#
# 2. Once the user submits the information that they want
# to add to the database by clicking the button NAMED
# "submit_addition", the script must be prepared to
# actually modify the database with the new information.
#
# This is handled in the next if test. The subroutine
# submit_addition which is the meet of this function is
# discussed later in this script.
#
# 3. On the other hand, the user might be asking to modify
# an item by clicking on the frontpage button NAMED
# "modify_item_button". If this is the case, the user
# must be presented a form upon which she can enter search
# criteria so that the database can bring back a list of
# database rows which match her search criteria. The user
# can then choose one of them and modify it. We need this
# search filter because in a large database, it would be
# hard to select which row you wanted to modify without
# dome level of filtering. The search form is actually
# generated with the modify_search_form subroutine
# discussed further down in this script.
#
# 4. Once the user fills out the search criteria on the
# form generated by the routine discussed above, and
# clicked on the button NAMED
# "search_and_display_for_modification_button" the
# script must search through the database and come up with
# a list of hits. The user then can select one of the
# rows to modify and then type in any new information she
# wants.
#
# This requires two things. 1) The script must generate a
# list of hits with a radio button for selection of each
# row (only one row may be modified at one time) and 2)
# the script must provide a form similar to the add an
# item form so that the user can enter new info (if the
# user does not enter info into an inpout field, the old
# data will be kept.) These chores are handled by
# the subroutine search_and_display_for_modification which
# is discussed later in this script.
#
# 5. Finally, the user will have selected an item and
# filled in some new information to modify and clicked on
# the button NAMED "submit_modification_button". Now it
# is time to actually submit this modification to the
# database. We'll do this with the subroutine
# "submit_modification" discussed below.
#
# 6. Besides adding an item and modifying an item, the
# user may wish to delete an item. If the user clicks the
# frontpage buttun NAMED "delete_item_button", the script
# will display a form identical to the one which the user
# got in step 3 so that the can define some search
# criteria with which the script can use to generate a
# dynamic list of database rows which may be deleted.
#
# 7. As with modification, the user will enter in some
# search criteria and click the submit button named
# "search_and_display_for_deletion_button". The script
# will then prepare the list of hits from which the user
# can delete. Unlike in the case of modification however,
# there is no need to also include a form similar to the
# "add" form, since we will not be modifying the data, we
# will instead be deleteing the row entirely.
#
# 8. Finally, the user will select one of the database
# rows displayed by routine 7 and click on the
# "submit_deletion_button" button. At this point, the
# script must actually delete the item from the database.
# This is performed with the submit_deletion subrtoutine
# discussed later in this script.
#
# 9. Next, the user might have asked to simply view the
# dataabse by clicking the "view_database_button" button
# on the frontpage. If this is the case, the user needs
#to be presented with the familiar search form so that
# they can enter in search criteria which the script can
# use to generate a dynamic list of database rows to view.
#
# 10. As before, once the user choose some search criteria
# and hits the submit button, the script must display the
# list of hits.
#
# Finally, the script must display the very frontpage from
# which the previous 10 functions were derrived. This
# frontpage must have 4 buttons, one for add, modify,
# delete and view functions.
#
# Okay, well that was quite a mouthful...I have a
# suspicion that a picture is worth those thousand
# words (each step is in parenthesis)...
#
# ----------------Display Frontpage (11)----------
# | | | |
# User Clicks User Clicks User Clicks User Clicks
# Add Button Modify Button Delete Button View Button
# | | | |
# Display Add Display (3) Display (6) Display (9)
# Form (1) Search Form Search Form Search Form
# | | | |
# User Adds User Submits User Submits User Submits
# New Item (2) Search Search Search
# | | |
# Mod Form (4) Delete Form Search Hits
# Displayed Displayed (7) Displayed
# | | (10)
# User Submits User Submits
# Modification Deletion (8)
# (5)
#
# Below are the 11 routines discussed above.
if ($form_data{'add_item_button'} ne "")
{
&generic_header("Add an Item to the Database Form");
&generic_form_header;
&add_form_header;
&add_modify_data_entry_form;
&add_form_footer;
&generic_form_footer;
exit;
}
elsif ($form_data{'submit_addition'} ne "")
{
&submit_addition;
exit;
}
elsif ($form_data{'modify_item_button'} ne "")
{
&modify_search_form;
exit;
}
elsif ($form_data{'search_and_display_for_modification_button'} ne "")
{
&search_and_display_for_modification;
exit;
}
elsif ($form_data{'submit_modification_button'} ne "")
{
&submit_modification;
exit;
}
elsif ($form_data{'delete_item_button'} ne "")
{
&delete_search_form;
exit;
}
elsif ($form_data{'search_and_display_for_deletion_button'} ne "")
{
&search_and_display_for_deletion;
exit;
}
elsif ($form_data{'submit_deletion_button'} ne "")
{
&submit_deletion;
exit;
}
elsif ($form_data{'view_database_button'} ne "")
{
&view_database_form;
exit;
}
elsif ($form_data{'search_and_display_db_button'} ne "")
{
&search_and_display_db_for_view;
exit;
}
else
{
&generic_header("Selena Sol's Example Database Manager Frontpage");
&generic_form_header;
&display_frontpage;
&generic_form_footer;
exit;
}
# Well, that is it! That is the whole script. Well, not
# exaclty, now it is time to explain the subroutines which
# make the above if tests work.
#######################################################################
# 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 "Content-type: text/html\n\n";
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
#######################################################################
# Perform Authentication #
#######################################################################
# Authentication is performed by the set of "auth"
# libraries in the Library subdirectory. However, the
# only one "you" need to worry about is auth-lib.pl.
# Specifically, you call GetSessionInfo from auth-lib.pl
# and it worries about all the other supporting libraries
# on its own.
#
# Authentication demands that you define several variables
# in the setup file as well and &GetSessionInfo will send
# you back several variables hich you can use within this
# script (particularly $session_username and
# $session_group which are used for authentication of
# modifications.
#
# The basic flow of authentication works like this:
# Initially, when you install the application, you set
# $auth_add_register and $auth_allow_register to "on" and
# you set $auth_default_group to "Admin". This will allow
# you to add yourself as an administrator. go ahead and
# run the script from the web and click on the "Register
# New User Button" and fill out the resulting form.
#
# Once you have registered yourself as an admin, you have
# two options. Firstly, you can turn off registration so
# that only you can use this applicaton. To do so, turn
# "off" $auth_add_register and $auth_allow_register. Now
# you will be able togon, but noone else will be able to
# do so without your username and encrypted password.
#
# You might also decide to allow "users" to have access to
# the database manager. These people will be able to add
# entries and delete and modify each others entries but
# not touch yours. To do so, change $auth_default_group
# to "user".
sub authentication
{
# Make sure if the session_file variable is coming in as
# form data that it is converted to a local variable name.
# This is important. We do not want to have to ask the
# user to logon for every screen they want to see! Thus,
# we must make sure that session_file is passed (in a self
# referential way) between every screen in this
# application through the use of hidden form variables.
$session_file = "$form_data{'session_file'}";
# Now perform the authentication and gather the returned
# values.
($session_file, $session_username, $session_group,$session_first_name,
$session_last_name, $session_email) =
&GetSessionInfo($session_file, $this_script_url, *form_data);
}
#######################################################################
# Submit an Addition #
#######################################################################
# The function of submit_addition is all in the name.
# This subroutine takes the user-defined input, formats
# it into a database row (along with some
# administrative data like the time it was submitted and
# by whom it was submitted) and appends it to the
# database.
sub submit_addition
{
# The subroutine begins however, by access the counter
# file defined in the setup file. The counter file shuld
# contain a number on one line. This number represents a
# unique database row identification. This is very
# important because in order for the script to know which
# row to modify or delete, it needs to be able to identify
# that row absolutely.
#
# The database id number is that identification and it is
# generated by incrementing the number in the counter file
# for every item that is added.
#
# The process of getting a unique id number is pretty
# straight forward.
#
# First the script uses get_file_lock discussed later in
# this script to lock the counter file so no other
# instances of this script can get to the counter file.
#
# Then, the script opens the counter file
# (exiting gracefully with file_open_error discussed later
# if there is a problem opening the file) and reads the
# counter file assigning the number to $current_counter .
#
# Next, after closing the counter file back up, it
# increments that number and assigns the incremented value
# to $new_counter
#
# Finally, the script reopens the counter file for writing
# and deletes the old number, replacing it with the new
# incremented number, thus incrementing the counter file.
&get_file_lock("$location_of_lock_file");
open (COUNTER_FILE, "$location_of_counter_file") ||
&file_open_error ("$location_of_counter_file", "Submit Addition",
__FILE__, __LINE__);
while (<COUNTER_FILE>)
{
$current_counter = $_;
}
close (COUNTER_FILE);
$current_counter++;
$new_counter = $current_counter;
open (COUNTER_FILE, ">$location_of_counter_file") ||
&file_open_error ("$location_of_counter_file", "Submit Addition",
__FILE__, __LINE__);
print COUNTER_FILE "$new_counter";
close (COUNTER_FILE);
# Once we have dealt with the database id number, we can
# now grab the current date using the get_date documented
# later in this script.
$when_modified = &get_date;
# With that, we are ready to open the database for
# appending (>>).
open (DATABASE, ">>$data_file_path") ||
&file_open_error ("$data_file_path", "Submit Addition",
__FILE__, __LINE__);
# Now, for every field that the user could define on the
# add form, we will append the value they entered to a
# variable called $new_row using the append operator (.=).
# Notice that we will add a pipe (|) delimiter between
# every field in a database row.
foreach $field (@db_user_definable_field_order)
{
$new_row .= "$form_data{$field}\|";
}
# Then, we will tag on the administrative fields of
# when the addition was made,who made the addition, what
# group are they in and the unique database id number.
$new_row .= "$when_modified\|$session_username\|$session_group\|$new_counter\n";
print DATABASE $new_row;
close (DATABASE);
# Once the database has been appended to, we will note
# what we did in the log file, release the lock file so
# that others can make their own additions and let the
# user know that their item was added successfully.
open (LOG_FILE, ">>$location_of_log_file") || &file_open_error
("$location_of_log_file", "Submit Addition",
__FILE__, __LINE__);
print LOG_FILE "ADD\|$new_row";
close (LOG_FILE);
&release_file_lock("$location_of_lock_file");
&successful_addition_message;
}
#######################################################################
# Submit a Modification #
#######################################################################
# The user might also be submitting a modification to the
# database.
sub submit_modification
{
# The first thing we must do is make sure that they
# actually chose a database item to modify. If they did
# not, we better warn them and stop processing.
if ($form_data{'item_to_modify'} eq "")
{
&no_item_submitted_for_modification;
exit;
}
# If they did choose an item, we need to find that items
# so that we can modify it. To do so, we'll open the data
# file and read through it a line at a time. We'll then
# split up each line into its fields and compare the id
# number given to us by the suer against the id number of
# the current line. If it is not the same, we will add
# the entire line to a variable called $new_data (By the
# end of this, $new_data is going to hold the entire
# contents of our database).
open (DATABASE, "$data_file_path") || &file_open_error
("$data_file_path", "Modify item", __FILE__, __LINE__);
while (<DATABASE>)
{
$line = $_;
chop $line;
@fields = split (/\|/, $line);
if ($fields[$index_of_db_id_number] ne $form_data{'item_to_modify'})
{
$new_data .= "$line\n";
}
# If the id numbers ARE equal, however, it means that we
# have found the database row that needs to be modified.
#
# First, we will save the old line in a variable $old_row.
# We are going to need that value when we report what
# happened in the log file.
#
# Then, we will go through the basic user-definable fields
# checking to see which fields the user has asked to
# modify (Only fields which have a $form_data value, will
# be modified.)
#
# Notice, that we will append this row to $new_row one
# field at a time. If the user has not submitted a
# change, we'll grab the value from the old row.
else
{
$old_row = "$line";
for ($i=0; $i <= (@db_user_definable_field_order-1); $i++)
{
$index = @db_user_definable_field_order[$i];
if ($form_data{$index} ne "")
{
$new_row .= "$form_data{@db_user_definable_field_order[$i]}\|";
}
else
{
$new_row .= "$fields[$i]\|";
}
} # End of for ($i=1; $i <= @db_user_definable_field_order; $i++);
$new_data .= "$new_row";
# Now we will complete the row by adding all of the
# administrative variables like the date and the
# authentication values. Notice that if the person who is
# modifying the data is an admin, we will use the group
# and user values from the old row instead of changing it
# to admin. That way the original poster will still be
# able to modify it.
$when_modified = &get_date;
$who_modified = $session_username;
if ($session_group eq "admin")
{
$group = $fields[$index_of_group_who_modified];
$who_modified = $fields[$index_of_who_modified];
}
else
{
$group = $session_group
}
$new_data .= "$when_modified|$who_modified|$group|$fields[$index_of_db_id_number]\n";
$new_row .= "$when_modified|$who_modified|$group|$fields[$index_of_db_id_number]";
} # End of else
} # End of while (<DATABASE>)
close (DATABASE);
# Now that we have appended the entire database as well as
# the modified row to $new_data, it is time to change the
# data file. However, first, we must make sure that the
# user is performing a legal operation. The next if test
# asks 1) is the user the original poster of the database
#row or are they in the same group as the user who posted
# it (provided that group is not the default "user" group
# which everyone is assigned to, 2) or are they an
# administrator. If the user passes this test and the
# admin has set the script to use authentication, the
# script will add the overwrite the old database with the
# contents of $new_data, protecting it with a lockfile as
# we did for addition.
if ((($session_username eq $fields[$index_of_who_modified]) ||
(($session_group eq $fields[$index_of_group_who_modified]) &&
($session_group ne "user"))) ||
($session_group eq "admin") || ($should_i_authenticate ne "yes"))
{
&get_file_lock("$location_of_lock_file");
open (DATABASE, ">$data_file_path") || &file_open_error
("$data_file_path", "Modify Item", __FILE__, __LINE__);
print DATABASE "$new_data";
close (DATABASE);
# We will also add the modification information to the log
# file.
open (LOG_FILE, ">>$location_of_log_file") || &file_open_error
("$location_of_log_file", "Modify Item", __FILE__, __LINE__);
print LOG_FILE "MODIFY_NEW\|$new_row|MODIFIED BY $session_username\n";
print LOG_FILE "MODIFY_OLD\|$old_row\n";
close (LOG_FILE);
&release_file_lock("$location_of_lock_file");
&successful_modification_message;
}
else
{
&unsuccessful_modification_message;
}
}
#######################################################################
# Submit a Deletion #
#######################################################################
# Finally, the user might be asking to make an actual
# deletion.
sub submit_deletion
{
# As in the case of modification, we must make sure the
# user actually chose an item to delete from the list.
if ($form_data{'item_to_delete'} eq "")
{
&no_item_submitted_for_modification;
exit;
}
# Because we can delete multiple items at one time, the
# next thing we do is to break up the item_to_delete
# incoming form data into its separate parts (if there are
# any) using SplitParam from cgi-lib.pl.
@items_to_delete = &SplitParam($form_data{'item_to_delete'});
# Then we will open the data file, read through it a line
# at a time, splitting each line into its component
# fields as we did for modificaiton.
open (DATABASE, "$data_file_path") || &file_open_error
("$data_file_path", "Delete Item", __FILE__, __LINE__);
while (<DATABASE>)
{
$line = $_;
chop $line;
@fields = split (/\|/, $line);
# Then, foreach item in the delete list, we will delete it
# if the current line's item id is equal to the id
# submitted.
$already_added = "no";
foreach $item (@items_to_delete)
{
if ($fields[$index_of_db_id_number] eq $item)
{
$deleted_row = "$line";
$already_deleted = "yes";
}
elsif (($already_added ne "yes") && ($already_deleted ne "yes"))
{
$new_data .= "$line\n";
$already_added = "yes"
}
}
}
close (DATABASE);
# As we did for modificaiton, we will pass the user
# through authentication and overwrite the old datafile
# with the new information.
if ((($session_username eq $fields[$index_of_who_modified]) ||
(($session_group eq $fields[$index_of_group_who_modified]) &&
($session_group ne "user"))) ||
($session_group eq "admin") || ($should_i_authenticate ne "yes"))
{
&get_file_lock("$location_of_lock_file");
open (DATABASE, ">$data_file_path") || &file_open_error
("$data_file_path", "Delet Item", __FILE__, __LINE__);
print DATABASE "$new_data";
close (DATABASE);
$who_deleted = $session_username;
# And as before, we will append the info to the log file.
open (LOG_FILE, ">>$location_of_log_file") || &file_open_error
("$location_of_log_file", "Delete Item", __FILE__, __LINE__);
print LOG_FILE "DELETE\|$deleted_row\|DELETED BY $who_deleted\n";
close (LOG_FILE);
&release_file_lock("$location_of_lock_file");
&successful_deletion_message
}
else
{
&unsuccessful_modification_message;
}
}
#######################################################################
# Search and Display the Database #
#######################################################################
sub search_and_display_db
{
# Before we go in and search however, we format any
# incoming sort_by information. We'll discuss the sorting
# algorithm in just a minute. However, I want to note
# here that there are two ways to define a field by which
# this script will sort the returned database rows. You
# can set a default row in the setup file by setting
# $index_of_field_to_be_sorted_by equal to the index of
# the field that you want sorted by. Thus, you may just
# want to sort automatically by last name and not even
# give the user the option to sort by another row.
#
# On the other hand, you might want to allow the user to
# choose which field the returned rows are sorted by. If
# this is the case, you need to add another form variable
# to your HTML interface. This variable MUST be called
# "sort_by" and will usually be in the form of a
# select box such as the following:
#
# <TH>Sort by which field</TH>
# <TD><SELECT NAME = "sort_by">
# <OPTION VALUE = "0">First Name
# <OPTION SELECTED VALUE = "1">Last Name
# <OPTION VALUE = "2">Email
# <OPTION VALUE = "9">Age
# </SELECT></TD>
#
# If you allow the user to define which field to sort on,
# then this information will override the information in
# the setup file using the following it test.
#
# Remember that arrays start counting from zero so the
# first filed in your dataabse has an index value of 0,
# not 1
if ($form_data{'sort_by'} ne "")
{
$index_of_field_to_be_sorted_by = $form_data{'sort_by'};
}
# okay, now display the header and grab our lisdt of
# database rows using &submit_query in db-lib.pl. Notice
# that you need to redefine
# $index_of_field_to_be_sorted_by "before" you display the
# header because the header displays the hidden form field
# which will carry that data throughout further
# self-referencing screens.
($total_row_count) = &submit_query(*database_rows);
# Now here is where the real fun comes in. We want to
# sort the database rows that are displayed to the user.
# The process of this is fairly simple. For every
# database row contained in @database_rows, we are going
# to grab the value of the field defined as the field to
# be sorted by and append that value to the very begining
# of the line (so that the field will be repeated twice.)
# Then you sort the rows (sort will sort on the first
# characters first which is why you need to append the
# sortable field to the front.) Then, finally, you remove
# the appended field so that the database rows are as they
# began, but in a sorted order.
#
# Thus, if you were sorting by last name and you had the
# following database rows ($row) in the @database_rows
# array:
#
# Eric|Tachibanaerict@eff.org
# Selena|Sol|selena@eff.org
# Gunther|Birznieks|birzniek@hlsun.redcross.org
#
# The script would then take each row and append the last
# name field to the front like so:
#
# Tachibana|Eric|Tachibana|erict@eff.org
# Sol|Selena|Sol|selena@eff.org
# Birznieks|Gunther|Birznieks|birzniek@hlsun.redcross.org
foreach $row (@database_rows)
{
@row = split (/\|/, $row);
$sortable_field = $row[$index_of_field_to_be_sorted_by];
unshift (@row, $sortable_field);
$new_row = join ("\|", @row);
push (@new_rows, $new_row);
}
# Once we have the rows reformatted as above, we are ready
# to sort them. First however, we erase the contents of
# @database_rows since we are going to want to recreate
# that array with the sorted rows from @new_rows in just a
# moment.
@database_rows = ();
# Then we are ready to sort...guess what the result is:
#
# Birznieks|Gunther|Birznieks|birzniek@hlsun.redcross.org
# Sol|Selena|Sol|selena@eff.org
# Tachibana|Eric|Tachibana|erict@eff.org
@sorted_rows = sort (@new_rows);
# Next, we need remove that first sortable field so that
# we have the following:
#
# Gunther|Birznieks|birzniek@hlsun.redcross.org
# Selena|Sol|selena@eff.org
# Eric|Tachibana|erict@eff.org
#
# Look! They are now sorted by last name! By the way, if
# you sort by a field with numbers, remember that
# computers sort with their own funky rules. That is, if
# you don't put a 0 before the nuber 1, it will sort after
# 9 but alphabetical sorting should be just fine.
foreach $sorted_row (@sorted_rows)
{
@row = split (/\|/, $sorted_row);
$sorted_field = shift (@row);
$old_but_sorted_row = join ("\|", @row);
push (@database_rows, $old_but_sorted_row);
}
# now that we have sorted the rows, lets figure out how to
# display them all.
#
# The reason that we wanted to get the $total_row_count
# back from the search libraries is so that we can then
# check to make sure that if their search returned no hits
# we can let them know rather than just sending them a
# blank screen.
if ($total_row_count < 1)
{
&no_hits_message;
exit;
}
# So what exactly do we show the user if their search did
# turn up some hits. Well, that depends on 1) how many
# rows were returned from the database as scoring matches
# to their search criteria, 2) how many rows we have
# defined in the setup file to allow them to see and 3)
# how many rows they have already seen.
#
# Let me expound. Let's assume that we have set
# $max_rows_returned to 2 in the setup file and that their
# search turned up 11 hits which have just been sorted.
#
# The first screen that they should see should say, "You
# scored 10 hits and I have been instructed to show you
# two at a time". It should then display the first two
# sorted rows and then provide a button which says "See
# next 2 hits". When the user clicks on that button, she
# should then get the next two sorted rows. The script
# needs to remember that she already saw the first two
# rows as well as remembering that it should only show her
# two at a time.
#
# Finally, the script will have gone through all the rows
# up to 9 and 10. The final trick is that it must then
# tell her that she can click the button to see the next 1
# hi(t)...no "s" on the end of that....the script has to
# know some grammar rules.
#
# So first, we will collect any incoming information about
# the hits that the client has seen so far. This
# information will be stored in a hidden inpout field
# called "hits_seen" which must accompany every submit
# button that promises to show "x more hits". Note that
# the first time around, there will be no new_hits_seen
# value coming in from the form since the user will not
# have seen any hits yet.
# Then we need to remove all of the rows from
# @database_rows that will not be shown to the user quite
# yet because we are only allowed to display
# max_rows_returned at any one time.
#
# To do this we will first figure out how many elements
# are left in the array. Then, we will pop out (remove
# from the end of the list) all of the extra rows.
$length_of_database_rows = @database_rows;
for ($i = $length_of_database_rows-1;$i >= $max_rows_returned;$i--)
{
$extra_row = pop (@database_rows);
}
&search_results_body;
&search_results_footer;
}
#################################################################
# 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";
}
$mon++;
$year = ($current_century-1) . "$year";
$date = "$mon/$mday/$year at $hour\:$min\:$sec";
return $date;
}
#######################################################################
# 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") || &file_open_error ("$lock_file",
"Lock File Routine",
__FILE__, __LINE__);
# 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);
}
#######################################################################
# 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) = @_;
print "Content-type: text/html\n\n";
&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.")
}