Program Examples

First, let's develop a Web-based phonebook so that you can get some experience working with databases with a Web interface. The Web-database application we will create will do this:

  • The application will show an opening screen with an add-record link and an area to enter searches.

  • The add-record link will point to another HTML page with the form fields for entering a new record.

  • The add-record page will take input from the user and add it to the database.

  • The search area on the main page will take input from the user, generate an SQL query statement using data fed by the user inputs, and display a page with the matches to the query.

This is a simple Web-based phonebook; it is not as full-featured as others you may find, but this one is for learning purposes. You are free to add features whenever you want. Adding features is also an excellent way to learn more about the program and Web programming.

Web phonebook

First, let's take a look at the two HTML pages we need to create for our Web phonebook. In these HTML files, we try to use several different HTML form fields so that you can see them in action.

Main HTML page

start example
01: <html> 02:  <head><title>Web Phonebook</title></head> 03:  <body> 04:   <form action="/cgi-bin/phonebook.cgi" method="post"> 05:    <input type="hidden" name="form_action" value="search" /> 06:   <table align="center" border="1" cellpadding="5"> 07:    <tr> 08:     <td colspan="2" align="center"> 09:      <font size="6">Web Phonebook</font> 10:     </td> 11:    </tr> 12:    <tr> 13:     <td> 14:      Add Record 15:     </td> 16:     <td> 17:      <a href="phone_add.html">Click to Add Record</a> 18:     </td> 19:    </tr> 20:    <tr> 21:     <td align="center"> 22:      Search Text:<br />on<br />Field(s): 23:     </td> 24:     <td> 25:      <input type="text" name="search" /><br /> 26:      <input type="checkbox" name="field"            value="firstname" />First Name 27:      <input type="checkbox" name="field"            value="lastname" />Last Name<br /> 28:      <input type="checkbox" name="field"            value="coworker" />Co-Worker 29:      <input type="checkbox" name="field"            value="lastname" />Phone # 30:     </td> 31:    </tr> 32:    <tr> 33:     <td colspan="2" align="center"> 34:      <input type="submit" value="Submit Search" /> 35:     </td> 36:    </tr> 37:   </table> 38:   </form> 39:  </body> 40: </html>
end example

The preceding HTML form contains the code needed to create the main page for our Web-based phonebook. Notice on line 5 that we use a hidden form field. This field is used to determine what action to take on the database. Since this page is also the search page, the value for this hidden element is set to "search". This tells our application that the user wants to search for a record.

Add record HTML page

start example
01: <html> 02:  <head> 03:   <title> 04:    Form Example 05:   </title> 06:  <head> 07:  <body> 08:   <form action="/cgi-bin/phonebook/phonebook.cgi"        method="get"> 09: <input type="hidden" name="form_action" value="addrecord" /> 10:   <table border="1" align="center"> 11:    <tr> 12:     <td colspan="2" align="center"> 13:      <font size="6">Web Phonebook</font><br /> 14:      <font size="4">(Add Record)</font> 15:     </td> 16:    </tr> 17:    <tr> 18:     <td>First Name:</td><td><input type="text"          name="firstname" /></td> 19:    </tr> 20:    <tr> 21:     <td>Last Name:</td><td><input type="text"          name="lastname" /></td> 22:    </tr> 23:    <tr> 24:     <td>Phone:</td> 25:     <td><input type="text" name="phone" /></td> 26:    </tr> 27:    <tr> 28:     <td>Extension:</td> 29:     <td><input type="text" name="extension" /></td> 30:    </tr> 31:    <tr> 32:     <td>Cell:</td><td><input type="text" name="cell" /></td> 33:    </tr> 34:    <tr> 35:     <td>Pager:</td><td><input type="text" name="pager"          /></td> 36:    </tr> 37:    <tr> 38:     <td>Co-worker?:</td> 39:     <td> 40:     <input type="radio" name="coworker" value="Y">Y  41:     <input type="radio" name="coworker" value="N" checked>N  42:     </td> 43:    </tr> 44:    <tr> 45:     <td>Notes:</td> 46:     <td> 47:      <textarea name="notes" cols="20" rows="3"           wrap="physical"></textarea> 48:     </td> 49:    </tr>  50:    <tr> 51:     <td align="center" colspan="2"> 52:      <input type="submit" value="Add Record" /><br /> 53:      <input type="reset" value="Reset Form" /><br /> 54:      <a href="phone.html">Main Page</a> 55:     </td> 56:    </tr> 57:   </table> 58:   </form> 59:  </body> 60: </html>
end example

Notice that on line 9 of the preceding code we again use a hidden form field. This one contains a value of "addrecord" and is used to tell the Web application that the user wants to add a record.

Now that we have the two HTML pages that we'll use for our phonebook application, we can start coding the application and learn how it all interacts with the HTML pages we just created.

phonebook.cgi

start example
01: #!/usr/bin/perl -wT 02: # 03: # program 7-1 04: # Web Phonebook Application 05: #  06: use strict; 07: use DBI; 08: use CGI qw(:standard); 09: use CGI::Carp qw(fatalsToBrowser);
end example

Line 1 tells the system where to find Perl and turns on warnings with the -w; notice, however, that we now add the taint flag with the T. Turning taint checking on prevents us from using, in system calls, any data which was passed into the program that we haven't checked. This is a safety measure that no other language offers at this level.

Lines 2-5 are simply comments about this program.

Line 6 turns on strict. strict forces the programmer to declare variables and stops many simple but hard to find errors from happening (such as using the same variable name more than once in the same block).

Line 7 loads the DBI module so that we have access to the phonebook database.

Line 8 calls the CGI module and tells it to import the :standard set of features. The CGI module is an incredibly useful module, written by Lincoln Stein, for working with Perl CGI programs. Don't try to write your own subroutines to pull data from a Web form; this CGI module is written specifically for that purpose and has been updated and optimized for many years now.

Line 9 calls the CGI::Carp module and imports the fatalsToBrowser method. By using this module, you can save a lot of time debugging and can also avoid a lot of confusion. Usually, when there is anything wrong with a CGI program, the Web server simply replies with a 500 Server Error and offers no other details. Many beginning CGI programmers get lost at this point because the error message is so vague. By using the CGI::Carp module, most of the errors that occur are sent to the browser so that you get a much more descriptive error than the 500 Server Error.

10: print header(); 11: my $action = param(‘form_action'); 12: my $DB_Handle = DBI->connect("DBI:mysql:BibleBook",      "bookuser", "testpass") 13:     or die("Cannot connect: $DBI::errstr\nAborting");

Line 10 calls the header function that is in the CGI.pm module. This function is just a shortcut that prints the "Content-Type: text/html\n\n" line for you.

Line 11 calls the param function from CGI.pm and reads in the value passed from the "form_action" field on the HTML form. Remember, this is the hidden field we use to tell the program what we want it to do.

Lines 12-13 connect to the database and store a handle to the connection in $DB_Handle. If there is a problem connecting to the database, line 13 will print an error message and cause the program to die.

14: Add_Record() if($action eq ‘addrecord'); 15: Search_DB()  if($action eq ‘search');

Line 14 calls the Add_Record if the value of $action equals "addrecord".

Line 15 calls Search_DB if the value of $action equals "search".

These two lines are where our program decides either to add a record to the database or to search the database for matching records. The program performs only one of these tasks at a time; it either adds a record to the database or searches the database for matching records.

16: sub Search_DB { 17:     my ($sql, $st_handle); 18:     my $search_for = param(‘search'); 19:     my @field      = param(‘field');

Line 16 begins the Search_DB subroutine. This subroutine searches the database-hence the descriptive name.

Line 17 creates two variables that we use in this subroutine. By creating them as my variables, their scope is restricted to the block they are declared in.

Line 18 uses the param function from CGI.pm to read in the value passed in the search field on the HTML form and then stores the result in the $search_for variable.

Line 19 uses the param function, as in Line 18, but in this case we are expecting a list to be returned to us and stored in @field. The param function "does the right thing": when you ask it to return a scalar, it does; when you ask it to return a list, it does. The reason we want a list to be returned is that the field selections on the HTML form are checkboxes and there can be several values returned. This line, then, stores all of the values that are checked (and named field) on the HTML form and stores them all in the list named @field.

20:    @field = map { "($_ LIKE ‘%$search_for%')" } @field;

Line 20 is my favorite for this program. The map function is truly cool. It performs the block of code on each value in the array passed to it and returns the results into a new, or the same, array.

In this line, we have each of the fields we want to search stored in the array named @field. We need to incorporate these fields into a valid SQL query. This can be done using a foreach loop, but doing it this way takes more lines of code. By using map, this can be accomplished in a single line.

Inside of the braces, we have the text "($_ LIKE ‘%$search_for%')". This is a string, and the result of this string is what the current value that map is evaluating becomes. The $_ is the current value that map is evaluating. So, this is just like saying this:

Note 

The next four lines are an example, not part of the program.

foreach my $current  (@field) {     $current = "($current LIKE ‘%$search_for%')";     push @new_field_array, $current; }

The whole task that we are trying to accomplish here is to take the field-name text that is in the array and change it to a valid portion of SQL so that we can append it to the entire SQL statement.

21:     $sql  = qq{SELECT * FROM phonebook WHERE (}; 22:     $sql .= join ‘ OR ‘, @field; 23:     $sql .= ")";

Lines 21-23 generate a valid SQL query used to query the database. Line 22 is possible because of how we set up the @field array in line 20.

Line 21 begins by simply setting the $sql variable to some text.

Line 22 uses the join function to take each value in the @field array and join it with OR.

Line 23 ends the SQL statement.

So, when the program gets to line 23, the SQL it generates may look something like this:

SELECT * FROM phonebook WHERE ((fname LIKE ‘%Brent%') OR (lname LIKE ‘%Brent%') OR (phone LIKE ‘%Brent%'))

This SQL statement means we have been looking for the text 'Brent' in fields fname, lname, and/or phone.

The %'s before and after the text we are looking for are the SQL wildcard characters. These characters act like a * in the shell. So, this translates to "find any matches that have Brent in them with any text before or after the matching text." If you want to be strict about the matching, leave off the %'s. Then the matches have to be exact.

24:     $st_handle =  $DB_Handle->prepare($sql); 25:     $st_handle -> execute(); 26:     Display_Results($st_handle); 27: } 

Line 24 calls the prepare method from the DBI module and passes it the SQL statement. The result of this method call is stored in $st_handle.

Line 25 takes the $st_handle statement handle and calls the execute method on it. This causes the SQL statement to be executed on the SQL database table. A pointer to the results returned is then stored in the $st_handle variable.

Line 26 calls the Display_Results subroutine and passes $st_handle to it.

Line 27 closes this subroutine.

28: sub Display_Results { 29:     my $handle = shift; 30:     my ($fname, $lname, $cow, $phone, $ext,             $cell, $pager, $notes, $count); 31:     my $bgcol = ""; 

Line 28 begins the Display_Results subroutine. This subroutine is used to generate an HTML page of the matching records from a database search.

Line 29 creates a my variable named $handle and uses the shift function to read in the value. This is the value passed to the subroutine when it is called and is a handle that contains the matching records.

Line 30 declares some scalar variables we will be using in this subroutine.

Line 31 declares a variable named $bgcol and sets it to an initial value of an empty string.

32:     print<<'    HTML';

Line 32 begins a here-document. A here-document prints out text between the starting line and the ending tag. The ending tag is the ‘ HTML' on line 32. The only reason there are spaces before the HTML in the tag is so that we can maintain our indenting. The ending tag in a here document must be exactly what is used for creating the tag. If you have four spaces before the tag, there must be four spaces in the ending tag.

33:      <html><head><title>Search Results</title></head> 34:       <body> 35:        <table align="center" border="1" cellspacing="0"> 36:         <tr bgcolor="#303030"> 37:          <td colspan="6" align="center"> 38:           <font size="5" color="white"> 39:            Search Results 40:           </font> 41:          </td> 42:         </tr>  43:         <tr bgcolor="#c0c0c0"> 44:          <td align="center"><b>Name</b></td> 45:          <td align="center"><b>Co-Worker?</b></td> 46:          <td align="center"><b>Phone</b></td> 47:          <td align="center"><b>Extension</b></td> 48:          <td align="center"><b>Cell</b></td> 49:          <td align="center"><b>Pager</b></td> 50:         </tr>  51:     HTML

Lines 33-50 are simply the HTML needed to begin the page for the search results. We are using a table to keep the data nice and formatted.

Line 51 is the ending tag for the here document that begins on line 32.

52:     $handle->bind_columns(undef,  53:      \($fname, $lname, $cow, $phone, $ext,             $cell, $pager, $notes)); 

Line 52 calls the bind_colums method on the $handle. This causes the variables that we referenced to be "bound" to the query results. This means that each time we call the fetch method, the values are stored in the variables they are bound to. The undef in the first argument to bind_columns is not currently used by the DBI, but it is required nonetheless.

54:     while($handle->fetch){  55:         $bgcol = ($bgcol eq "ffffff") ? "e0e0e0": "ffffff";

Line 54 begins a while loop that calls the fetch method. This gets the query matches, one record at a time, until there are no more matches left. Once we are out of matches, the while loop terminates because the call to $handle->fetch is no longer true.

Line 55 makes the data more readable. This uses the trinary operator to alternate the background colors of each row of data. The value on the left side of the ? is evaluated; if it is true, the first item to the right of the ? is executed. It the value on the left side of the ? is false, the value to the right of the : is executed. This is an easier way to do an if..else block on simple expressions.

56:    print qq(<tr bgcolor="#$bgcol">); 57:     print qq(<td>$fname $lname</td><td          align="center">$cow</td>); 58:     print qq(<td>$phone</td><td>$ext</td><td>$cell</td>); 59:     print qq(<td>$pager</td>); 60:    print qq(</tr>);

Line 56 prints the table-row tag, with the appropriate background color.

Lines 57-59 print a row of data, with each cell of data being in its own table cell. The exception to this is the name field, where we list both the first name (fname) and last name (lname) in a single table cell.

Line 60 prints the closing table row tag.

61:      $count++; 62:     }

Line 61 increments the $count variable. This variable is used for the sole purpose of checking to see if any data has actually returned.

Line 62 ends the while loop that fetches the data, row by row.

63:     No_Data() unless($count); 64:     print qq(</table></body></html>); 65: } 

Line 63 calls the No_Data subroutine. This subroutine gets called if the $count variable is false. If there are no matches for the query, the while statement on line 54 will never be entered-and the $count variable never gets incremented. This is a simple trick to see if there are any matches.

Line 64 prints the ending tags for the HTML page.

Line 65 closes this subroutine.

66: sub No_Data{ 67:    print qq(<tr><td colspan="6" align="center">); 68:    print qq(No matches found, return to); 69:    print qq(<a href="/phonebook/phone.html">main page</a>.); 70:    print qq(</td></tr>); 71: }

Line 66 begins the No_Data subroutine. This subroutine simply prints some text to let the user know that there are no matches. Without this, if there are no matches, an HTML page with a table consisting solely of headings will be displayed.

Lines 67-70 print some HTML to inform the user that there are no matches and to provide him or her with a link to the main phonebook page.

Line 71 closes this subroutine.

72: sub Add_Record { 73:     my $fname    = param(‘firstname'); 74:     my $lname    = param(‘lastname'); 75:     my $phone    = param(‘phone'); 76:     my $ext      = param(‘extension'); 77:     my $cell     = param(‘cell'); 78:     my $pager    = param(‘pager'); 79:     my $coworker = param(‘coworker'); 80:     my $notes    = param(‘notes'); 81:     my $sql;

Line 72 begins the Add_Record subroutine. This subroutine is used to gather the data entered on the HTML form, generate the appropriate SQL statement, and execute the SQL statement to add the record to the phonebook database table.

Lines 73-80 use the param function from the CGI.pm module to read in the data sent from the HTML form.

Line 81 declares a scalar variable named $sql.

82:     $sql  = qq{INSERT INTO phonebook (firstname, lastname,}; 83:     $sql .= qq{coworker, phone, extension, cell, pager,                     notes)}; 84:     $sql .= qq{VALUES (?, ?, ?, ?, ?, ?, ?, ?)}; 

Lines 82-84 create the SQL statement needed to add a record to the database table. The ?'s on line 84 are placeholders. They allow you to prepare an SQL statement. When you call the execute method, you pass values used to fill in the ?'s. Line 86 shows the execute method call with the values being passed.

85:    my $st_handle = $DB_Handle->prepare($sql); 86:    my $rval = $st_handle->execute($fname, $lname, $coworker, 87:         $phone, $ext, $cell, $pager, $notes);

Line 85 prepares the SQL statement that we create on lines 82-84. Remember that when you prepare an SQL statement, it gets stored in an internally compiled format, ready to be executed against the database. The return value is a handle to that particular SQL statement that we store in the $st_handle variable in this instance.

Lines 86-87 call the execute method on the SQL statement. This line also passes the values, which we read in on lines 73-80, to the SQL statement. The number of values you pass in the execute call must match exactly the number of placeholders (?'s) in the SQL statement.

88:     Handle_DB_Error() unless($rval); 89:     Display_Page(); 90: }

Line 88 calls the Handle_DB_Error subroutine if the $rval scalar is not true. On line 86, if for some reason the execute method call has failed, $rval remains undefined.

Line 89 calls the Display_Page subroutine. Display_Page is the subroutine that generates a simple HTML page that tells the user he or she has successfully added a record to the database.

Line 90 ends this subroutine.

91: sub Display_Page { 92:     print<<'    HTML';

Line 91 begins the Display_Page subroutine. This subroutine simply generates an HTML page to inform the user that his or her record addition has been successful.

Line 92 begins a here document. By using a here document, we save ourselves from having repeatedly to type print statements at the beginning of each line.

93:     <html><head><title>Record Added!</title></head> 94:      <body> 95:       <center> 96:        <font size="6"> 97:         Record Added! 98:        </font> 99:        <hr /> 100:       <font size="4"> 101:       <a href="/phonebook/phone.html">Back to Main Page</a> 102:        </font><br /> 103:       </center> 104:      </body> 105:     </html>

Lines 93-105 are the HTML for the entire page we are generating.

106:     HTML 107: }

Line 106 ends the here document that we begin on line 92.

Line 107 closes this subroutine.

108: sub Handle_DB_Error { 109:     print<<'    HTML';

Line 108 begins the Handle_DB_Error subroutine. This subroutine is used to display an error message if there is a database error of some kind.

Line 109 begins a here document used to print the HTML for the error page.

110:     <html><head><title>Database Error</title></head> 111:      <body> 112:       <center> 113:        <font size="6"> 114:         Error with database call. 115:        </font> 116:        <hr /> 117:        <font size="4" color="red"> 118:         $DBI::errstr 119:        </font><br /> 120:        <font size="3"> 121:         Please hit your <b>back</b> button to re-enter the              data and try again. 122:        </font> 123:       </center> 124:      </body> 125:     </html>

Lines 110-125 are the HTML needed for our error page. On line 118, we print the $DBI::errstr variable, which is a DBI variable that contains the error message.

126:     HTML 127:     exit; 128: } 

Line 126 closes the here document that we begin on line 109.

Line 127 calls the exit function. Since an error has occurred, we want to call the exit function to be absolutely sure that the program does not continue from here.

Line 128 closes this subroutine and is the end of the program as well.

So, in 128 lines of code, much of it just HTML strings, we have a Web application that can search its database table and add records to the database table. If you feel up to it, use your knowledge from this book and modify this application so that you can also modify and delete records.

Web-based quiz program

The next, and final, example for this chapter is a Web-based quiz program. It actually comprises several related programs. For this system, we take a different approach. Instead of a large program that handles everything, we now have a central module that contains the common functions and several small CGI programs that perform a specific task.

The Quizzer program allows an 'administrator' to add new tests and add questions to tests. It also allows users to take the tests that have already been created. There is not much error-trapping code, nor is there a lot of code to make it look pretty. Instead, we will be working on the framework of a program that can be incorporated into a Web site and made to look very polished.

Remember that with Perl, there is more than one way to do it! You may find this programming style easier than one where you try to keep all of the functionality in one larger program. The idea is that if you like to program one way, do so. Perl doesn't force you to do things this way or that way. Sure, there are rules that must be followed, but the main task at hand is what is important, not how you go about handling that task.

This program is not meant for public consumption, not without a few changes. The administration programs and HTML pages are probably not something you want everyone to have access to. It is up to you to put those pages and scripts in a location that you have made safe if you don't want them to be accessible to everyone.

The SQL information for creating the tables can be found at the end of this chapter or online at this book's Web site.

The online Quizzer application consists of:

  • A MySQL database with three tables

    • Questions

    • Answers

    • Test_config

  • Three HTML files for navigation

    • index.html

    • admin.html

    • create_test.html

  • Six Perl/CGI programs

    • add_question.cgi

    • create_test.cgi

    • test_chooser.cgi

    • take_test.cgi

    • score_test.cgi

    • Quizzer.pm

That seems like a lot, but with each program performing a specific task, if there is an error, this strategy can make it much easier to debug. Let's start off by looking at the HTML pages. These pages are not ornate, only functional. Also, as far as the HTML goes, only the important information is pointed out.

index.html

start example
01: <html><head><title>Quizzer v0.1</title></head> 02:  <body> 03:   <center><font size="6">Quizzer v0.1</font></center> 04:   <hr width="75%" /> 05:   <p align="center"> 06:    <font size="4"> 07:     <ul> 08:      <li><a href="/cgi-bin/quizzer/test_chooser.cgi">Take           a Test</a></li> 09:      <li><a href="admin.html">Admin Page</a></li> 10:     </ul>  11:    </font> 12:   </p>  13:  </body> 14: </html>
end example

This file is just a simple 14 lines of HTML. It provides you with two choices: a link to choose a test to take or a link to the administration page. For a real-world situation, you may choose to forgo this page and link directly to the 'test chooser' page so that the user can only choose a test to take and not have the option to go to the administration pages.

admin.html

start example
01: <html><head><title>Quizzer v0.1</title></head> 02:  <body> 03:   <center><font size="6">Quizzer v0.1</font></center> 04:   <table border="1" align="center"> 05:    <tr> 06:     <td> 07:      <font size="4"> 08:       <ul> 09:        <li><a href="create_test.html">Create a new                 test</a></li> 10:        <li><a href="/cgi-bin/quizzer/test_chooser?action=add">                Add a question to a test</a></li> 11:        <li><a href="index.html">Main Page</a></li> 12:       </ul>  13:      </font> 14:     </td> 15:    </tr> 16:   </table>  17:  </body> 18: </html>
end example

This page simply provides test administrators with a link to create a test or to add a question to a test.

Line 10 is of interest here. On this line, we provide a link to the test_chooser.cgi program-but this time we add ?action=add to the end of the URL. We do this to tell the test_chooser.cgi program that we want to add a question, so take the appropriate actions to make sure we get to the proper pages. The test_chooser. cgi program looks for this data added to the URL. If it finds it, a different page title is shown, and the HTML form action also calls a different CGI program.

create_test.html

start example
01: <html> 02:  <head><title>Quizzer v0.1 - Create Test</title></head> 03:  <body> 04:   <center> 05:    <font size="6">Quizzer v0.1 - Create Test</font> 06:   </center> 07:   <hr width="75%" /> 08:   <form action="/cgi-bin/quizzer/create_test.cgi"          method="post"> 09:    <table border="1" align="center"> 10:     <tr> 11:      <td><b>Test Name:</b></td> 12:      <td><input type="text" name="test_name"></td> 13:     </tr><tr> 14:      <td><b>Number of Questions:</b></td> 15:      <td><input type="text" name="questions"></td> 16:     </tr><tr> 17:      <td><b>Choices per Question:</b></td> 18:      <td><input type="text" name="choices"></td> 19:     </tr><tr> 20:      <td colspan="2" align="center"> 21:       <input type="submit" value="Add Test to Database"> 22:      </td> 23:     </tr> 24:    </table>  25:   </form> 26:   <hr width="75%" /> 27:   <p align="center">  28:    <font size="4"><b> 29:     [ <a href="index.html">Main Page</a> ] 30:    </font> 31:   </p>  32:  </body> 33: </html>
end example

This file creates a form so that a test administrator can create a new test. There are three input fields: the test name, the number of questions that make up a test, and the maximum number of choices that each question will have.

The number of questions for a test does not mean how many questions an administrator will enter; it simply means how many questions a user needs to answer to 'finish' a test. For example, a test may have 100 questions in the database, but a test may only be 10 questions long. In a case like this, the user is presented with 10 randomly chosen questions, and the test is scored.

Figure 6-4 shows what the page generated by create_test.html looks like.

click to expand
Figure 6-4: Create test HTML form

Next, we are going to cover the Perl programs that make up the complete Quizzer application. We'll start with the administration programs so that you can get an idea how a quiz is created. Then we will cover the 'user' pieces; these are the pieces that a user sees. Finally, we will cover the Quizzer.pm module. We are covering the Quizzer.pm module last because this is the piece that brings it all together; if we cover it first, you will not have the basic understanding of how the application works as a whole. By covering it last, you will have already seen what the application is supposed to do-now we'll see how it does it.

The following program is called by the create_test.html file. This program simply takes the information entered into the HTML form and stores it in the MySQL database we are using for the Quizzer application.

create_test.cgi

start example
01: #!/usr/bin/perl -wT 02: # 03: # create_test.cgi 04: # Chapter 7 05: # Online Quizzer 06: # 07: use strict; 08: use DBI; 09: use CGI qw(:standard);
end example

Line 1 tells the system where to find Perl. It also turns on warnings and taint mode with -wT.

Lines 2-6 are simply comments that give information about the program.

Line 7 turns on the strict module. The strict module forces the programmer to declare variables and enforces more rules on the programmer so that common mistakes can be avoided.

Line 8 uses the DBI module. This allows us to have access to the database that the test information is stored in.

Line 9 loads the CGI module and reads in the standard methods.

10: my $conn = DBI->connect("DBI:mysql:quizzer",        "bookuser","testpass")        or die("Cannot connect: $DBI::errstr\nAborting");

Line 10 creates a connection to the Quizzer database and returns a reference (handle) to the database, which we store in the $conn variable. If there is a problem connecting to the database, the program will call die and an error will be printed.

11: my $test_name = param(‘test_name'); 12: my $questions = param(‘questions'); 13: my $choices   = param(‘choices');

Lines 11-13 declare some variables with my and use the param function from the CGI.pm module to each read in the data from the HTML files form fields.

14: my $rval = $conn->do("INSERT INTO test_config SET  15:     NumQs=$questions, Choices=$choices,  16:     test_name='$test_name'");

Lines 14-16 are a single Perl statement that declares a my variable named $rval (which stands for return value, in this case) and sets it to the value returned by the do statement. The do statement here is a call to the do method on the SQL statement inside of the parentheses. Since we are not returning any data, we can skip the prepare/execute way of accessing the database and do it in a single call with do.

The following SQL statement takes the information passed from the HTML form in lines 11-13 and stores it in the database.

17: my $val = $conn->{‘mysql_insertid'}; 18: print header();

Line 17 calls the mysql_insertid MySQL function. This function returns the value of the last autoincremented field. This feature is really not needed for our program, but I found that there really weren't any documented examples of its usage so I wanted to include an example with it in actual use. If there is a problem, undef is returned by this function call.

Line 18 prints the value/data returned by the header function. The header function is part of the CGI.pm module. This simply prints our Content-Type header.

19: if($rval) { 20:     print <<"    HTML"; 21:      <html><head><title>Test Added!</title></head> 22:       <body> 23:        <font size="4"> 24:         <center> 25:          Test $val Added to Database!<br> 26:         </center> 27:        </font> 28:       </body> 29:      </html> 30:     HTML 31: }

Line 19 checks to see if the variable $rval contains any data. If it does, we enter this block of code.

Line 20 begins a here document that prints the HTML message informing the user/administrator that the addition of a new test has been successful.

Note that in this here-document, the terminating string is surrounded by double quotes. This causes the HTML data inside of the here document to behave as if it were a double-quoted string. If we had used single quotes, the variable would not be interpolated on line 25.

Lines 21-29 are simply the HTML message we are going to display. Notice that on line 25 we have a variable, $val, which will be interpolated with its current value when this page is displayed.

Line 30 is the ending tag for the here document. Since we use four spaces before the ending tag on line 20, we must also have four spaces before the ending tag on this line.

Line 31 closes this block of code.

32: else { 33:     print <<"    HTML"; 34:      <html><head><title>Error!</title></head> 35:       <body> 36:        <font size="4"> 37:         <center> 38:          ERROR!!! ($DBI::errstr)<br /> 39:          Something unexpected happened!<br />  40:         </center> 41:        </font> 42:       </body> 43:      </html> 44:     HTML 45: }

Line 32 begins the else part of this if..else block. If the variable $rval contains no value, or undef in this case, we enter this block of code and execute it.

Line 33 begins the here document used to print the error message to the user.

Lines 34-43 are the HTML for the error message.

Line 44 ends the here document that we begin on line 33.

Line 45 closes the else portion of the if..else block. This is also the end of this program.

This program simply reads the three values that are entered on the HTML form, stores them into the database, and displays a message to the user, telling them that they are successful or that an error has occurred.

The following program is the CGI used to add a question to an existing test. It looks at the test-configuration file and dynamically generates an HTML form so that the test administrator can simply fill in the blanks to create a new question. Let's take a look at the code for this program.

add_question.cgi

start example
01: #!/usr/bin/perl -wT 02: # 03: # add_question.cgi 04: # Chapter 7 05: # Online Quizzer 06: # Add question 2 07: # 08: use strict; 09: use CGI qw(:standard); 10: use lib qw(.); 11: use Quizzer;
end example

Line 1 tells the system where to find Perl and turns on warnings and taint checking with the -wT.

Lines 2-7 are comments that give information about the program.

Line 8 turns on the strict module. strict causes the programmer to be more careful with declaring variables and also turns on other safeguards to help prevent common programming mistakes. Using strict is something you should always do.

Line 9 loads the CGI module and imports the standard set of functions. The CGI module should be used for all HTML form parsing. Writing your own HTML form parser is possible, but the CGI module is a proven and works very well. The CGI module also has been very thoroughly tested for security holes.

Line 10 tells Perl that the current directory can also be a library directory-it actually gets added to the @INC array. This means that when you load modules with use, Perl also looks in the current directory for a matching module. We need to do this because our Quizzer.pm module is in the same directory as this program.

The qw(.) is actually a bit more verbose than it could be; we could have simply used this:

Use lib ".";

There are two reasons why we didn't do that, though. First, using qw is a standard way of doing this. Second, if, at a later time, you want to add more directories to this list, you can easily do so by just adding more paths in the parentheses.

Line 11 loads the Quizzer Perl module. This is the module that contains much of the functionality of the programs for our Quizzer application.

12: my $test_id = param(‘test_id'); 13: my $Qtext   = param(‘Qtext'); 14: my $correct = param(‘correct'); 15: my @false   = param(‘false'); 16: my $rval;

Line 12 declares a my scalar variable named $test_id and reads in the value passed in from the test_id input box on the HTML form. This is the ID that the database has the test stored as.

Line 13 declares a my scalar variable named $Qtext and reads in the value passed in from the Qtext input box on the HTML form. This is the question text.

Line 14 declares a my scalar variable named $correct and reads in the value passed in from the correct input box on the HTML form. This is the text of the correct answer.

Line 15 declares a my array variable named @false and reads in the all of the values passed in from the text-input boxes named false on the HTML form. These are the text of each of the false answers.

Line 16 declares a my scalar variable named $rval. This is used to store a return value later on.

17: my ($TestID, $NumQs, $Choices, $test_name) =        Get_Test_Config($test_id); 18: if($Qtext ne ‘'){ 19:    $rval = Add_Question($Qtext, $test_id, $correct, @false); 20: }  21: print header();

Line 17 calls the Get_Test_Config function and passes it the test ID number. This function is located in the Quizzer.pm module. It takes a test ID as its input, and returns the test ID, number of questions, number of choices for each question, and the test name. These returned values are stored in the variables $TestID, $NumQs, $Choices, and $test_name.

Line 18 checks to see if the value in $Qtext contains any data. In English, this statement is saying: If $Qtext is not empty, enter this code block. So, if $Qtext is empty, we don't want to add the question to the database. By not adding the question to the database, $rval remains without a value. (We'll check that in a moment.)

Line 19 calls the Add_Question function and passes $Qtext, $test_id, $correct, and @false to the function. The function returns 1 if it is successful, and that value gets stored in $rval.

Line 20 closes the small code block that we begin on line 18. Since we are using Perl, and the motto is There is more than one way to do it!, this could have been written on a single line instead. This is basically what that looks like as a single line statement:

$rval = Add_Question($Q, $T, $C, @F) if($Q ne ‘');

This does the same thing, but is a bit too long to fit nicely in this book (if we used the same variable names), so we opt for a regular if block.

Line 21 prints the return value of the header function. The header function is part of the CGI.pm module.

22: print <<HTML; 23:  <html><head><title>Add A Question</title></head> 24:   <body> 25:    <form action="add_question.cgi"> 26:     <input type="hidden" name="test_id" value="$test_id"> 27:     <table border="1" align="center"> 28:      <tr bgcolor="#c0c0c0"> 29:       <td colspan="2" align="center"> 30:       <font size="6">Add question to $test_name test.</font> 31:       </td> 32:      </tr>  33:      <tr bgcolor="#e0e0e0"> 34:       <td>&nbsp;</td> 35:       <td align="center"><b>Question/Answer Text</b></td> 36:      </tr> 37:      <tr> 38:       <td bgcolor="#e0e0e0"> 39:        <b>Question:</b> 40:       </td> 41:       <td><input type="text" size="60" name="Qtext"></td> 42:      </tr> 43:      <tr> 44:       <td bgcolor="#e0e0e0"> 45:        <b>Correct Answer:</b> 46:       </td> 47:       <td><input type="text" size="60" name="correct"></td> 48:      </tr> 49: HTML 

Lines 22-49 are a here document that prints the top portion of the HTML page. Notice on line 25 that this CGI makes itself the target (‘action') of the form that it produces. This is perfectly normal-when the user hits ‘Submit,' it just produces another instance of this program, to carry on processing. If data is entered, and this CGI program is called, it will again display a form so that the user can enter another question-but the data that is entered is displayed at the bottom of the page, so the user can see that what he or she has entered has been added to the database.

Line 30 uses the $test_name variable to make the HTML page have a title in the table so that the user knows which test he or she is adding a question to.

50: for (1..($Choices - 1)){ 51:     print qq( 52:         <tr> 53:          <td bgcolor="#e0e0e0"> 54:           <b>False Answer:</b> 55:          </td> 56:          <td> 57:           <input type="text" size="60" name="false"> 58:          </td> 59:         </tr> 60:     ); 61: }

Line 50 begins a for loop that loops from 1 to the value of ($Choices - 1). We take one away from the $Choices variable because we already have an input item for the correct answer in line 47. This loop is used to create the proper number text input boxes for the false answers.

Line 51 begins a qq block. qq is yet another way to quote text. The way we are using qq here is very similar to a here document. Line 51 opens the qq( block, and anything between it and the closing ) on line 60 gets printed.

qq is a nice way to print because variables still get interpolated, as in a double-quoted string, but you do not have to escape any of the quotes or double quotes that you may have inside of the block.

Lines 52-59 are simply HTML elements that make up the page we are displaying.

Line 60 closes the qq block that we began on line 51.

Line 61 closes the for loop that we begin on line 50.

62: print <<HTML; 63:     <tr bgcolor="#e0e0e0"> 64:      <td colspan="2" align="center"> 65:       <input type="submit" value="Add Question"> 66:      </td> 67:     </tr> 68: HTML 

Line 62 begins a here document. Again, we could have done it another way and used qq instead. These examples show you over and over that with Perl, you have a lot of freedom with how you program. If you are more comfortable with qq blocks, by all means change this to a qq block.

Lines 63-67 are HTML for the add question form.

Line 68 closes the here document.

69: if($rval){ 70:     print qq( 71:         <tr><td colspan="2"> 72:          <b><u>Question Added</u></b><br> 73:           <b>$Qtext</b> 74:           <ul> 75:      <li><font color="green">$correct</font> *Correct answer 76:     ); 77:     foreach my $tmp (@false){ print "<li>$tmp" if($tmp); } 78:     print qq(</ul></td>); 79: }

Line 69 checks to see if the scalar variable $rval contains any data. If so, this block of code is entered.

Line 70 begins a qq block for printing some HTML data.

Lines 72-75 are HTML for the resulting page we are generating.

Line 76 closes the qq block that begins on line 70.

Line 77 begins a foreach loop that traverses through all of the false responses and displays them in an HTML unordered list. This line just incorporates a whole foreach block of code onto a single line.

Line 78 prints some HTML tags.

Line 79 closes the if block that begins on line 69.

80: print <<HTML; 81:     </tr> 82:     <tr> 83:      <td colspan="2" align="center"> 84:       <a href="/quizzer/admin.html">Admin Menu</a> 85:      </td> 86:     </tr> 87:    </table> 88:   </form></body></html> 89: HTML 

Line 80 begins a here document that prints the closing HTML for this page.

Lines 81-88 are the closing HTML for this page.

Line 89 closes the here document and is the end of this program.

The resulting page should look something like the page in Figure 6-5.

click to expand
Figure 6-5: Add question form

That is it for the add_question.cgi program-the program that generates an HTML form based upon the test configuration, accepts the input from the test administrator, and stores the new questions in the database.

The next CGI application we look at is the test_chooser.cgi program. This program queries the database to get a list of all of the available tests; then it generates a page with a drop-down list for the user to choose a test from.

test_chooser.cgi

start example
01: #!/usr/bin/perl -wT 02: # 03: # test_chooser.cgi 04: # Chapter 7 05: # Online Quizzer 06: # Test Chooser 07: #
end example

Line 1 should be very familiar by now. This line tells the system where to find Perl and turns on warnings and taint checking.

Lines 2-7 are simply comments about the program.

08: use strict; 09: use lib qw(.); 10: use Quizzer; 11: use CGI qw(:standard);

Line 8 turns on strict.

Line 9 tells Perl that the current directory (.) is to be added to the @INC array. This allows modules in the current directory to be imported with the use statement.

Line 10 imports the Quizzer module.

Line 11 imports the CGI module and its :standard functions.

12: my $passed = param(‘action');

Line 12 declares a scalar variable named $passed and uses the param function from the CGI module to read in the value passed from the HTML form. In this case, action is actually passed only on the URL by adding ?action=add.

13: my $title  = "Choose a Test to Take"; 14: $title     = "Add Question to?"  if($passed eq ‘add'); 15: my $action = "take_test.cgi"; 16: $action    = "add_question.cgi"  if($passed eq ‘add');

Line 13 declares a scalar variable named $title and sets it to an initial value.

Line 14 changes the value of $title if the value of $passed has been added. If not, it leaves the value of $title alone.

Line 15 declares a scalar variable named $action and sets it to an initial value.

Line 16 changes the value of $action if the value of $passed has been added. If not, it leaves the value of $action alone.

Lines 13-16 effectively perform an if..else, but we were able to leave off the else part by setting the variables to an initial, default, value.

17: my $sth_testlist = Get_Test_List(); 18: print header(); 

Line 17 calls the Get_Test_List function and stores the results of the function call in the variable named $sth_testlist. This function returns a handle to the query results, so $sth_testlist is a handle that references the results.

Line 18 prints the data returned by a call to CGI.pm's header function.

19: print <<"HTML"; 20:     <html><head><title>Choose A Test</title></head> 21:      <body> 22:       <form action="$action"> 23:        <table border="1" align="center"> 24:         <tr bgcolor="#c0c0c0"> 25:          <td align="center"> 26:           <font size="5">$title</font> 27:          </td> 28:         </tr> 29:         <tr> 30:          <td align="center"><select name="test_id"> 31: HTML

Line 19 begins a here document that prints the beginning of the HTML page and also begins a drop-down select box named test_id.

Lines 20-30 are the beginning HTML for the page this program generates.

Line 22 places the $action value into the HTML form action tag. This gives the program the ability to dynamically change the action of the HTML form that it generates.

Line 31 ends the here document that begins on line 19.

32: while(my $p = $sth_testlist->fetchrow_hashref){ 33:     print "<option value='$p->{TestID}'>$p->{test_name}</option>"; 34: }

Line 32 begins a while block that fetches the data from the database. Notice that we are using the fetchrow_hashref function from the DBI module, but we haven't even had a use DBI statement yet. This is because the Quizzer.pm module handles all of the database functions for us, and the necessary use statements to the DBI module are contained within it.

Line 33 prints the options that make up the drop-down box on the HTML form. The value attribute of the option is the TestID, and the value shown in the drop-down box is the test_name.

Line 34 ends the while loop that begins on line 32.

35: print <<HTML; 36:            </select> 37:           </td> 38:          </tr> 39:          <tr bgcolor="#e0e0e0"> 40:           <td align="center"> 41:            <input type="submit" value="Choose Test"> 42:           </td> 43:          </tr> 44:         </table> 45:        </form> 46:      </body></html> 47: HTML

Line 35 begins a here document that prints the closing HTML tags needed for the page we are generating.

Lines 36-46 are the HTML for the page.

Line 47 closes the here document and is also the last line of this program.

This wraps up the test_chooser.cgi program. This is a short program that displays a simple HTML form with the various tests in a drop-down box, but it is a necessary springboard to the other parts of the application because the application must know which test to work with.

The next program, take_test.cgi, is what a user sees when he or she takes a test. This program reads the test-configuration information, gets a random question, randomizes the order of the answer-choices, and displays it for the user. This program is doing quite a bit, but we have kept the code to a minimum by consolidating everything that we can into the Quizzer.pm module.

take_test.cgi

start example
01: #!/usr/bin/perl -wT 02: # 03: # take_test.cgi 04: # Chapter 7 05: # Online Quizzer 06: # Test taker thing 07: #
end example

Line 1 tells the system where to find Perl and turns on warnings and taint checking.

Lines 2-7 are comments about this program.

08: use strict; 09: use lib qw(.); 10: use CGI qw(:standard); 11: use Quizzer; 

Line 8 turns on the strict module. This forces the programmer to declare variables and prevents many common programming mistakes, such as having two variables in the same scope with the same name.

Line 9 tells Perl to add the current directory (.) to the @INC array. This line must come before our call to the Quizzer module, since the Quizzer module is in the same directory as this program and since this directory is not in the @INC array yet.

Note 

Remember @INC is an array of paths that Perl uses to find libraries and modules.

Line 10 imports the CGI module and its standard functions.

Line 11 imports the Quizzer module.

12: my $cookie  = cookie(‘Quizzer'); 13: my $a_id    = param(‘a_id'); 14: my $test_id = param(‘test_id'); 15: my $q_id    = param(‘q_id');

Line 12 declares a scalar variable named $cookie and stores the results returned by a call to the CGI modules cookie function. The call to the cookie function passes the string ‘Quizzer' to it, so this call fetches the cookie named Quizzer if there is one stored on the users browser.

Line 13 declares a scalar variable named $a_id and uses CGI.pm's param function to fetch the value from the a_id form field on the HTML page that calls this CGI program.

Line 14 declares a scalar variable named $test_id and uses param to get the value passed from the HTML form.

Line 15 declares a scalar variable named $q_id and uses the param function to get the value passed from the HTML form.

16: $test_id    = 2 unless($test_id); 17: my ($TestID, $NumQs, $Choices, $test_name) =          Get_Test_Config($test_id);

Line 16 sets $test_id to 2 unless there is a value in $test_id. This means that if $test_id is empty, and nothing is passed from the HTML form, we set it to 2 as a default value. You can change the 2 to the ID of whatever test ID you want to be the default test.

Line 17 calls the Get_Test_Config function and passes it $test_id. This function returns four values that are stored in $TestID, $NumQs, $Choices, and $test_name, respectively. The Get_Test_Config function is located in the Quizzer.pm module.

18: # check cookies here 19: my $data = "$TestID:$q_id:$a_id"; 20: $data    = $data . "*" . $cookie; 21: $data    = ‘' unless($a_id);

Line 18 is simply a comment.

Line 19 declares a scalar variable named $data and sets it to a string consisting of the values of $TestID, $q_id, and $a_id all joined by a colon. This is the correct response to the question that the user just answered. The way we encode the answers makes them look something like this 21:32:43. The first number is the test ID, then the question ID, and finally the answer ID.

Line 20 then takes the current value of $data and appends an asterisk to it and then the existing cookie. This prepends the current answer to the cookie that already exists.

Line 21 sets $data to an empty string unless an answer was provided and is in $a_id.

22: my $write_cookie = cookie(  23:     -name    => ‘Quizzer', 24:     -value   => $data, 25: );

Line 22 declares a scalar variable named $write_cookie and stores the value returned by a call to CGI.pm's cookie function.

Note 

We use a cookie to store the answers that the user has given to the questions. An HTTP cookie allows us to store pieces of data on the client computer.

Lines 23-24 pass some information to the cookie function.

Line 25 ends the block that contains the cookie function arguments that we opened on line 22.

26: my ($Q, $Ans, $taken) =        Get_Question($test_id, $data, $NumQs, $write_cookie); 27: my $Qtext = $Q->{‘Qtext'}; 28: my $Qid   = $Q->{‘Qid'}; 29: $taken++;

Line 26 calls the Get_Question function. This function expects to be passed four items: $test_id, which is the ID of the test; $data, which is the answers of the questions already taken; $NumQs, which is the number of questions that make up a test session; and $write_cookie, which is the value of the updated cookie we need to write.

The Get_Question function returns three values: $Q, which is a reference to a hash containing the question; $Ans, which is a reference to a hash containing the answers; and $taken, which is the number of the current question.

Line 27 declares a scalar variable named $Qtext and sets it to the value stored in the Qtext key of the hash referenced in $Q.

Line 28 declares a scalar variable named $Qid and sets it to the value stored in the Qid key of the hash referenced in $Q.

Line 29 increments the $taken variable. This variable is used to show the user a count of questions they have taken so far (for example: Question 4 of 10).

Tip 

Using ++ is a shorthand way of saying '$x = $x + 1;'

30: print header(-cookie => $write_cookie);

Line 30 uses the header function from the CGI.pm module, as we have many times before. However, this time, we pass it a cookie that gets set on the user's system so that we can keep track of the questions that have already been answered.

31: print <<HTML; 32:  <html><head><title>Question $taken of $NumQs</title></head> 33:   <body> 34:    <form action="take_test.cgi"> 35:     <input type="hidden" name="test_id" value="$test_id"> 36:     <input type="hidden" name="q_id"    value="$Qid"> 37:     <table border="1" align="center"> 38:      <tr bgcolor="#c0c0c0"> 39:       <td align="center"> 40:        <font size="6">Test name: $test_name</font><br> 41:        Question $taken of $NumQs. 42:       </td> 43:      </tr>  44:      <tr> 45:       <td><b>$Qtext</b></td> 46:      </tr> 47: HTML

Line 31 begins a here document that is used to print the HTML that begins the page we are generating.

Lines 32-46 contain some of the HTML for the page we are generating.

Lines 35-36 have hidden variables so that we can keep track of the current test_id and q_id.

On line 40, we display the test name. On line 41, we show the user what question he or she is on and how many questions are on this test. Then, on line 45, we display the actual question.

Line 47 is the ending tag for the here document we begin on line 31.

48: my @k = keys %{$Ans}; 49: fisher_yates_shuffle( \@k );

Line 48 uses the keys function, which returns a list that consists of all of the keys in the hash passed to it-the hash referenced by the $Ans variable, in this case. This list is then stored in the new array @k that we also declare on this line.

Line 49 passes a reference to the @k array to the fisher_yates_shuffle subroutine, which is defined in the Quizzer.pm module. This performs a Fisher-Yates algorithm shuffle on the array, in place. The Fisher-Yates algorithm is an efficient method for randomizing the values in an array. We are merely randomizing the order in which the answers are presented to the user.

50: for my $key (@k){ 51:     print qq( 52:         <tr> 53:          <td> 54:           &nbsp;&nbsp;&nbsp; 55:           <input type="radio" name="a_id"                 value="$key"> $Ans->{$key} 56:          </td> 57:         </tr> 58:     ); 59: }

Line 50 loops through the @k array. Each time through the array, it sets $key to the current value. Notice that we use for and not foreach here-this is another Perlism; for and foreach are interchangeable in Perl.

Line 51 begins a qq block used to print the HTML needed for the page we are generating.

Lines 52-57 are the HTML for the generated page.

Line 55 creates the radio-button choices you see on the page.

Line 58 ends the qq block that begins on line 51.

Line 59 ends the for loop that begins on line 50.

60: print <<HTML; 61:     <tr bgcolor="#e0e0e0"> 62:      <td align="center"> 63:       <input type="submit" value="Submit Answer"> 64:      </td> 65:     </tr> 66:     <tr> 67:      <td colspan="2" align="center"> 68:       <a href="/quizzer/index.html">Home Page</a> 69:      </td> 70:     </tr> 71:    </table>  72:   </form></body></html> 73: HTML

Line 60 begins a here document that prints the HTML for the Submit button and the link to the Quizzer home page on the HTML form.

Lines 61-72 are the HTML tags needed for the table element and the Submit button.

Line 73 closes the here document that we begin on line 60.

When a user takes a test, he or she sees the questions and answers on a page like the one in Figure 6-6.

click to expand
Figure 6-6: Test Question page

This program, in just 73 lines of code, handles page generation and maintains that answers that the user has already submitted. It is even more impressive when you consider that a majority of the 73 lines are simply HTML needed to generate the page!

Well, so far we have created and taken tests, but we need some way to score the tests so that the user can see how he or she has done. The score_test.cgi is a small program that reads the cookie on the user's system, checks the database to verify the answers, and gives the user his or her score, along with the answers to the questions he or she has missed.

There is no extra 'fluff' to this program. If you are going to incorporate this into a public site, you may want to work on making the output nicer looking and maybe even adding a pass/fail notification.

score_test.cgi

start example
01: #!/usr/bin/perl -wT 02: # 03: # score_test.cgi 04: # Chapter 7 05: # Online Quizzer 06: # Test scoring program 07: #
end example

Line 1 tells the system where to find Perl and turns on warnings and taint checking.

Lines 2-7 are comments about this program.

08: use strict; 09: use lib qw(.); 10: use CGI qw(:standard); 11: use Quizzer;

Line 8 loads the strict module. This forces the programmer to declare variables and prevents many common programming mistakes, such as having two variables in the same scope with the same name.

Line 9 tells Perl to add the current directory (.) to the @INC array. This line must come before our call to the Quizzer module, since the Quizzer module is in the same directory as this program and this directory is not in the @INC array.

Line 10 imports the CGI module and its standard functions.

Line 11 imports the Quizzer module.

12: my $cookie  = cookie(‘Quizzer'); 13: my $test_id = param(‘test_id');

Line 12 declares a scalar variable named $cookie and calls the cookie function. The text ‘Quizzer' is passed to the cookie function, and the return value of this function gets stored in the $cookie variable.

Line 13 declares a scalar variable named $test_id and sets it to the value returned by the call to the param function on the right side. This gets the value passed from the test_id field on the HTML form that calls this program.

14: my ($TestID, $NumQs, $Choices, $test_name) =        Get_Test_Config($test_id); 15: my ($wrong, $score) = Score_Test($cookie); 16: print header();

Line 14 declares four new variables ($TestID, $NumQs, $Choices, and $test_name). Each of these new variables gets set to the values returned from the call to the Get_Test_Config function.

Line 15 declares two new scalar variables named $wrong and $score. These variables are set to the values returned from the call to the Score_Test function. In the call to the Score_Test function, we pass the value stored in the $cookie variable.

Line 16 prints the data returned from the header function.

17: print <<HTML; 18:  <html><head><title>Score Test</title></head> 19:   <body> 20:    <form action="take_test.cgi"> 21:     <input type="hidden" name="test_id" value="$test_id"> 22:     <table border="1" align="center"> 23:      <tr bgcolor="#c0c0c0"> 24:       <td align="center" colspan="2"> 25:       <font size="5">Missed Questions: $test_name</font><br> 26:        Score: $score\% 27:       </td> 28:      </tr>  29:      <tr bgcolor="#e0e0e0"> 30:       <td><b>Question</b></td> 31:       <td><b>Correct Answer</b></td> 32:      </tr> 33: HTML

Line 17 begins a here document that prints the HTML needed to generate the top portion of the page that displays the user's test score.

Lines 18-32 are the HTML for the test score page. On line 25, the test name is printed. On line 26, the actual score is printed.

Line 33 ends the here document that we begin on line 17.

34: for my $item (@$wrong) { 35:     print qq( 36:         <tr> 37:          <td> 38:           $item->{‘question'} 39:          </td> 40:          <td> 41:           $item->{‘answer'} 42:          </td> 43:         </tr> 44:     ); 45: }

Line 34 begins a for loop that traverses through each of the array elements in @$wrong. The variable $wrong is a reference to an array; it holds only the address where Perl can find the array. By putting the @ in front of it, we tell Perl that we want to get at the array that $wrong holds the address to. In other words, we dereference the array.

Line 35 begins a qq block that prints the questions and correct answers that the user got incorrect.

Lines 36-43 print the HTML for the page we are generating.

Line 44 closes the qq block that we begin on line 35.

Line 45 closes the for loop that we begin on line 34.

46: print <<HTML; 47:     </tr> 48:     <tr> 49:      <td colspan="2" align="center"> 50:       <a href="/quizzer/index.html">Home Page</a> 51:      </td> 52:     </tr> 53:    </table> 54:   </form></body></html> 55: HTML

Line 46 begins a here document that finishes off the HTML for the page we are generating.

Lines 47-54 are the closing HTML tags for the page.

Line 55 ends the here document that we begin on line 46. This line is also the last line in this program.

When the user is finished and clicks the Score Test link, he or she sees a page like the one in Figure 6-7.

click to expand
Figure 6-7: Score Test page

So, we have finished all of the programs needed to create tests, add answers, take tests, and score tests. Now we need to look at the module that does most of the work for us.

Quizzer.pm is the module that does most of the work for our online quiz application. We call it a module instead of an application because Quizzer.pm cannot be run on its own. Instead, it contains many of the functions we have used in the preceding programs. Quizzer.pm also contains the database routines for all of the applications, except the create_test.cgi program.

Let's dive into the Quizzer.pm code so we can bring together how all of these programs work.

Quizzer.pm

start example
01: # Quizzer.pm 02: package Quizzer; 03: require Exporter; 04: use DBI;
end example

Line 1 is a comment with the name of this module. Notice that this module does not have the normal #!/usr/bin/perl that all of the other programs have-that is because this module cannot be executed on its own.

Line 2 declares that this file is in the Quizzer namespace. This is important because it helps us separate the variables in this file from the variables in the files that import this one. For example, if we create a variable named $foo in this file and also one in our main program named $foo, how will we know which is which?

The variable in this file is actually named $Quizzer::foo, and the one in the main application is named $main::foo. If the first part is left off, and it usually is, the default is the $main:: package.

The scope of this namespace is from the point where it is declared to the end of this file. If it is declared in a block, the scope ends when the block does-just as with a my variable.

Line 3 brings in the Exporter module. We use this so that we can export functions and variables to the applications that use this module. This enables us to keep some of the functions and variables "private" to this module.

Line 4 imports the DBI module. We'll need this module for our database connectivity in this Quizzer.pm module.

05: my $conn = DBI->connect("DBI:mysql:quizzer", "bookuser",        "testpass") 06:     or die("Cannot connect: $DBI::errstr");

Line 5 creates a connection to the Quizzer database and stores a handle to the connection in the variable named $conn.

This connection is being made outside of all of the functions in this module so that it is globally available to all of them in this module. This means that we need only to connect to the database once, instead of a different time for each different subroutine that calls the database.

Line 6 is a continuation of line 5. It tells the system to die if there is a problem connecting to the database.

07: our @ISA    = qw(Exporter); 08: our @EXPORT = qw(Add_Question Get_Test_Config Get_Question                   Score_Test fisher_yates_shuffle Get_Test_List);

Line 7 tells Perl that this module is a subclass of the Exporter module. This is so that exporting will work right, when a program says use Quizzer.

Line 8 tells Perl which subroutines are okay to export by default. If you create a new subroutine, and forget to add it to your @EXPORT array, when you try to access it from a program you get an error; Perl won't be able to find the subroutine. If you are accustomed to the terms public and private, this line makes the subroutines public.

09: sub Get_Test_List { 10:   my $sql = qq(SELECT test_name, TestID  11:       FROM test_config ORDER BY test_name); 12:   my $sth_getlist = $conn->prepare($sql); 13:   $sth_getlist->execute() or die("Error! $DBI::errstr\nAnborting"); 14:   return($sth_getlist); 15: }

Line 9 begins the Get_Test_List subroutine.

Lines 10-11 are the SQL needed to get a list of test names from the database. The ORDER BY clause in the SQL statement causes the list of matches to be sorted on the field, or fields, following the clause.

Line 12 prepares the SQL statement and stores a handle to the prepared statement in the variable named $sth_getlist.

Line 13 calls the execute method on the $sth_getlist handle. If there is a problem executing this SQL query, die function is called, and an error is printed.

Line 14 returns the $sth_getlist variable, which is now a handle containing the matching data.

Line 15 closes the Get_Test_List subroutine.

That is it for the function that gets the list of available tests from the database.

16: sub Score_Test { 17:     my $cookie  = shift; 18:     my @data    = split(/\*/, $cookie); 19:     my $tot_q   = @data; 20:     my @wrong   = ();

Line 16 begins the Score_Test subroutine.

Line 17 declares a scalar variable named $cookie and shifts in the value that is passed to this subroutine when it is called.

Line 18 uses the split function to split the data in $cookie at the asterisk. When this is done, @data contains a list of data that is further split into the answers the user chooses.

Line 19 declares a scalar variable named $tot_q and stores the number of elements that are in the @data array into it.

Line 20 declares an array variable named @wrong and sets it to an initial value of (),ensuring that it contains no data.

21:  my $sth_Ans =  22:   $conn->prepare("SELECT a.Aid, a.Atext, q.Qtext 23:   FROM answers AS a, questions AS q WHERE  24:   ((q.Qid = ?) AND (a.Qid = q.Qid) AND (a.correct = ‘Y'))");

Line 21 declares a variable named $sth_Ans; this will be used as a statement handle to this SQL query. It also sets the variable to the value returned by the prepare statement on lines 22-24.

Line 22 prepares the SQL inside of its block using the $conn database handle that we create at the beginning of this module.

Lines 22-24 contain the SQL statement we are dealing with for this query. It is a fairly complex SQL query that pulls data from two different tables.

The a.Aid, a.Atext values come from the answers table, and the q.Qtext value comes from the questions table. So, we are fetching the answer ID (Aid), answer text (Atext), and the question text (Qtext).

On line 24, we have (q.Qid = ?)-where ? is a placeholder that allows us to dynamically get data for the appropriate question just by passing the question ID in the execute statement.

Also, the (a.Qid = q.Qid) ensures that this is the proper response for this question. Qid is the primary key of the questions table and is used in the answers table as a foreign key to link a question to an answer.

Finally, notice that we have (a.correct = ‘Y'), but we don't return this data. This field is used to let us know that this is the correct response for this question. If we had left this off, we would get back a list of all of the answers for this question (correct and incorrect). We care only about the correct response, so we limit our matches by using this statement.

25:     for(@data){ 26:         my %rec = (); 27:         my ($TestID, $q_id, $a_id) = split(/:/);

Line 25 begins a for loop that traverses through all of the elements in the @data array.

Line 26 declares a hash named %rec and initializes it to an empty set.

Line 27 declares three variables $TestID, $q_id, and $a_id, and sets them equal to the values returned by the split statement. The split statement here splits on the colon but has nothing telling it what variable to split, so it splits the default variable $_. Each time through the loop, the $_variable gets set to the current value of the @data array-this is what gets split.

28:  $sth_Ans->execute($q_id) or die("Error! $DBI::errstr\nAborting"); 29:  my $ans = $sth_Ans->fetch;

Line 28 calls the execute method on the $sth_Ans handle and passes the $q_id. The value in $q_id is substituted in for the placeholder in the SQL statement.

Line 29 declares a scalar variable named $ans and sets it to the value returned by calling the fetch method on the $sth_Ans handle. The fetch method returns a reference to an array, so $ans contains a reference to the array returned by fetch.

30:       if($ans->[0] ne $a_id) { 31:           $rec{‘answer'}   = $ans->[1]; 32:           $rec{‘question'} = $ans->[2]; 33:           push @wrong, \%rec; 34:       } 35:     }

Line 30 checks the first element of the array that the $ans variable references to see if it is not equal to the value stored in $a_id. The $ans->[0] tells Perl to get the first element of the array that $ans references. The $a_id variable contains the ID of the correct answer to this question. If these two values don't match, the user has responded to the question incorrectly.

Line 31 stores the correct answer text in the hash $rec at hash key answer.

Line 32 stores the question text in the hash $rec at hash key question.

Line 33 pushes a reference to the hash %rec onto the @wrong array. The @wrong array contains a list of hash references to all incorrect responses.

Line 34 closes the if statement that is opened on line 30.

Line 35 closes the for loop that begins on line 25.

So, we should have now looped through all of the answers the user has chosen. All incorrect responses are stored in the @wrong array.

You may be wondering how we know that the answer will be array element 1 and question array element 2. ($ans->[1]). The fetch method returns an array, and the array is always in the order that the fields are listed in the SQL statement. So, in our SQL statement, we have "SELECT a.Aid, a.Atext, q.Qtext"-this makes array element 0 Aid, 1 Atext, and 2 Qtext.

Also, each time through this loop, we reset the hash %rec to (), so it contains nothing. However, we store a reference to the hash in the @wrong array if the response is incorrect. This reference still exists in the @wrong array, even though we have cleared the hash!

When we go through and reset the hash on line 26, Perl assigns that hash a memory address. As long as there is still something referencing that memory address, Perl will not reuse it. Each time through the loop, a different memory address is assigned to %rec, and we maintain a reference to the hash in the @wrong array.

36:     my $tot_w = @wrong; 37:     my $tot_s = sprintf("%2.0f", ((($tot_q-$tot_w)/$tot_q) *             100)); 38:     return(\@wrong, $tot_s); 39: }

Line 36 declares a scalar variable named $tot_w and sets it to the number of items in the @wrong array. This gives us the total number of wrong responses.

Line 37 declares a scalar variable named $tot_s and sets it to the total score. We get the total score by taking the total number of questions ($tot_q) and subtracting the total number of wrong responses ($tot_w), then by dividing that by the total number of questions, finally multiplying it all by 100.

By using sprintf, we can tell Perl exactly what format we want this data in; it will round the results for us. So if the user got 10 out of 11 right, that's .9090909, which we'll multiply by a hundred, and then sprintf will round it to "91".

In this example, we set our results to at least 2 characters and 0 items after the decimal - which ends up giving us whole-number percentages. For more information, see 'perldoc -f sprintf'.

Line 38 returns a reference to the @wrong array and the $tot_s.

Line 39 closes the Score_Test subroutine that begins on line 16.

40: sub Get_Question { 41:     my $test_id      = shift; 42:     my $cookie       = shift; 43:     my $num_qs       = shift; 44:     my $write_cookie = shift;

Line 40 begins the Get_Question subroutine. This subroutine accesses the database and chooses a random question for the user. It is set up so that it will not return an answer if the user has already answered it.

Lines 41-44 declare some scalar variables and use the shift function to read in the values that are passed to the subroutine.

start sidebar
shift

In case you are wondering how the shift function works for subroutines like this, remember that the $_ variable is the default variable in Perl. If you call a function that expects a scalar variable and you do not pass one to the function, Perl assumes you mean to use the $_ variable.

The same thing goes for functions that expect to receive an array, except instead of defaulting to $_, they default to @_. @_ is the array that all values passed to a subroutine get placed in, so by just saying "shift", you end up shifting a value off of the @_ array. That is how shift works at the beginning of all of these functions.

45:     my %Question; 46:     my %Answer; 47:     my %Qid; 48:     my @data = split(/\*/, $cookie);
end sidebar

Lines 45-47 declare some hashes that we'll use to keep track of the questions and answers.

Line 48 splits the $cookie variable at the asterisks and stores the results in the @data array.

49:     my $sth_Qlist =  50:       $conn->prepare("SELECT Qid FROM questions              WHERE TestID = ?"); 51:     $sth_Qlist->execute($test_id)              or die("Error! $DBI::errstr\nAborting");

Line 49 declares a scalar variable named $sth_Qlist that we are going to use as a handle to the SQL statement on line 50.

Line 50 continues line 49. This is the SQL statement that we execute to get all of the questions from the database for a particular test. Notice that we use a placeholder so that we can prepare the SQL. Then we just pass a value in the execute statement to fill in the placeholder.

Line 51 calls the execute method on this handle. It passes the value in $test_id to the SQL statement. If there is some sort of problem executing this statement, the die method is called, and an error message is printed.

52:     my $Qnum = $sth_Qlist->fetchall_arrayref; 53:     @Questions = map { $_->[0] } @$Qnum; 

Line 52 declares a scalar variable named $Qnum and sets it to the value returned from the fetchall_arrayref method call on the $sth_Qlist handle. This causes $Qnum to hold a reference to an array of all of the matching values from the SQL statement.

Line 53 uses the map function to get the first element from each match in the @$Qnum array. Since we ask only to get back one value from the SQL query, all of the matches should be stored in the first element of the array.

This simply takes the array that is referenced to by the $Qnum variable and generates a regular array named @Questions that contains all of the matching values.

54:     for(@data){ 55:         my @tmp = split(/:/); 56:         $Qid{$tmp[1]} = 1; 57:     }

Line 54 begins a for loop that loops through each element in the @data array. Since we do not specify a variable for the current value to go into each time through the array, Perl's default variable, $_, is used.

Line 55 declares an array named @tmp and splits the current value of $_ at the colon. The result of this split is stored in the @tmp array. This is just a temporary array that we store the data in. It gets overwritten each time through the loop.

Each data element in the @data array looks something like this (43:31:12). Each number is an index to the test number, question, and answer that the user chooses. By splitting the colon, we are able to extract these elements and on the next line create a hash to maintain a list of the questions that have already been asked.

Line 56 uses the current question index, which is at $tmp[1], as a key in the $Qid hash. By setting this to 1, we are able to maintain a list of questions that have already been asked. The 1 is really irrelevant; we are looking for a duplicate key. If there is a duplicate key, we choose a different answer. (At this point, we are not choosing questions from the database, just building a hash of the questions that have already been asked.)

Line 57 closes the for loop that begins on line 54.

58:     my $taken = keys(%Qid); # Count number taken

Line 58 declares a scalar variable named $taken and counts the number of keys that are in the %Qid hash. This is how we get the number of questions that have been answered so far.

59:     while(1){ 60:         my $Qcount = @Questions;

Line 59 creates an infinite loop! while continues looping as long as the value inside of the parentheses is true and as long as 1 always evaluates as true. We want to do

this because inside of this loop we want to keep looping until we choose a question that has not been asked yet. Don't worry about the program getting 'stuck' here, though; we will add a few ways for the program to escape from this infinite loop.

Line 60 declares a variable named $Qcount and sets it to the number of items that are in the @Questions array. This is how we get a count of how many questions there are for this test.

61:         No_More_Questions($test_id, $write_cookie)  62:             if(($num_qs == $taken) or ($taken >= $Qcount));

Line 61 calls the No_More_Questions subroutine and passes it the $test_id and $write_cookie variables.

Line 62 limits how the subroutine on line 61 is called. This line is actually just a continuation of line 61. Notice that there is no semicolon on line 61. This line tells Perl to call only the No_More_Questions subroutine if the conditions listed are true.

The first condition is if the number of questions for this test equals the number of questions already taken. ($num_qs == $taken). $num_qs is the question-count for a complete quiz. There may be 38 questions for this test, but a complete "quiz" could have $num_qs set to 25 so that the user is presented with only 25 questions.

The next condition checks to see if the number of questions taken is equal to or greater than the number of total questions for this test ($taken >= $Qcount). If so, we will never find a question that has not yet been asked, so we have to exit this infinite loop anyway-even if the user has not been presented enough questions to make up a complete quiz. This can be the case if there are 14 questions for this test in the database, but a complete "quiz" is set up as 25 questions in the database. This prevents the program from getting stuck in the loop under this condition.

63:         $Qnum = $Questions[(int(rand($Qcount)))]; 64:         last unless(exists $Qid{$Qnum}); 65:     }

Line 63 sets $Qnum equal to the value at a random location in the @Questions array. (int(rand($Qcount))) chooses a random integer between 0 and $Qcount.

Line 64 calls the last function unless the question already exists in the %Qid hash at key $Qnum. last breaks out of the current loop it is in. The unless is the same as if not. exists checks to see if there is data at the hash location that is passed to it. So, if nothing exists in $Qid{$Qnum}, last gets called.

Line 65 closes the infinite while loop that we begin on line 59.

66:     $sql = qq{SELECT q.Qtext, q.Qid, a.Aid, a.Atext  67:               FROM questions AS q, answers AS a  68:               WHERE ((q.Qid = a.Qid) AND (q.Qid = ?))}; 

Lines 66-68 make up the SQL statement that we use to select the question and answers that we return from this subroutine. We are selecting Qtext and Qid from the questions table and Atext and Aid from the answers table. The (q.Qid = a.Qid) ensures that we get only the answers that are tied to this question. The AS q and AS a in the SQL statement allow us to create shorter names that we can then reference the tables as in the SQL statement.

69:   $sth_QA = $conn->prepare($sql); 70:   $sth_QA->execute($Qnum) or die("Error! $DBI::errstr\nAborting");

Line 69 prepares the SQL statement that we create on lines 66-68.

Line 70 executes the SQL statement and passes the value in $Qnum to take the place of the placeholder in the SQL statement. If there is a problem executing the SQL statement, the program dies and prints an error message.

71:     while(my $p = $sth_QA->fetchrow_hashref) { 72:         $Question{‘Qid'}       = $p->{‘Qid'}; 73:         $Question{‘Qtext'}     = $p->{‘Qtext'}; 74:         $Answer  {$p->{‘Aid'}} = $p->{‘Atext'}; 75:     }

Line 71 begins a while loop that fetches one row of data returned from the SQL call. A reference to the hash returned by the fetchrow_hashref method gets stored in the scalar variable $p.

Line 72 sets the $Question hash with Qid as the key to the value in the hash returned by the fetchrow_hashref method.

Line 73 sets the $Question hash with Qtext as the key to the value in the hash returned by the fetchrow_hashref method.

Line 74 sets the $Answer hash with the answer ID (Aid) as the key to the value in the hash returned by the fetchrow_hashref method.

Line 75 ends the while loop which begins on line 71.

If there are 4 answers to a question in the database table, 4 rows of data will be returned. The Qid and Qtext will be the same for all 4 rows because this doesn't change. However, the answers will all have different text and different answer IDs. So, we can end up with something like this:

$Question{‘31'} = 31; $Question{‘This is a question.'} = "This is a question. "; $Answer{‘23'} = "Answer number 1"; $Answer{‘12'} = "Answer number 2"; $Answer{‘78'} = "Answer number 3"; $Answer{‘94'} = "Answer number 4"; 

Each time through the loop, the $Question values get set to the same thing-but that is fine. An answer is added each time through the loop as well. To end up with what is in the preceding example, the loop has to be traversed 4 times-once for each question.

76:     return(\%Question, \%Answer, $taken); 77: }

Line 76 returns a reference to the %Question hash, a reference to the %Answer hash, and the value stored in the $taken variable.

Line 77 ends the Get_Question subroutine.

78: sub fisher_yates_shuffle { 79:     my $array = shift; 80:     my $i;

Line 78 begins the fisher_yates_shuffle subroutine. This subroutine is designed to take a reference to an array as the input, and it will randomize the items in the array in place. This means that we pass a reference to an array, and the array gets randomized. This is a commonly used method for randomizing arrays, the "Fisher-Yates shuffle," named after Sir Ronald A. Fisher and Frank Yates, who introduced the algorithm in example 12 of their 1938 book Statistical Tables.

Line 79 declares a scalar variable named $array and shifts the value passed to the subroutine into it. This value should be a reference to an array.

Line 80 declares a scalar variable named $i.

81:     for($i = @$array; --$i;) { 82:         my $j = int rand ($i+1);

Line 81 begins a for loop that sets $i to the current value each time through the loop and also decrements $i. This has the effect of setting $i to the number of elements in the array @$array and then counting down one by one.

Line 82 declares a scalar variable named $j and sets it to a random integer between 1 and $i.

83:         next if $i == $j; 84:         @$array[$i,$j] = @$array[$j,$i]; 85:     } 86: }    

Line 83 causes the for loop to skip to the next iteration if $i and $j are equal.

Line 84 swaps the values at $array[$i] and $array[$j].

Line 85 ends the for loop that we begin on line 81.

Line 86 ends this subroutine.

87: sub No_More_Questions { 88:     my $TestID = shift; 89:     my $cookie = shift;

Line 87 begins the No_More_Questions subroutine. This subroutine gets called if the program runs out of questions for a quiz or if the user has answered the predetermined number of questions that make up a complete quiz.

Line 88 declares a scalar variable named $TestID and shifts in the first value that is passed in the subroutine call.

Line 89 declares a scalar variable named $cookie and shifts in the second value that is passed in the subroutine call.

90:     print CGI::header(-cookie => $cookie); 91:     print qq(No more questions for this test.<br>); 92:     print qq(Click      <a href="/cgi-bin/quizzer/score_test.cgi?test_id=$TestID">); 93:     print qq(here</a> to score test.<br>);

Line 90 prints the HTTP header and passes the cookie stored in the $cookie variable to the client's browser.

Line 91 prints some informational text to the user.

Line 92 prints a link to the score_test.cgi program with the $TestID included in the URL so that the program knows what to score.

Line 93 prints some more informational text to the user.

94:     exit; 95: }

Line 94 exits the program. Since the user is done with all of the questions, we want to make sure that he or she just sees the information and links to score the test-we don't want the user to try and answer more questions.

Line 95 ends this subroutine.

96: sub Get_Test_Config { 97:     my $test_id = shift;

Line 96 begins the Get_Test_Config subroutine. This is the subroutine that gets called by several of the programs to get the configuration information for the test.

Line 97 declares a scalar variable named $test_id and shifts in the value that is passed in the subroutine call.

98:     my $sql     = qq{SELECT * FROM test_config WHERE                          TestID='$test_id'}; 99:     my $sth_cfg = $conn->prepare($sql);

Line 98 declares a scalar variable named $sql and sets it to the SQL statement that is needed to get the test configuration information.

Line 99 declares a scalar variable named $sth_cfg, which will be used as a handle to the preceding SQL statement. The statement on the right calls the prepare method, which returns a handle to the prepared statement.

100:     $sth_cfg->execute() or die("Error! $DBI::errstr\nAborting"); 101:     return ($sth_cfg->fetchrow_array()); 102: }

Line 100 calls the execute method on the $sth_cfg handle. If there is a problem with the execute, the program will die and print an error message.

Line 101 returns a pointer to the array of data that we have just fetched. This SQL statement gets only one record, so we know that the call to fetchrow_array contains the record we want.

Line 102 ends this subroutine.

103: sub Add_Question { 104:     my ($Qtext, $test_id, $correct, @false) = @_;

Line 103 begins the Add_Question subroutine. This subroutine takes the values passed to it and inserts them into the database table.

Line 104 reads in the values that passed in the subroutine call.

We don't use shift here to get the data; instead, we read in the data from the @_ array. This is another way of getting the data passed to a subroutine. In this case, it makes it easy to read in the three scalar values and then the array at the end.

105:   my $sql_Q  = qq{INSERT INTO questions (Qtext, TestID) 106:                  VALUES (‘$Qtext', ‘$test_id')}; 107:   my $sql_F  = qq{INSERT INTO answers (Qid, Atext) 108:                  VALUES (?, ?)};      # Wrong answers. 109:   my $sql_C  = qq{INSERT INTO answers (Qid, Atext, correct) 110:                  VALUES (?, ?, ‘Y')}; # Correct answer. 

Lines 105-110 declare three separate scalar variables and create three separate SQL statements.

The first SQL statement, stored in the $sql_Q variable, is used to insert a question into the question database table. This SQL statement remains static, so we don't use any placeholders.

The second SQL statement, stored in the $sql_F variable, is used to store the wrong/false answers into the answers database table. This SQL statement uses placeholders.

The third SQL statement, stored in the $sql_C variable, is used to store the correct answer in the answers database table. This one also uses placeholders and passes a Y into the "correct" database-table field so that we know that this is the correct answer.

111:     my $sth_Q  = $conn->prepare($sql_Q); 112:     my $sth_F  = $conn->prepare($sql_F); 113:     my $sth_C  = $conn->prepare($sql_C);

Lines 111-113 prepare each of the SQL statements and store the handles returned by the prepare method in their respective scalar variables.

114:     $sth_Q->execute() or die("Error! $DBI::errstr\nAborting"); 115:     my $Qid = $conn->{‘mysql_insertid'};

Line 114 executes the $sth_Q SQL statement and dies with an error message if there is a problem executing it.

Line 115 calls the mysql_insertid MySQL API function. This function call returns the value of the last autoincrement field. This value will contain the ID of the question that we've just added to the database table. The Qid is the primary key for the questions table and the foreign key for the answers table.

116:     $sth_C->execute($Qid, $correct)             or die("Error! $DBI::errstr\nAborting");

Line 116 executes the $sth_C SQL statement and dies with an error message if there is a problem executing it.

117:     for my $txt (@false) { 118:         next unless($txt); 119:         $sth_F->execute($Qid, $txt)                 or die("Error! $DBI::errstr\nAborting"); 120:     } 

Line 117 begins a for loop that iterates over each of the values in the @false array. Each time through the array, the variable $txt is set to the current value.

Line 118 jumps to the next iteration of the loop unless $txt contains some value. This means that if the current value in the @false array is blank, we just skip it.

Line 119 executes the $sth_F SQL statement that adds the false answers to the database. If there is a problem adding the record to the database, the program dies and displays an error message.

Line 120 closes the for loop that begins on line 117.

121:     return 1; 122: } 123: 1;

Line 121 returns 1 from this subroutine. Returning 1 from a subroutine is an easy way to determine if the subroutine has completed successfully. If you check the return value and it is 1, you know that the subroutine has made it to the end.

Line 122 closes this subroutine.

Line 123 returns 1 from this module. A module must return a true value, or Perl generates an error message.

That is it for this entire Web-based Quizzer application! Quite a bit of code, but by going through it all you should have learned a lot. We've tried to use many different techniques to maximize your exposure to some of the different ways you can do things in Perl.

If you've noticed that we used the -T, taint flag, in all of our CGI applications but never did anything with it, you are right! We have made no system calls, so there is no worry that these programs may have tried to do something with tainted data. Putting -T into every CGI program is a very good habit to get into; if you happen to add some code that is potentially dangerous, the taint flag should catch it and tell you about it.



Perl Database Programming
Perl Database Programming
ISBN: 0764549561
EAN: 2147483647
Year: 2001
Pages: 175

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net