#!/usr/local/bin/perl # Name: db_search.cgi # # Version: 5.02 # # Last Modified: 01-23-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; # Also, send out the http header early for easy debugging # and so that the server will not time us out if we take a # while to process. print "Content-type: text/html\n\n"; ####################################################################### # Read and Parse Form Data # ####################################################################### # Next, the ReadParse subroutine in cgi-lib.pl is used 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 descriptinve # name rather than just %in. &require_supporting_libraries (__FILE__, __LINE__, "./Library/cgi-lib.pl", "./Library/db-lib.pl", "./Library/auth-lib.pl"); &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 search 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_search.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. # # # # For example, the following code would define a setup # file called address_book.setup: # # # # You might also create a select box so that the user can # choose from a number of databases dynamically: # # # # 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. # # Once the setup file has been loaded, the script also # uses the require_supporting_libraries subroutine to # load the mail library which we will use to send email to # the form administrator. $setup_file = $form_data{'setup_file'}; &require_supporting_libraries (__FILE__, __LINE__, "./Setup_files/$setup_file"); #print "$form_data"; ####################################################################### # Perform Authentication # ####################################################################### if ($should_i_authenticate eq "yes") { $confirm_access = "./admin/confirm_access.pl"; # $cgi_utils = "./cgi-utils.pl"; # Access level required to view this page ### G -- guest access $access_code = "U"; # U -- user access # C -- content author # S -- system administrator # require $cgi_utils; # Load up libraries ### require $confirm_access; # # %query = &get_query; # get session key and check it ### $session_key = $form_data{'session_key'}; # #chdir '..'; #change to base dir if necessary(GIMME/ISAP) # # print " -> $form_data{'session_key'} - session key "; $access_ok = &confirm_access($session_key, $access_code); # if (!$access_ok) { exit; } } ####################################################################### # Printout the Database # ####################################################################### if ($form_data{'print_data'} ne "") { ($total_row_count) = &submit_query(*database_rows); &print_db; exit; } ####################################################################### # Printout the Menu # ####################################################################### if ($form_data{'print_menu'} ne "") { ($total_row_count) = &submit_query(*database_rows); &print_menu($form_data{'frame'}); exit; } ####################################################################### # Display Search Screen # ####################################################################### # There are really only two things required of this # script. Firstly, it will have to give the user a form # in which she can submit her search criteria and # secondly, it must process her search request. # # The way the script knows which task it is being asked to # perform depends on a special HTML form submit button. # When the user clicks on the submit button "Submit the # Search Request" the script will know that it is supposed # to search. # # Of course you can have the button say anything you want, # the important thing is that you have the submit button # on the bottom of your frontend form which looks like # this: # # # # The other thing this script does, as we said, is create # the search form on which the above button appears. The # script does this by accessing the output_html_query_form # subroutine in the setup file which basically prints out # an HTML form for you which you can create or customize # as you desire. # # Thus, the following routine asks, "if thre is no value # for the submit button, that means I am not be asked to # do a search...therefore, I must be being asked to # display the search form!" It does so and then quits. # # By the way, you can bypass this form generated script # and use your own so long as you have the correct submit # button on your form. You can also hardcode searches by # including the submit_search parameter as URl encoded # data. Maybe something like the following: # # http://www.you.com/cgi/db_manager.cgi?setup_file=customer_list.setup&submit_search=yes&lname=B # # This would theoretically access a setup file called # customer_list.setup and would search the database set in # that setup file for the lname field for all names # starting with B. if ($form_data{'submit_search'} eq "") { &output_html_query_form; exit; } ####################################################################### # Search the Database # ####################################################################### # As we said, the other thing that you can do with this # script is to actually search the database which is # defined in the setup file. # # The script begins by sending out the beginning of the # HTML response to the client defined in # search_results_header which is located in the setup # file. # # Then we are ready to begin returning search results. # # Most of the work for search is done by db-lib.pl. # The script access the submit_query subroutine passing it # an array database_rows by reference (which means that # the subroutine is going to fill that array with # database rows which were matches to the users search # criteria directly and not pass it back when it is done), # # It will also expect to be returned a total row count of # successful hits. # # The working of the search are covered in depth in # db-lib.pl so go there if you are still confused. else { # $form_data{'db_id'} = $form_data{'item_id'}; # 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: # # Sort by which field # # # 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. &search_results_header; ($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; } # Now we will actually send the results of the search to # the user as well as send the HTML footer. And that, # they say, is that. # okay, anyone who is following the script for whatever reasons, be aware. # I had to depart from the norm by way, way much here. First off, # if there is more than 1 hit, now only an abbreviated part of the # table gets shown. If there is only 1 hit, however, then all of the # data will be shown. It was necessary to do this to make something # readible and attractive with the data I was using. So, in short, the # added part is this: if ($total_row_count == 1) { &search_results_single; #long &footer; 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. $hits_seen = $form_data{'new_hits_seen'}; # Now the script will go through and remove from # @database_rows all of the rows that have already been # seen by shifting off (remove from the front of # the array) one element up to the value of $hits_seen for ($i = 1;$i <= $hits_seen;$i++) { $seen_row = shift (@database_rows); } # 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); } # Now we will reset $new_hits_seen so that we can # incorporate this value in the hidden form tag generated # by &search_results_footer so that the next time we come # to this routine, we can give the user the next set of # hits. $new_hits_seen = $hits_seen + $max_rows_returned; &search_results_multi; #body &multi_footer; &footer; } ####################################################################### # 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