#! /usr/bin/perl -s # quarkshop.pl -- CGI script to accept an order from a caller, mail details # to the site manager and an acknowledgement to the caller. # # $Id: quarkshop.perl,v 1.4 2007/12/05 23:03:27 webman Exp webman $ # # $Log: quarkshop.perl,v $ # Revision 1.4 2007/12/05 23:03:27 webman # Added mail handling. # # Revision 1.3 2007/12/05 21:13:22 webman # Rearranged the code a bit and parametrised things properly. # # Revision 1.2 2007/12/05 20:52:42 webman # Added some serious boilerplate for error handling. # Logs the order to a local file. # # Revision 1.1 2007/12/05 17:52:39 webman # Initial revision # # #------------------------------------------------------------------------------- use strict; # use CGI qw(:standard); # use Mail::Mailer; # use DBI; ... when we have a database to talk to... #------------------------------------------------------------------------------- # global variables. # headerGenerated - Set when the first header is returned to the browser. # Used in error handling to prevent us generating another # header before the error message. # inError - Set in the error handler before generating the response # to the browser. If it is already set when we get into the # error handler, don't bother trying to tell the browser # (since we've already tried once and failed). my ($headerGenerated, $inError) = (), "", 0, 0; #------------------------------------------------------------------------------- # HTML-related stuff... #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # sendHtmlHeader -- send the bit up to / including the body tag. # Limits : It's a bit inflexible. # Errors : None. # Params : title - the title string to go into the header. # Result : None. sub sendHtmlHeader () { if (@_ != 1) { &moan ("sendHtmlHeader has " . @_ . " parameters"); } my ($title) = @_; print ( "Content-Type: text/html\n" . "\n" . " " . $title . " \n" . "\n"); $headerGenerated = 1; } #------------------------------------------------------------------------------- # sendHtmlTrailer -- send the bit including and after the end body tag. # Limits : It's a bit inflexible. # Errors : None. # Params : None. # Result : None. sub sendHtmlTrailer () { print (" \n"); } #------------------------------------------------------------------------------- # sendHtmlBody -- send a string as it is. # Limits : It's a bit inflexible. # Errors : None. # Params : The string that forms this bit of the body. # Result : None. sub sendHtmlBody () { if (@_ != 1) { &moan ("sendHtmlBody has " . @_ . " parameters"); } my ($body) = @_; print ($body); } #------------------------------------------------------------------------------- # makeHtmlPara -- put paragraph tags either end of a string. # Limits : None. # Errors : None. # Params : The string forming the paragraph. # Result : An HTML string that represents the paragraph. sub makeHtmlPara () { if (@_ != 1) { &moan ("makeHtmlPara has " . @_ . " parameters"); } my ($string) = @_; return "

" . $string . "

\n"; } #------------------------------------------------------------------------------- # makeHtmlHeading -- put header tags round a string. # Limits : None. # Errors : None. # Params : The numeric level of the heading. # : The string forming the paragraph. # Result : An HTML string that represents the heading. sub makeHtmlHeading () { if (@_ != 2) { &moan ("makeHHtmlHeading has " . @_ . " parameters"); } my ($level, $text) = @_; return " " . $text . "\n"; } #------------------------------------------------------------------------------- # makeHtmlCentred -- put 'center' tags round a string. # Limits : None. # Errors : None. # Params : The string to be centred. # Result : An HTML string that represents the centred text. sub makeHtmlCentred () { if (@_ != 1) { &moan ("makeHtmlCentred has " . @_ . " parameters"); } my ($text) = @_; return "
" . $text . "
\n"; } #------------------------------------------------------------------------------- # makeHtmLink -- put an anchor tag around some text and a URL. # Limits : None. # Errors : None. # Params : The URL the link is to lead to. # : The text to appear as the link. # Result : An HTML string that represents the link. sub makeHtmlLink () { if (@_ != 2) { &moan ("makeHtmlLink has " . @_ . " parameters"); } my ($url, $text) = @_; return " " . $text . " \n"; } #------------------------------------------------------------------------------- # unmangleCgiString -- unmangle the form data parameter strings. # Limits : None. # Errors : None. # Params : The string to translate. # Result : The translated string. # Effect : Replace '+' symbols by spaces, then translate %xx hex to characters. sub unmangleCgiString () { if (@_ != 1) { &moan ("unmangleCgiString has " . @_ . " parameters"); } my ($str) = @_; $str =~ tr/+/ /; $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack ("C", hex ($1))/eg; return $str; } #------------------------------------------------------------------------------- # readPostInput -- read the parameters supplied by the web page. # Limits : The form must have generated a 'post' event. # Errors : None. If this is not a "post" event, returns an empty dictionary. # Params : None. # Result : A dictionary (hash) containing the name/value pairs from the form. sub readPostInput () { my ($buffer, $pair, @pairs, $name, $value); my (%myPostInputs) = (); if ($ENV {'REQUEST_METHOD'} eq 'POST') { read (STDIN, $buffer, $ENV {'CONTENT_LENGTH'}); @pairs = split (/&/, $buffer); foreach $pair (@pairs) { ($name, $value) = split (/=/, $pair); $name = &unmangleCgiString ($name); $value = &unmangleCgiString ($value); $myPostInputs {$name} = $value; } } return %myPostInputs; } #------------------------------------------------------------------------------- # Error handling. #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # moan -- report an error. # Limits : This is a bit ad-hoc if it is called after we have started # generating the output page. # We really need a stack of nested elements already generated... # Errors : None. This is the error reporter! # Params : An error message. # Result : Never returns - kills the process. # Effect : Log an error file in the private directory, make up # a page to return to the browser, quit. sub moan () { my ($message) = @_; # Remember this has happened... # If we can't open the file there's not a lot we can do. if (open (FILEHANDLE, ">>../private/html_error_log.txt")) { print ( FILEHANDLE "Perl processing error " . $inError . " : " . $message . " at " . gmtime () . "\n"); close (FILEHANDLE); } # If $inError is already set, we're already trying to generate an error # response to the user and we've failed again - give up... if ($inError) { exit (1); } # Make sure we'll stop if the things we're doing next fail. # Otherwise we're into infinite recursion... ++$inError; # If we haven't already generated a header, put out something to keep # the browser happy. if (!$headerGenerated) { &sendHtmlHeader ("ERROR!"); } # Add an error message to the page being generated. &sendHtmlBody ( &makeHtmlPara ( "There has been an error which may prevent me completing this " . "operation
" . "The error has been recorded in the server log.
" . "Please try again later.
" . "If you contact the site administrators, it would be helpful to " . "quote the message '" . $message . "'.")); # We just have to assume that there are no HTML structures open. &sendHtmlTrailer (); # That's all, folks... exit (1); } #------------------------------------------------------------------------------- # Mail handling. #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # sendMail -- send a mail message to a specified recipient. # Limits : Assumes we're running 'sendmail' (i.e. this is a Unix system). # Errors : Silently does nothing if sendmail not running. # Params : Address to send the mail to. # : Address to use as sender. # : Subject of the mail. # : Text to be sent as the body of the mail. # Result : true if all is OK, false (0) if anything went wrong. sub sendMail () { if (@_ != 4) { &moan ("sendMail has " . @_ . " parameters"); } my ($toAddress, $fromAddress, $subject, $text) = @_; open (MAILHANDLE, "|/usr/sbin/sendmail -t") || return 0; # Ok, we have an output stream to the mail server. print (MAILHANDLE "To: " . $toAddress . "\n" . "From: " . $fromAddress . "\n" . "Subject: " . $subject . "\n\n" . $text . "\n\n"); close (MAILHANDLE); return 1; } #------------------------------------------------------------------------------- # Useful general subroutines. #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # getNextCounterValue -- read last value, add one, rewrite, return value # Limits : None. If there is no counter, starts at value of default parameter. # Errors : None. # Params : The name of the counter. # : The default start value. # Result : The next counter value in sequence. # Notes : This version keeps a file in the private data of the web site. # A later version should use a database and record the counter there. sub getNextCounterValue () { if (@_ != 2) { &moan ("getNextCounterValue has " . @_ . " parameters"); } my ($myApplicationName, $counterValue) = @_; my ($fileName) = "../private/" . $myApplicationName . "_counter.txt"; if (open (FILEHANDLE, "<" . $fileName)) { $counterValue = ; close (FILEHANDLE); } if (! $counterValue) { $counterValue = 0; } ++$counterValue; open (FILEHANDLE, ">" . $fileName) || &moan ("Cannot rewrite order number file"); print FILEHANDLE $counterValue; close (FILEHANDLE); return $counterValue; } #------------------------------------------------------------------------------- # The logic that applies specifically to quarkshop. #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # recordOrder -- take a permanent record of the current order. # Limits : None. # Errors : moans if it can't write an order file. # Params : The order number. # : A dictionary (hash) containing the order details. # Result : None. # Effect : Creates an order file in the private directory. sub recordOrder () { my ($orderNumber, $orderDictionary) = @_; my ($key); open (FILEHANDLE, ">../private/quarkshop_order_" . $orderNumber . ".txt") || &moan ("Cannot open order file for writing"); foreach $key (sort keys %$orderDictionary) { print (FILEHANDLE "'$key' is set to '" . $$orderDictionary {$key} . "'\n"); } close (FILEHANDLE); } #------------------------------------------------------------------------------- # processOrder -- generate a text summary of the order to be used in replies. # Limits : None. # Errors : None. # Params : Order number. # : Date and time order placed. # : Dictionary (hash) containing the details of the order. # Result : A string describing the order.. sub processOrder () { if (@_ != 3) { &moan ("processOrder has " . @_ . " parameters"); } my ($orderNumber, $orderTime, $postInputs) = @_; my ($order) = ""; my ($also) = ""; my ($plural); my ($pqParam) = $$postInputs {'pquantity'}; my ($wqParam) = $$postInputs {'wquantity'}; my ($gqParam) = $$postInputs {'gquantity'}; my ($packParam) = $$postInputs {'pack'}; my ($colourParam) = $$postInputs {'colour'}; my ($gcolourParam) = $$postInputs {'gcolour'}; my ($flavourParam) = $$postInputs {'flavour'}; my ($wchargeParam) = $$postInputs {'wcharge'}; my ($weightParam) = $$postInputs {'weight'}; if ($pqParam > 0) { $plural = ($pqParam == 1) ? "" : "s"; $order = "You have ordered $pqParam $weightParam " . $packParam . $plural; if ($packParam eq "meson") { $order .= " in a $colourParam/anti-$colourParam quark pair."; } else { $order .= " with a $colourParam $flavourParam singleton quark."; } $order .= "
"; $also = " also "; } if ($wqParam > 0) { $plural = ($wqParam == 1) ? "" : "s"; $order .= "You $also ordered $wqParam charge $wchargeParam "; $order .= "anti-W-intermediate boson$plural.
"; $also = " also "; } if ($gqParam > 0) { $plural = ($gqParam == 1) ? "" : "s"; $order .= "You $also ordered $gqParam $gcolourParam gluon$plural.
"; $also = " also "; } $order .= "Your order was received at " . $orderTime; return $order; } #------------------------------------------------------------------------------- # sendReplyPage -- generate an HTML reply to be displayed in the callers browser. # Limits : None. # Errors : Reported on the page. # Params : Order number that has been allocated to this order. # : Name of the client. # : E-mail address of the client. # : Text string describing the order. # Result : None. sub sendReplyPage () { if (@_ != 4) { &moan ("sendReplyPage has " . @_ . " parameters"); } my ($myOrderNumber, $userName, $userEMail, $order) = @_; my ($body) = ""; $body = &makeHtmlCentred (&makeHtmlHeading (1, "Quark Shop!")); $body .= &makeHtmlCentred ( &makeHtmlHeading ( 3, "Thank you for your order, " . $userName)); $body .= &makeHtmlPara ( "Your order number is " . $myOrderNumber . ".
". "We have sent an e-mail to " . $userEMail . " confirming your order.
" . "Remember, we haven't accepted your order until we contact " . "you directly."); $body .= &makeHtmlPara ($order); $body .= &makeHtmlLink ("http://www.ecsel.co.uk/quarkshop.html", "Back to the shop"); &sendHtmlHeader ("Quark shop!"); &sendHtmlBody ($body); &sendHtmlTrailer (); } #------------------------------------------------------------------------------- # makeMailMessage -- create a mail message to send to the client and us. # Limits : None. # Errors : None. # Params : Order number that has been allocated to this order. # : Name of the client. # : E-mail address of the client. # : Text string describing the order. # : Received time. # Result : A string that can be used as the body of the message. sub makeMailMessage () { if (@_ != 5) { &moan ("sendReplyPage has " . @_ . " parameters"); } my ($myOrderNumber, $userName, $userEMail, $order, $orderTime) = @_; # Cheat - use the order string from the web page, just change
# to newline... $order =~ s//\n/g; return "This e-mail acknowledges receipt of order " . $myOrderNumber . " from " . $userName . " at e-mail '" . $userEMail . ".\n\n" . $order . "\n" . "Order received at " . $orderTime . "\n" . "Remember, we have not yet accepted your order.\n" . "A representative will contact you in due course.\n"; } #------------------------------------------------------------------------------- # Main program -- read and translate the input, then generate reply page. # Main program local variables. # %formInputs - Dictionary holding the supplied parameters from the form. # $orderString - String holding humanly-readable description of the order. # $orderNumber - Reference number given to client for this order. # $orderTime - Date and time order was received. # $name - The name field form the form. # $email - The e-mail field from the form (actually 'email1' but they # are the same). # $mailMessage - The string to be mailed to client and to us. my (%formInputs, $orderString, $orderNumber, $orderTime, $name, $email, $mailMessage); # Run the main program in an 'eval' block. If anything fails, the eval # will stop, leaving a sensible message for 'moan'. eval { $orderTime = gmtime (); $orderNumber = &getNextCounterValue ("quarkshop_order_number", "1234"); %formInputs = &readPostInput (); $name = $formInputs {'name'}; $email = $formInputs {'email1'}; &recordOrder ($orderNumber, \%formInputs); $orderString = &processOrder ($orderNumber, $orderTime, \%formInputs); $mailMessage = &makeMailMessage ($orderNumber, $name, $email, $orderString, $orderTime); # Send the message to him... &sendMail ( $email, "webmaster\@ecsel.co.uk", "Thank you for ordering from Quark Shop!", $mailMessage); # ...and send it to us. &sendMail ( "webmaster\@ecsel.co.uk", $email, "Another idiot put in an order!", $mailMessage); # Finally, send out a response page. &sendReplyPage ($orderNumber, $name, $email, $orderString); }; if ($@) { &moan ($@); } #------------------------------------------------------------------------------- # End of file quarkshop.pl #-------------------------------------------------------------------------------