|
|
Using CGI.pm With Formsby Brent MichalskiJuly 10, 1998 We have just begun to
tap the power of CGI and forms. This week's article takes us further into
the realm of CGI programming with a comment/survey form using the CGI.pm
module.
This week's programThis week we dive into the CGI.pm module. While we won't be getting too deep into CGI.pm in this article, the program we present will give you a definite flavor of CGI.pm's powerful features. CGI.pm is capable of handling file uploads, saving a script's state, cookie processing, and much more.It's important to note that this week's program does NOT check for many of the security issues that may arise in CGI scripts. This article and the corresponding program are for demonstration purposes only. We will be covering CGI security in a later series. If you would like to learn more about security and CGI programs, please visit The WWW Security FAQ by Lincoln Stein. This week's program is fairly simple in concept. It is a comment form that emails the data submitted by the Web form to an address specified in the script. While this is nothing too fancy, I've added a twist. Take a look at the first form. Notice that next to the Submit Comments button there is a checkbox that says: Please check here if you will fill out our survey. If you check this box and click the Submit Comments button, instead of the form simply being emailed, you are presented with a survey form. If the user then fills out the survey, all of the information gets emailed. As I mentioned, this is a fairly simple example, but you don't have to stop where I left you. You can build on this example to, say, make a completely interactive set of screens that your customers fill out, and then store the information in a database. Or, you could create an online test that only displays one question at a time, and have the next question be determined by the answer of the last. I'm sure you can think of many other ideas too. If you have any cool ideas, let me hear them. Diving InThis week's program is rather lengthy. I first wrote it without using any of the features of CGI.pm and it was over 230 lines. I was able to cut it back to around 140 lines by simply using some of the features in the CGI.pm module.I have numbered the lines of code; the line numbers are not part of the program. If you want to see the program without the line numbers, click here. The line numbers simply make it easier for me to talk through the program. 1: #!/usr/local/bin/perl 2: $mail_prog = '/usr/lib/sendmail'; 3: use CGI qw/:standard :html3 :netscape/; 4: $q = new CGI; 5: @months=('January','February','March','April','May','June', 6: 'July','August','September','October','November','December'); 7: $home="http://www.webreview.com"; 8: $message = ""; 9: $s_message = ""; 10: $found_err = ""; 11: &Get_Web_Data; 12: &Validate_Data; 13: if ($survey eq "1"){ 14: &Print_Survey; 15: exit; 16: } else { 17: &Send_Mail; 18: &Print_Thanks; 19: exit; 20: } 21: #### End of main program. 22: sub Send_Mail{ 23: $recip = "perlguy\@mailexcite.com" ; 24: open (MAIL, "|$mail_prog $recip"); 25: print MAIL "Reply-to: $first_name\n"; 26: print MAIL "From: $first_name\n"; 27: print MAIL "Subject: Web Review Survey\n\n"; 28: 29: print MAIL "The following comments$s_message were sent from the Web Review test form:\n\n"; 30: print MAIL "Name: $first_name $last_name\n" ; 31: print MAIL "E-Mail: $e_mail\n" ; 32: print MAIL "Comments: $comments\n\n" ; 33: 34: if($birth_month ne ""){ 35: print MAIL "*** Survey ***\n"; 36: print MAIL "Birth Month: $birth_month\n"; 37: print MAIL "Job Title: $job_title\n"; 38: print MAIL "Marital Status: $marital\n"; 39: print MAIL "Sex: $sex\n\n"; 40: } 41: print MAIL "The End.\n" ; 42: close (MAIL); 43: 44: return; 45: } # End of Send_Mail subroutine. 46: sub Print_Survey{ 47: # Variables to save some typing. 48: $FS="<FONT SIZE=2 FACE=ARIAL><B>"; 49: $FSNB="<FONT SIZE=2 FACE=ARIAL>"; 50: $FC="</B></FONT>"; 51: $FCNB="</FONT>"; 52: print<<HTML; 53: <HTML><HEAD><TITLE>CGI Sample Form Number 2</TITLE></HEAD> 54: <BODY BGCOLOR="#FFFFFF"> 55: HTML 56: print p({-align=>'CENTER'}, 57: font({-size=>"5",-face=>"arial"},"CGI Programming with CGI.pm (Form 2)"), 58: hr({-width=>'75%'}),"\n"); 59: 60: print "<CENTER><FORM METHOD=POST>\n"; 61: print hidden(-name=>'first_name',-value=>"$first_name"); 62: print hidden(-name=>'last_name', -value=>"$last_name"); 63: print hidden(-name=>'e_mail', -value=>"$e_mail"); 64: print hidden(-name=>'comments', -value=>"$comments"); 65: 66: print "<TABLE BORDER=1 CELLSPACING=0>\n"; 67: 68: print Tr({-BGCOLOR=>E0E0E0}, 69: [ 70: td(["$FS Name: $FC\n","$FSNB $first_name $last_name $FCNB\n"]), 71: td(["$FS Birth Month: $FC\n","$FSNB".popup_menu( -name=>'birth_month', 72: -values=>\@months)."$FCNB"]), 73: td(["$FS Job Title: $FC\n",textfield(-name=>'job_title', -size=>40)]), 74: td(["$FS Marital Status: $FC\n","$FSNB".radio_group( -name=>'marital', 75: -values=>['Married','Single','Unsure'])."$FCNB"]), 76: td(["$FS Sex: $FC\n","$FSNB".radio_group(-name=>'sex', 77: -values=>['Male','Female','Unsure'])."$FCNB"]), 78: td(["$FS Favorite Programming Language: $FC\n","$FSNB".radio_group(-name=>'language', 79: -values=>['Perl'])."$FCNB"]), 80: td({-colspan=>'2'},"<CENTER>", submit('Send Survey and Comments'),"</CENTER>") 81: ]); 82: 83: print<<HTML; 84: </TABLE></FORM><P><HR WIDTH=75%></CENTER> 85: </BODY></HTML> 86: HTML 87: return; 88: } # End of Print_Survey subroutine. 89: sub Validate_Data{ 90: if ($e_mail !~ /.+\@.+\..+/) { 91: $errmsg = "<p>Please enter a valid email address</p>\n" ; 92: $message = $message.$errmsg ; 93: $found_err = 1 ; 94: } 95: if ($comments eq "") { 96: $errmsg = "<p>Field 'comments' must be filled in.</p>\n" ; 97: $message = $message.$errmsg ; 98: $found_err = 1 ; 99: } 100: 101: if ($found_err) { 102: &Print_Error; 103: } 104: return; 105: } # End of Validate_Date subroutine. 106: sub Print_Error { 107: print "Content-type: text/html\n\n"; 108: print $message ; 109: exit 0; 110: } # End of Print_Error subroutine. 111: sub Get_Web_Data{ 112: # These intermediate variables make your script more readable 113: # but somewhat less efficient since they aren't necessary. 114: $first_name = $q->param('first_name'); 115: $last_name = $q->param('last_name'); 116: $e_mail = $q->param('e_mail'); 117: $comments = $q->param('comments'); 118: $submit = $q->param('submit'); 119: $survey = $q->param('survey'); 120: $form = $q->param('form_number'); 121: if($form ne "1"){ 122: $birth_month = $q->param('birth_month'); 123: $job_title = $q->param('job_title'); 124: $marital = $q->param('marital'); 125: $sex = $q->param('sex'); 126: $language = $q->param('language'); 127: $s_message=" and the survey"; 128: } 129: return; 130: } # End of Get_Web_Data subroutine. 131: sub Print_Thanks{ 132: print start_html(-bgcolor=>FFFFFF,-title=>'Thank You!'), 133: font({-size=>5, -face=>'arial'}, 134: p({-align=>center},"Thank You, $first_name!")), 135: hr({-width=>'75%'}), 136: font({-size=>3, -face=>'arial'}, 137: p({-align=>center},'Your comments are very valuable to us!'), 138: p({-align=>center},a({-href=>'$home'},"Home"))), 139: end_html; 140: return; 141: } # End of Print_Thanks subroutine. Line-by-line ExplanationLine 1: Tells the program where to find Perl on the Web server. This line will vary depending on where Perl is installed on your server so you need to make any necessary changes. On a UNIX server, this line is required. If you are running this program on an NT server, this line is not required but won't hurt anything if included.Line 2: Tells the program where to find the mail program on the server. Again, make the appropriate adjustments on your server, if needed. Line 3: Loads the CGI.pm module into the program. The arguments in the qw/:standard :html3 :netscape/ bring in more functions for us to use in our program. These functions are all part of the CGI.pm module. Line 4: Creates a new instance of the CGI object and calls it $q. Lines 5-6: Simply create an array of the months of the year. Line 7: A variable we use to determine the site that we link to in the Thank You page. Line 11: Calls the Get_Web_Data subroutine. We use this subroutine to get the information from the calling Web page. Line 12: Calls the Validate_Data subroutine. This subroutine checks to make sure the data that was submitted and is valid data. Line 13: Begins an if..else block. This line checks to see if the value stored in $survey is equal to "1". Since I am dealing with text strings, I used the "eq" operator. Yes, the number 1 is a number, but I am treating it like a string in this case. If I had been treating it like a number, the statement would have read if($survey == 1). Lines 14-15: Call the Print_Survey subroutine, then exits. If we got to this statement, this MUST mean that $survey was equal to 1. So, we print out the survey and then exit. We exit here because we have printed out the survey page at this point and don't need to do any other steps in the program until the user fills out the survey and submits it. Line 16: Else statement, executes if the above if statement was false, ie if $survey was NOT equal to 1. Lines 17-20: Call the Send_Mail subroutine, then call the Print_Thanks subroutine and finally, exit the program. Line 21: Comment noting the end of the program. Hey, wait a minute! What do you mean the program is done? There are still 120+ lines left! Actually, this is the end of the program. I just did all of the important stuff in the subroutines below. I find putting what you can into subroutines makes programming easier. If you have problems and can isolate them to a particular subroutine, then you can concentrate on fixing just that subroutine. Line 23: Sets the email address we want the form(s) contents to be mailed to. Line 24: Opens a handle, but instead of writing to a file, pipes the output to our mail program. Lines 25-33: Simply print the data to the MAIL handle. Line 34: Checks to see if the $birth_month variable is blank. This is a simple way for us to check which form is being submitted. If we had been on the survey form (generated when we click submit and with survey checked), then there must be a value in $birth_month. Why? Because it is a "drop down" box and there is no empty choice. But, if we are on the first form, $birth_month would be blank because there is no variable called $birth_month on that page. Lines 35-41: These lines print out more information that will be mailed. Line 40 closes our if block, so line 41 will get printed out whether the user completed the survey or not. Lines 44-45: These lines return us back to line 18, since we were called on line 17. Then, we close the Send_Mail subroutine. Line 46: Starts the Print_Survey subroutine. This subroutine is called from line 14 only if the "survey" box was checked when the user clicked the Submit button. Many of the lines in this subroutine use CGI.pm's notation to reference internal subroutines. These subroutines were designed to make HTML output from a CGI program easier. I will briefly cover what the items are doing but it is beyond the scope of this article to go into detail about exactly how and why they work. I will be covering other CGI.pm topics in the future that will better explain how these items work. For now, sit back and enjoy the code. Some of it is pretty simple and doesn't require much explaining, other parts are harder to understand but will be covered in the future. Lines 47-51: These lines define some variables that I will be using later on. All they do is store some common HTML tags that I need to use in a nice, small variable. Doing little things like this for repetitive code can really save you some keystrokes. Line 52: Starts a "here" block that prints to STDOUT until it encounters the closing tag. Lines 53-54: Print out some standard HTML header information. Line 55: Closes out "here" block. Line 56: This line is our first real use of the CGI.pm module. This tells it to print a Paragraph tag and make its alignment centered. Line 57: Creates our title in font face "Arial" with a size of 5. Line 58: Creates a <HR WIDTH=75%> tag. Line 60: Some more HTML tags for our document. Lines 61-64: Create some hidden tags that we use to "remember" what the user submitted in the first form. This is the easiest way for a CGI script to remember information from one Web form to the next. Line 66: Another line of HTML for our document. Lines 68-81: This section uses CGI.pm to create most of the HTML code for survey form. Take a look here for what it would look like if I had just used HTML. I accomplished in 14 lines with CGI.pm what took over 85 lines with standard HTML. However, the straight HTML code is much cleaner looking if you look at the source. Lines 83-86: Finish off our HTML code for the survey form. Lines 87-88: Return us to line 15, and then close the subroutine's block. Line 89: Starts our Validate_Date subroutine. We call this subroutine to make sure all the required information is filled out. If there is something missing, we can generate an error message. In this example, we only checked the E-mail and the Comment fields, but you could just as easily check ALL of the fields. Line 90: An if statement that checks to see if the email address appears to be valid. I say appears to be because we are not validating the actual address, just checking that it includes some text, an @, followed by more text. The !~ means Not Equal To. The forward slashes, //, are the match operator. The stuff in between the forward slashes is the Regular Expression to match the text. The .+\@.+\..+ all have meaning: The . is a metacharacter, that is, it doesn't match itself. . matches any character. The + is a quantifier matching the previous character, . one or more times. The \@ is the literal at sign, escaped using the backslash (\). And the \. is the literal period, again escaped using the backslash so as not to confuse it with the metacharacter . which we now know matches any character. In pseudo English this would say: Match one or more characters until you come to an @ sign. Then match one or more characters until you come to a period. Then match one or more characters again. Regular expressions are probably the hardest thing to master in Perl, but also one of its most powerful features. I have a LONG way to go. There are complete books written on regular expressions so don't expect to understand them right away. Line 91: Sets our $errmsg variable to the text shown. Line 92: Concatenates $errmsg on to the end of whatever is currently stored in $message. Line 93: Sets our $found_err variable to 1. We later check this variable and if it is equal to 1, we print an error message. Line 94: Closes the if block. Line 95: Checks to see if our $comments variable is blank. If it is we execute the code in the block, otherwise we skip the block. Lines 96-99: Perform identical functions as lines 91-94 for the comments field. Line 101-103: Check to see if $found_err was set to 1, (not 0), and if so, call the &Print_Error subroutine. Lines 104-105: Return us to line 12 and close the subroutine's block. Line 106: Starts the Print_Error subroutine. Line 107: Prints out the standard HTTP header so the server knows what kind of data it is sending. Line 108: Prints the error message to the screen. The error message is whatever we ended up storing in the $message variable. Lines 109-100: Exit the program with a code of 0, an error condition, and then close the subroutine's block. Line 111: Starts the Get_Web_Data subroutine. This subroutine reads the data from the calling Web form. Lines 112-113: These lines are simply comments letting you know that you don't have to do it this way, but that it makes your code more readable. Lines 114-120: Read the information from the Web form and store it in variables. Lines 121-126: Check to see if the variable $form is equal to 1. If so, we read in some more data from the Web form. Line 127: Creates a message that adds a little extra text to the email we send. We use this on line 29. Line 128: Closes the if block. Lines 129-130: Return us to line 11, and then closes the subroutine's block. Lines 131-141: This whole subroutine simply
prints out an HTML thank you page. We use this to thank the user and give
them some feedback so they know the form worked.
Wrapping It UpWell, we have covered WAY too much for this week. I know my head is spinning, how about yours?Forms don't have to be static "do-one-thing" pages. As I have shown, you can use forms to generate forms, or to email documents. Or both. Prev: Handling
Forms in Perl
|
|
|
Web Techniques and Web Design and Development copyright © 1995-99 Miller Freeman, Inc. ALL RIGHTS RESERVED |