[system] / trunk / webwork / system / cgi / cgi-scripts / welcomeAction.pl Repository:
ViewVC logotype

View of /trunk/webwork/system/cgi/cgi-scripts/welcomeAction.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 115 - (download) (as text) (annotate)
Fri Aug 10 19:03:37 2001 UTC (11 years, 9 months ago) by sh002i
File size: 36845 byte(s)
Rewrote downloadAllSets to mak sense, fixed error reporting, fixed several
"used only once" warnings, enhanced filename handling for downloaded files
(this is very nice -- it can tell whether we're getting one set for
multiple students or multiple sets for one student :), changed temp file
naming scheme. what fun!

    1 #!/usr/local/bin/webwork-perl
    2 
    3 
    4 ################################################################
    5 #  Copyright @1995-1999 by Michael E. Gage, Arnold K. Pizer and
    6 #  WeBWorK at the University of Rochester. All rights reserved.
    7 ################################################################
    8 
    9 my $debugON=0;      ## set this to 1 to save debugging information for errors in hardcopy output
   10 
   11 use lib '.'; use webworkInit; # WeBWorKInitLine
   12 require 5.001;
   13 
   14 $/ ="\n";
   15 
   16 use strict;
   17 use Global;
   18 use Auth;
   19 use CGI qw(:standard);
   20 use Net::SMTP;
   21 use Safe;
   22 
   23 
   24 use PGtranslator;
   25 #use sigtrap;
   26 BEGIN {
   27   # set to 1 to enable timing_log
   28   # (contains information about time taken by scripts to run)
   29   $main::logTimingData = 0;
   30 
   31   # begin Timing code
   32   if( $main::logTimingData == 1 ) {
   33     use Benchmark;
   34     $main::beginTime = new Benchmark;
   35   }
   36   # end Timing code
   37 
   38 # ## Setting these time out comstants to zeros removes the time constraint completely. (zero = infinity :=)  )
   39    $main::TIME_OUT_CONSTANT = 60;                   # one minute wait for on screen problems
   40 #  $main::DOWNLOAD_TIME_OUT_CONSTANT = 300;         # give it five minutes
   41    $main::CLASS_DOWNLOAD_TIME_OUT_CONSTANT = 1200;  #twenty minutes
   42 #  $main::DOWNLOAD_NICE = 2;
   43    $main::CLASS_DOWNLOAD_NICE = 5;   # higher numbers indicated lower priorities
   44 
   45 # ## ATTENTION:  The handlers PG_floating_point_exception_handler and PG_warnings_handler
   46 # ## have to be installed after CGI::Carp is called since it also
   47 # ## modifes the die and warn labels. Finding the right warning mechanism using these two
   48 # ## methods bears further investigation
   49 # ## They are defined in Global.pm
   50    $SIG{'FPE'}  = \&Global::PG_floating_point_exception_handler;
   51    $SIG{__WARN__}=\&Global::PG_warnings_handler;
   52    $SIG{'TERM'} = sub {die '[',scalar(localtime),"] Caught a SIGTERM, Error: $!   stopped at $0\n"; };
   53    $SIG{'PIPE'} = sub {$main::SIGPIPE = 1, die '[',scalar(localtime),"] Caught a SIGPIPE, Error: $!   stopped at $0\n"; };
   54    $SIG{ALRM} = sub { $main::SIG_TIME_OUT = 1; exit(0) };
   55 
   56    alarm($main::TIME_OUT_CONSTANT);
   57 #    By explicitly catching the signals and dieing one forces the execution of the END statements which clean up the files.
   58 #
   59 };
   60 
   61 use vars qw ($modules_to_evaluate $extra_packages_to_be_loaded
   62   );
   63 
   64 &CGI::ReadParse;
   65 my %inputs=%main::in;
   66 
   67 my $query = $main::in{CGI};
   68 
   69 # verify that the rest of the information has been received
   70 my $Course = $inputs{'course'};
   71 my $User = $inputs{'user'};
   72 
   73 #my $psvn = $inputs{'probSetKey'};
   74 
   75 my @local_psvns = $query -> param('local_psvns');
   76 my $psvn = $local_psvns[0]; ## get the first one for doing problem sets
   77 $inputs{'probSetKey'} = $psvn;  ## only used by htmlBOTTOM
   78 my $Session_key = $inputs{'key'};
   79 
   80 
   81 &Global::getCourseEnvironment($Course);
   82 
   83 my $scriptDirectory       = getWebworkScriptDirectory();    #$Global::scriptDirectory;
   84 my $databaseDirectory     = getCourseDatabaseDirectory();   #$Global::databaseDirectory;
   85 my $courseScriptsDirectory  = getCourseScriptsDirectory();    #$Global::courseScriptsDirectory;
   86 my $templateDirectory     = getCourseTemplateDirectory();         #$Global::templateDirectory;
   87 
   88 # this is globally defined for the file, since it is needed for cleanup in END
   89 my $tempDirectory            = getCourseTempDirectory();
   90 
   91 eval{require "${courseScriptsDirectory}$Global::displayMacros_pl";} ;
   92 eval{require "${scriptDirectory}$Global::DBglue_pl";};
   93 eval{require "${scriptDirectory}$Global::classlist_DBglue_pl";};
   94 eval{require "${scriptDirectory}$Global::HTMLglue_pl";};
   95 eval{require "${scriptDirectory}$Global::FILE_pl";} ;
   96 
   97 
   98 
   99 ####################################################################
  100 # load the modules to be used in PGtranslator
  101 
  102 require "${courseScriptsDirectory}PG_module_list.pl" or
  103     wwerror($0, "Can't read ${courseScriptsDirectory}PG_module_list.pl");
  104 ####################################################################
  105 
  106 my $keyFile = &Global::getCourseKeyFile($Course);
  107 my $permissionsFile = &getCoursePermissionsFile($Course);
  108 
  109 ## check to see if prob set has been selected ##
  110 verifyInput();
  111 
  112 ##############################################
  113 sub verifyInput {
  114 
  115     if(!defined($psvn)  || $psvn eq "") {
  116   &selectionError; # The calling script did not specify a problem set.
  117         #die "Content-type: text/html\n\nThe calling script did not specify a problem set.";
  118         exit();
  119     }
  120 }
  121 
  122 # log access
  123   &Global::log_info('', query_string);
  124 
  125 
  126 &verify_key($inputs{'user'}, $Session_key, $keyFile, $Course);
  127 
  128 my $permissions = &get_permissions($User,$permissionsFile);
  129 
  130 &attachProbSetRecord($psvn);
  131 my $login_name_for_psvn = &getStudentLogin($psvn);
  132 attachCLRecord($login_name_for_psvn);
  133 
  134 my $setNumber=&getSetNumber($psvn);
  135 $setNumber = $inputs{'setNo'} if defined $inputs{'setNo'};  ## script called from profChangeDates.pl
  136 
  137 # keep strict happy
  138 my $tempTexFileBaseName;
  139 
  140 ###### check to see that it is after the open date
  141 my ($currentTime,$odts,$ddts,$remainingTime, $TimeString);
  142 $currentTime = time;
  143 $odts=&getOpenDate($psvn);
  144 $ddts=&getDueDate($psvn);
  145 $remainingTime=$ddts-$currentTime;
  146 
  147 if($currentTime<$odts && $permissions !=$Global::instructor_permissions) {
  148   print &htmlTOP("Before open date error");
  149   print "<CENTER><h2>Sorry, cannot download or do problem set $setNumber yet.
  150   <BR>It is before the open date.</h2></CENTER>";
  151   print &htmlBOTTOM("downloadPS.pl",\%inputs);
  152     exit(0);
  153 }
  154 
  155 my %PSVNHashForSet = %{getPSVNHashForSet($setNumber)};
  156 my $action = $inputs{'action'};
  157 my $downloadType= $inputs{'downloadType'}; # either pdf, ps, tex, or dvi
  158 
  159 # Verify that the problem set has been created if a psvn number has been passed
  160 unless ($action eq 'Get_all_copies') {
  161     unless (defined $PSVNHashForSet{$psvn} ) {
  162       print   &htmlTOP("Problem set version number $psvn not created");
  163         print ( "Pin number $psvn was not created for set $setNumber");
  164         print   &htmlBOTTOM("downloadPS.pl", \%inputs);
  165         exit(0);
  166     }
  167 }
  168 
  169 my $texFile = "${login_name_for_psvn}.tempTex-CGIscript";
  170 my $save_errors='';
  171 
  172 if ($action eq 'Do problem set' or $action eq 'Do_problem_set') {displayProbSet();}
  173 #elsif ($action eq 'Get hard copy' or $action eq 'Get_hard_copy') {downloadIndividualSet();}
  174 elsif ($action eq 'Get hard copy' or $action eq 'Get_hard_copy') {downloadAllSets();}
  175 elsif ($action eq 'Get_all_copies') {downloadAllSets();}
  176 else {wwerror($0, "Unknown action: $action");}
  177 
  178 
  179 # begin Timing code
  180 # my $endTime = new Benchmark;
  181 # &Global::logTimingInfo($main::beginTime,$endTime,$0,$Course,$User);
  182 # end Timing code
  183 
  184  exit;
  185 #############################################
  186 
  187 sub displayProbSet {
  188     my $studentName=&CL_getStudentName($login_name_for_psvn);
  189     my $probHeaderFileName = &getProbHeaderFileName($psvn);
  190 
  191     my @problems=sort numerical &getAllProblemsForProbSetRecord($psvn);
  192     sub numerical { $a <=> $b};
  193 
  194     my $numberOfProblems=0;
  195     my $prob;
  196     foreach $prob (@problems) {$numberOfProblems++;}
  197 
  198 
  199     print &probSet_htmlTOP("Problem Set $setNumber from  $inputs{'course'} for $studentName");
  200             #see subroutines at the bottom of this file
  201                     #this allows the use of a small gif for the webwork logo
  202                     #and takes up less screen real estate.
  203 
  204     print &probSet_titleBar("Problem Set $setNumber from  $inputs{'course'} for $studentName");
  205 
  206     print <<"ENDOFHTML";
  207 <TABLE BORDER=1>
  208   <TR>
  209     <!-- Row 1 Column 1 -->
  210     <TD>
  211 
  212 Select one of the $numberOfProblems problems to try:
  213 <FORM METHOD=POST ACTION="$Global::processProblem_CGI">
  214 <INPUT TYPE=HIDDEN NAME=probSetKey VALUE=$psvn>
  215 <P>
  216 <SELECT NAME=probNum SIZE=11>
  217 ENDOFHTML
  218 
  219     my ($problem,$problemAttempted, $problemStatus,$longProblemStatus);
  220     foreach $problem(@problems) {
  221       $problemStatus = getProblemStatus($problem,$psvn);
  222       $problemAttempted = getProblemAttempted($problem,$psvn);
  223 
  224       if (!$problemAttempted) {
  225         $longProblemStatus  = '';  # default value
  226       }   elsif   ($problemStatus  >= 0 and $problemStatus <=1 ) {
  227           my $percentCorr = int(100*$problemStatus+.5);
  228         $longProblemStatus    = "${percentCorr}\% correct"
  229       } else  {
  230         $longProblemStatus  = 'unknown status';  # default value
  231       }
  232       print "<OPTION VALUE=$problem>Problem $problem -- $longProblemStatus </OPTION>\n";
  233   }
  234 
  235   ## nice note to warn if there's less than one day left to complete problem set
  236     if ($remainingTime<86400 && $remainingTime>0)  {
  237       $TimeString = "<BR><RM>Note: you have less than one day left
  238              to complete this problem set</EM>";
  239     }
  240     else  {
  241       $TimeString = "";
  242     }
  243 
  244     print <<"ENDOFHTML";
  245 </SELECT>
  246 <BR>
  247 ENDOFHTML
  248 
  249 
  250   my $practiceUser = $Global::practiceUser;
  251   if (($currentTime > $ddts) or ($User =~ /^$practiceUser/)) {
  252     print q!<INPUT type="checkbox" name="show_old_answers" value=1> Show my old answers<BR>!;
  253   }
  254   else {
  255     print q!<INPUT type="checkbox" name="show_old_answers" checked value=1> Show my old answers<BR>!;
  256   }
  257 
  258     print &sessionKeyInputs(\%inputs);
  259     my $mode = $inputs{'Mode'};
  260     $mode = $Global::htmlModeDefault unless ($mode);
  261     &displaySelectModeLine($mode);    ## displays mode select buttons
  262             ## the sub displaySelectModeLine is in
  263             ## "${courseScriptsDirectory}$Global::displayMacros_pl"
  264     print <<"ENDOFHTML";
  265 <BR>
  266 <INPUT TYPE=SUBMIT VALUE="Get Problem">
  267 $TimeString
  268 
  269 </FORM>
  270 
  271 ENDOFHTML
  272 
  273     print "<FORM METHOD=POST ACTION=\"${Global::cgiWebworkURL}welcome.pl\"><P>";
  274     print &sessionKeyInputs(\%inputs);
  275 
  276     print <<"ENDOFHTML";
  277 <INPUT TYPE=HIDDEN NAME="probSetKey" VALUE=$psvn>
  278 <INPUT TYPE=SUBMIT VALUE="Problem Sets">
  279 </FORM>
  280 
  281 
  282     </TD>
  283     <!-- Row 1 Column 2 -->
  284     <TD>
  285 ENDOFHTML
  286 
  287 
  288 ##  process problem and save @printlines
  289     my $probHeader = $Global::PROB_HEADER;  # default value
  290 
  291     if ( (defined($probHeaderFileName)) and  ($probHeaderFileName =~ /\S/)) {
  292       $probHeader = $probHeaderFileName;
  293     }
  294         ## use $probHeader as default unless $probHeaderFileName is defined
  295         ## in the set definition file
  296     my $source;
  297     if (-e "${templateDirectory}$probHeader" ) {
  298         unless (-r "${templateDirectory}$probHeader") {
  299           wwerror($0, "Can't read ${templateDirectory}$probHeader");
  300         }
  301         open(PROB,"<${templateDirectory}$probHeader");
  302         $source = join("",<PROB>);
  303         close(PROB);
  304     }
  305     my %envir=defineProblemEnvir($mode,0,$psvn,$Course);
  306          my $pt = new PGtranslator;  #pt stands for problem translator;
  307          $pt->environment(\%envir);
  308        $pt->initialize();
  309        $pt-> set_mask();
  310        $pt->source_string($source);
  311        $pt -> unrestricted_load("${courseScriptsDirectory}PG.pl");
  312          $pt -> unrestricted_load("${courseScriptsDirectory}dangerousMacros.pl");
  313        $pt ->translate();
  314        my $PG_PROBLEM_TEXT_REF = $pt->ra_text();
  315        my $PG_HEADER_TEXT_REF = $pt->r_header;#\$PG_HEADER_TEXT;
  316        my $PG_ANSWER_HASH_REF = $pt->rh_correct_answers;
  317        my $PG_FLAGS_REF =$pt ->rh_flags;
  318 
  319 
  320 
  321     my @printlines;
  322     if($mode eq "HTML" || $mode eq 'HTML_tth') {
  323       @printlines=@{$pt->ra_text()};
  324     }
  325     elsif ($mode eq 'Latex2HTML') {
  326       @printlines = &createDisplayedInsert($setNumber,
  327                     $probHeader,$psvn,$Course,$pt->ra_text());
  328     }
  329     print @printlines;
  330     print <<"ENDOFHTML";
  331     </TD>
  332   </TR>
  333 </TABLE>
  334 ENDOFHTML
  335 print &htmlBOTTOM('welcomeAction.pl', \%inputs,'probSetHelp.html');
  336 exit;
  337 } ## end of problem selection form and end of sub displayProbSet
  338 
  339 ####################
  340 ## subroutines
  341 ####################
  342 
  343 $/ = "\n";
  344 sub createTexSource {
  345     my $psvn = shift;
  346 
  347     # check that the psvn corresponds to the user and that it is after the open
  348     # date.  This should only fail if someone is trying to break into WeBWorK.
  349 
  350     &attachProbSetRecord($psvn);
  351     $login_name_for_psvn = &getStudentLogin($psvn);
  352   attachCLRecord($login_name_for_psvn);
  353 
  354     if ( ( ( $User ne &getStudentLogin($psvn)) ||($currentTime < $odts) )
  355             and ($permissions != $Global::instructor_permissions)
  356                 and ($permissions != $Global::TA_permissions)
  357         )   {
  358     &hackerError;
  359     exit;
  360   }
  361 
  362     my $probSetHeader = $Global::SET_HEADER;
  363 
  364     my $setHeaderFileName = &getSetHeaderFileName($psvn);
  365 
  366     my $answersRequestedQ = 0;
  367   $answersRequestedQ=  $inputs{'ShowAns'} if defined($inputs{'ShowAns'});
  368 
  369     my $adts=&getAnswerDate($psvn);
  370     my $displayCorrectAnswersQ = 0;  #initialize
  371     $displayCorrectAnswersQ =1 if  $answersRequestedQ && ($currentTime > $adts);
  372   $displayCorrectAnswersQ =1 if $answersRequestedQ && ($permissions == $Global::instructor_permissions);
  373 
  374 
  375 #    chdir "$tempDirectory";
  376 #    umask(022);
  377 
  378     my $texSource ='';
  379 
  380 #   open(OUTPUT, ">${tempDirectory}${texFile}${psvn}.tex")
  381 #|| wwerror("Can't create $tempDirectory${texFile}$psvn.tex\n");
  382 
  383 
  384 
  385     print STDERR "%%Creating a tex version of set $setNumber<BR>\n" if $debugON;
  386     print STDERR "%%For", &CL_getStudentName($login_name_for_psvn), "psvn=$psvn<BR>\n" if $debugON;
  387 
  388 
  389     # input TeX preamble
  390 #   print OUTPUT &texInput($Global::TEX_SET_PREAMBLE);
  391     $texSource = &texInput($Global::TEX_SET_PREAMBLE);
  392 
  393     # print TeX Header
  394 #   print OUTPUT &texInput($Global::TEX_SET_HEADER);
  395     $texSource .= &texInput($Global::TEX_SET_HEADER);
  396 
  397     # Print setheader
  398     my $mode = "TeX";
  399     my @PG_COMPILE_ERRORS = ();
  400   if ( (defined($setHeaderFileName)) and  $setHeaderFileName =~ /\S/) {
  401     $probSetHeader = $setHeaderFileName;
  402   }
  403         ## use $probSetHeader as default unless $setHeaderFileName is defined
  404         ## in the set definition file
  405     if ( open(INPUT,"${templateDirectory}$probSetHeader") ) {
  406 
  407 #                 ##Determine language
  408 
  409         $probSetHeader =~ /\.([^\.]*)$/;
  410         my $displayMode = $1;
  411 
  412         if ($displayMode eq 'pg') {
  413       my %envir=defineProblemEnvir($mode,0, $psvn,$Course,undef());
  414       my $input_string=  join("",<INPUT> );
  415             my ($PG_PROBLEM_TEXT_REF, $PG_HEADER_TEXT_REF, $PG_ANSWER_HASH_REF, $PG_FLAGS_REF);
  416             my $pt = new PGtranslator;  #pt stands for problem translator;
  417       $pt -> evaluate_modules( @{main::modules_to_evaluate}) ;
  418           $pt -> load_extra_packages(@{main::extra_packages_to_be_loaded});
  419 
  420           # The variables in the two preceding lines are  defined in PG_module_list.pl
  421           # require "${courseScriptsDirectory}PG_module_list.pl";
  422           # (Modules are defined by  require statement above found near the top of this file, outside the loop.)
  423             $pt->environment(\%envir);
  424       $pt->initialize();
  425       $pt-> set_mask();
  426       $pt->source_string($input_string);
  427         $pt -> unrestricted_load("${courseScriptsDirectory}PG.pl");
  428         $pt -> unrestricted_load("${courseScriptsDirectory}dangerousMacros.pl");
  429         $pt ->translate();
  430         $PG_PROBLEM_TEXT_REF = $pt->ra_text();
  431       $PG_HEADER_TEXT_REF = $pt->r_header;#\$PG_HEADER_TEXT;
  432       $PG_ANSWER_HASH_REF = $pt->rh_correct_answers;
  433       $PG_FLAGS_REF =$pt ->rh_flags;
  434 #             print OUTPUT @{$PG_PROBLEM_TEXT_REF};
  435             $texSource .= join '', @{$PG_PROBLEM_TEXT_REF};
  436 
  437         } else {
  438 #         print OUTPUT "Don't understand languages with extension $displayMode.<BR>\n";
  439             $texSource .= "Don't understand languages with extension $displayMode.<BR>\n";
  440         }
  441     close INPUT;
  442     } else {
  443     print STDERR ( "Can't open ${templateDirectory}${probSetHeader}\n") if $debugON;
  444       wwerror("$0", "\n######## Could not open the set header file: ${templateDirectory}${probSetHeader}","","");
  445     }
  446 
  447 
  448     # Print problems
  449     my @problems = sort {$a <=> $b } &getAllProblemsForProbSetRecord($psvn);
  450     my @refSubmittedAnswers = ();
  451    # print "content-type: text/plain\n\nproblems @problems";
  452 
  453     my $probNum;
  454     foreach $probNum (@problems) {
  455       my $source;
  456         my $probFileName    =   &getProblemFileName($probNum,$psvn);
  457       if (-e "${templateDirectory}$probFileName" ) {
  458         unless (-r "${templateDirectory}$probFileName") {
  459           wwerror($0, "Can't read ${templateDirectory}$probFileName");
  460         }
  461         open(PROB,"<${templateDirectory}$probFileName");
  462         $source = join("",<PROB>);
  463         close(PROB);
  464       }
  465       local($^W) =0;   ##########CHANGE THIS BACK!!!!
  466       my %envir=defineProblemEnvir('TeX',$probNum,$psvn,$Course,undef());
  467         my ($PG_PROBLEM_TEXT_REF, $PG_HEADER_TEXT_REF, $PG_ANSWER_HASH_REF, $PG_FLAGS_REF,$PG_EVALUATED_ANSWERS_REF);
  468 #
  469          my $pt = new PGtranslator;  #pt stands for problem translator;
  470          $pt->environment(\%envir) ;
  471        $pt->initialize();
  472        $pt-> set_mask();
  473        $pt->source_string($source);
  474        $pt -> unrestricted_load("${courseScriptsDirectory}PG.pl");
  475          $pt -> unrestricted_load("${courseScriptsDirectory}dangerousMacros.pl");
  476        $pt ->translate();
  477 
  478         $PG_PROBLEM_TEXT_REF    = $pt->ra_text();
  479       $PG_HEADER_TEXT_REF     = $pt->r_header;#\$PG_HEADER_TEXT;
  480 
  481 #       $PG_ANSWER_HASH_REF     = $pt->rh_correct_answers;
  482         $PG_EVALUATED_ANSWERS_REF       = $pt->process_answers;
  483       $PG_FLAGS_REF           = $pt ->rh_flags;
  484 
  485 
  486 #       print OUTPUT @{$PG_PROBLEM_TEXT_REF};
  487           $texSource .= join '', @{$PG_PROBLEM_TEXT_REF};
  488 
  489       if (defined($PG_FLAGS_REF->{'error_flag'}) and  $PG_FLAGS_REF->{'error_flag'} ==1) {
  490         push(@PG_COMPILE_ERRORS, qq{<A HREF="#problem$probNum">$probNum</A>} );
  491       }
  492       if ($displayCorrectAnswersQ) {
  493       my %correctAnswerHash = ();
  494 
  495 
  496       my @correctAnswerList = ();
  497 
  498       my %submittedAnswerHash = ();
  499       if ( ref($PG_EVALUATED_ANSWERS_REF) eq 'HASH' ) {
  500         %correctAnswerHash = %$PG_EVALUATED_ANSWERS_REF;
  501       } else {
  502           warn "ERROR: Please pass the PG answer list as a hash not a list.";
  503       }
  504 
  505       # insert answers (if any)
  506 
  507           if ( %correctAnswerHash ) {
  508             $texSource .= "Correct Answers:\\par\\begin{itemize}\n";
  509           my ($correctFlag,$normalizedCorrectAnswer,
  510              $normalizedSubmittedAnswer,
  511              $answerMessage) = ();
  512           # determine the correct order for the answers
  513           my @answer_entry_order = ( defined($pt->{PG_FLAGS_REF}->{ANSWER_ENTRY_ORDER}) ) ?
  514                         @{$pt->{PG_FLAGS_REF}->{ANSWER_ENTRY_ORDER}} :  keys %{$pt->rh_evaluated_answers}  ;
  515 
  516           foreach  my $ky (@answer_entry_order) {
  517 
  518           $normalizedCorrectAnswer = $correctAnswerHash{$ky}->{correct_ans};
  519                   $normalizedCorrectAnswer =~ s/\^/\\\^\{\}/g;
  520                 $normalizedCorrectAnswer =~ s/\_/\\\_/g;
  521           $texSource .= "\\item $normalizedCorrectAnswer\n";
  522 
  523           }
  524 
  525               $texSource .= "\\end{itemize} \\par\n";
  526             }
  527       }
  528 
  529 
  530     }
  531 
  532     #  print Tex postamble
  533 #    print OUTPUT &texInput($Global::TEX_SET_FOOTER);
  534     $texSource .= &texInput($Global::TEX_SET_FOOTER);
  535 
  536     return \$texSource, \@PG_COMPILE_ERRORS;
  537 }
  538 
  539 ################### END main routine ##################################
  540 
  541 sub downloadAllSets {
  542   system("/usr/bin/renice +$main::CLASS_DOWNLOAD_NICE -p $$ 1>/dev/null") && warn "Could not renice process. pid $$";
  543   alarm( $main::CLASS_DOWNLOAD_TIME_OUT_CONSTANT);
  544 
  545   my $downloadMulti = "unknown";
  546   $downloadMulti = "multistudent" if $action eq "Get_all_copies";
  547   $downloadMulti = "multiset" if $action eq "Get_hard_copy";
  548 
  549   my @psvns_to_download = $query->param('local_psvns');
  550   my $num_psvns_to_download = @psvns_to_download;
  551   my $max_psvns_to_download = $Global::max_num_of_ps_downloads_allowed;
  552 
  553   if($num_psvns_to_download > 1 and $permissions != $Global::instructor_permissions) {
  554     wwerror("Non-professors are not permitted to download more than one problem set at a time.");
  555   }
  556 
  557   if(@psvns_to_download > $Global::max_num_of_ps_downloads_allowed) {
  558     my $tooManyWhat = $downloadMulti eq "multiset" ? "problem sets" : "students";
  559     wwerror("Too many $tooManyWhat are selected for download.",
  560       "The maximum number of $tooManyWhat which can be downloaded at one time is $Global::max_num_of_ps_downloads_allowed."
  561       ." You selected $num_psvns_to_download. Go back and select fewer $tooManyWhat."
  562       ." (This maximun is set by the variable \$max_num_of_ps_downloads_allowed in Global.pm.)");
  563   }
  564 
  565   my ($cumulativeTexSource, $currentTexSourceRef, $currentTexSource, $currentTexErrorRef);
  566 
  567   # this could be used when eliminating pre-foreach createTexSourceHandleErrors call
  568   # s/\\end\{document\}.*?\\begin\{document\}/\n\\newpage\n/s;
  569 
  570   $tempTexFileBaseName = "$tempDirectory$User\_$Course";
  571   my $first_psvn = $psvns_to_download[0];
  572 
  573   my $current_psvn = shift @psvns_to_download;
  574   ($currentTexSourceRef, $currentTexErrorRef) = createTexSource($current_psvn);
  575   @psvns_to_download = () if(@$currentTexErrorRef);
  576   $currentTexSource = $$currentTexSourceRef;
  577   $currentTexSource =~ s|\\end\{document\}\s$|\n|s;
  578   $cumulativeTexSource .= $currentTexSource;
  579 
  580   foreach $current_psvn (@psvns_to_download) {
  581     ($currentTexSourceRef, $currentTexErrorRef) = createTexSource($current_psvn);
  582     @psvns_to_download = () if(@$currentTexErrorRef);
  583     $currentTexSource = $$currentTexSourceRef;
  584     $currentTexSource =~ s|^.*?\\begin\{document\}|\n\\newpage\n|s;
  585     $currentTexSource =~ s|\\end\{document\}\s$|\n|s;
  586     $cumulativeTexSource .= $currentTexSource;
  587   }
  588 
  589   $cumulativeTexSource .= "\n\\end{document}\n";
  590 
  591   open TEXOUTPUT, ">$tempTexFileBaseName.tex"
  592     or wwerror("Can't open $tempTexFileBaseName.tex for output.");
  593   print TEXOUTPUT $cumulativeTexSource;
  594   close TEXOUTPUT;
  595 
  596   if(@$currentTexErrorRef) {
  597     PG_error_print($tempTexFileBaseName, $current_psvn, @$currentTexErrorRef);
  598   } else {
  599     $downloadType = lc $downloadType;
  600     my $mimeType = prepareHardcopy($tempTexFileBaseName, $downloadType);
  601 
  602     my ($setName, $userName);
  603     &attachProbSetRecord($first_psvn);
  604     if($downloadMulti eq "multistudent") {
  605       if($num_psvns_to_download == 1) {
  606         $setName = getSetNumber($first_psvn);
  607         $userName = getStudentLogin($first_psvn);
  608       } else {
  609         $setName = getSetNumber($first_psvn);
  610         $userName = "multistudent";
  611       }
  612     } elsif($downloadMulti eq "multiset") {
  613       if($num_psvns_to_download == 1) {
  614         $setName = getSetNumber($first_psvn);
  615         $userName = getStudentLogin($first_psvn);
  616       } else {
  617         $setName = "multiset";
  618         $userName = getStudentLogin($first_psvn);
  619       }
  620     }
  621     my $hardCopyName = "$Course.$userName.$setName.$downloadType";
  622 
  623     open(TEXINPUT, "$tempTexFileBaseName.$downloadType")
  624       or wwerror($0, "Can't open $tempTexFileBaseName.$downloadType: $!\n");
  625     print "Content-type: $mimeType\n";
  626     print "Content-disposition: attachment; filename=\"$hardCopyName\"\n\n";
  627     print while (<TEXINPUT>);
  628     close TEXINPUT;
  629   }
  630 }
  631 
  632 sub prepareHardcopy
  633 {
  634   my ($texFileBaseName, $targetFormat) = @_;
  635   my $mimeType;
  636 
  637   chdir $tempDirectory;
  638 
  639   my $dviCommandLine = "$Global::$Global::externalLatexPath $texFileBaseName.tex >/dev/null 2>/dev/null";
  640   my $psCommandLine = "$Global::externalDvipsPath -o $texFileBaseName.ps $texFileBaseName.dvi >/dev/null 2>/dev/null";
  641   my $pdfCommandLine = "$Global::externalPs2pdfPath $texFileBaseName.ps $texFileBaseName.pdf";
  642 
  643   if($targetFormat eq "pdf") {
  644     system($dviCommandLine) and die "dvi generation failed.";
  645     system($psCommandLine) and die "ps generation failed.";
  646     system($pdfCommandLine) and die "pdf generation failed.";
  647     $mimeType = "application/pdf";
  648   } elsif($targetFormat eq "ps") {
  649     system($dviCommandLine) and die "dvi generation failed.";
  650     system($psCommandLine) and die "ps generation failed.";
  651     $mimeType = "application/postscript";
  652   } elsif($targetFormat eq "dvi") {
  653     system($dviCommandLine) and die "dvi generation failed.";
  654     $mimeType = "application/x-dvi";
  655   } elsif($targetFormat eq "tex") {
  656     $mimeType = "application/tex";
  657   } else {
  658     die "unrecognized format: $targetFormat";
  659   }
  660 
  661   return $mimeType;
  662 }
  663 
  664 sub PG_error_print
  665 {
  666   my ($tempTexFileBaseName, $current_psvn, @errors) = @_;
  667 
  668   # get set name and student name from psvn
  669   &attachProbSetRecord($current_psvn);
  670   my $userName = &getStudentLogin($current_psvn);
  671   my $setName = &getSetNumber($current_psvn);
  672 
  673   # print error page header
  674   print &htmlTOP("PG compile error");
  675   print "<H3>PG error while compiling problem number", (@errors>1) ? 's ' : ' ',
  676     join(', ', @errors), " in problem set $setName for $userName.</H3>";
  677   print "<h3>TeX source file:</h3>";
  678 
  679   # open temp tex file
  680   if(open TEXINPUT, "$tempTexFileBaseName.tex") {
  681     print "<pre>\n";
  682     my $lineNumber = 1;
  683     while(<TEXINPUT>) {
  684       if(/<A NAME/) { print $_; }
  685       else { print protect_HTML("$lineNumber $_"), "\n"; }
  686       $lineNumber++;
  687     }
  688     print "</pre>\n";
  689     close TEXINPUT;
  690   } else {
  691     print "<p>Unable to open TeX source file:<br><tt>$tempTexFileBaseName.tex</tt></p>";
  692   }
  693 
  694   # print page footer
  695   print &htmlBOTTOM("welcomeAction.pl", \%inputs);
  696   exit;
  697 }
  698 
  699 # -----
  700 
  701 sub logPrint {
  702     print &htmlTOP("TeX Error or error in creating PostScript file");
  703     open (LOGFILE, " $tempDirectory$texFile$psvn.log")
  704   || print  "<H3>Can't open log file:</H3> path= $tempDirectory$texFile$psvn.log<BR>$!<BR><BR>"  ;
  705 
  706 
  707     print "<H3>TeX Error Log:</H3>";
  708     my $print_error_switch = ($debugON) ? 1: 0;
  709     my $out='';
  710     #warn ord $/, ord  "\n", ord "\r";
  711     #warn "length of separator = ", length($/);
  712     $/ = "\n";
  713     #warn ord $/, ord  "\n", ord "\r";
  714     while (<LOGFILE>) {
  715       $out = $_;
  716       $print_error_switch = 1 if $out =~ /^!/;  # after a fatal error start printing messages
  717     print protect_HTML($out)."<BR>\n" if $print_error_switch;
  718     }
  719     close(LOGFILE);
  720 
  721     open (TEXFILE, "${tempDirectory}${texFile}${psvn}.tex")
  722   || print "<H3>Can't open tex source file:</H3> path= ${tempDirectory}${texFile}${psvn}.tex:<BR> $!<BR><BR>\n";
  723     print "<BR>\n<H3>TeX Source File:</H3><BR>\n";
  724     print "<PRE>";
  725 
  726     my $lineNumber = 1;
  727     while (<TEXFILE>) {
  728     print protect_HTML("$lineNumber $_")."\n";
  729         $lineNumber++;
  730     }
  731     close(TEXFILE);
  732     print "</PRE>";
  733     print &htmlBOTTOM("downloadPS.pl", \%inputs);
  734 }
  735 sub protect_HTML {
  736   my $line = shift;
  737   chomp($line);
  738   $line =~s/\&/&amp;/g;
  739   $line =~s/</&lt;/g;
  740   $line =~s/>/&gt;/g;
  741   $line;
  742 }
  743 sub selectionError {
  744     print &htmlTOP("Selection error");
  745     print"<H2>Error:</H2>  You must first select a problem set in order to download a hard copy!\n";
  746     print "<FORM METHOD=POST ACTION=\"${Global::cgiWebworkURL}welcome.pl\"><P>";
  747     print &sessionKeyInputs(\%inputs);
  748     print <<"ENDOFHTML";
  749   <INPUT TYPE=SUBMIT VALUE="Return to Welcome Page">
  750 </FORM>
  751 ENDOFHTML
  752     print &htmlBOTTOM("welcomeAction.pl", \%inputs);
  753 }
  754 
  755 sub probSet_htmlTOP {
  756     my ($title, $bg_url) = @_;
  757     my $background_url = $bg_url || $Global::background_plain_url;
  758 
  759 
  760     my $out = <<ENDhtmlTOP;
  761 content-type: text/html
  762 Expires: 0
  763 
  764 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
  765 <HTML>
  766 <HEAD>
  767 <TITLE>$title</TITLE>
  768 </HEAD>
  769 <BODY BACKGROUND="$background_url"><p>
  770 <P>
  771 
  772 ENDhtmlTOP
  773     $out;
  774 }
  775 
  776 sub probSet_titleBar {
  777   my ($title) = @_;
  778   my $title_bar = "";
  779   $title_bar .= qq{
  780     <TABLE BORDER="0" WIDTH="100%">
  781     <TR ALIGN=CENTER  >
  782     <TD ALIGN=LEFT >
  783     <A HREF="$Global::webworkDocsURL">
  784     <IMG SRC="$Global::squareWebworkGif" BORDER=1 ALT="WeBWorK"></A>
  785     </TD>
  786     <TD VALIGN=MIDDLE>
  787     <H2 ALIGN=CENTER>
  788     $title
  789     </H2>
  790     </TD>
  791     <TD ALIGN=RIGHT >
  792     <FORM METHOD=POST ACTION=\"${Global::cgiWebworkURL}welcome.pl\"><P>
  793   };
  794   my $inputkeys = &sessionKeyInputs(\%inputs);
  795 
  796   $title_bar .= qq{
  797     $inputkeys
  798     <INPUT TYPE=HIDDEN NAME=\"probSetKey\" VALUE=$psvn>
  799     <INPUT TYPE=SUBMIT VALUE=\"Problem Sets\">
  800     </FORM>
  801     </TD>
  802       </TABLE>
  803   };
  804   $title_bar;
  805 }
  806 
  807 sub hackerError  { ## prints hacker error message
  808 
  809     my $msg = "Attempt to hack into WeBWorK \n Remote Host is: ". remote_host()."\n";
  810     $msg .= query_string;
  811     &Global::log_error('hacker error', $msg);   ## log attempt
  812 
  813     ## notify by email
  814 
  815    my $toAdd = $Global::feedbackAddress;
  816 
  817     my $emailMsg = "To: $toAdd
  818 Subject: Attempt to hack into WeBWorK
  819 
  820 Here are the details on the attempt to hack into weBWorK:\n
  821 $msg
  822 \n";
  823 
  824   my $smtp = Net::SMTP->new($Global::smtpServer, Timeout=>20);
  825   $smtp->mail($Global::webmaster);
  826   $smtp->recipient($Global::feedbackAddress);
  827   $smtp->data($msg);
  828   $smtp->quit;
  829 
  830 
  831 #    my $SENDMAIL = $Global::SENDMAIL;
  832 #    open (MAIL,"|$SENDMAIL");
  833 #    print MAIL "$emailMsg";
  834 #    close (MAIL);
  835 
  836     print   &htmlTOP("Hacker Error"),
  837                         "<H2>Error:Please do not try to hack into WeBWorK!</H2>",
  838                         startform(-action=>"${Global::cgiWebworkURL}${Global::welcomeAction_CGI}"),
  839                         "<p>",
  840                         &sessionKeyInputs(\%inputs),
  841                         hidden(-name=>'local_psvns', -value=>$psvn),
  842                         hidden(-name=>'action', -value=>'Do_problem_set'),
  843                         submit(-value=>"Return to Problem Set"),
  844                         endform(),
  845                         &htmlBOTTOM($0, \%inputs);
  846 }
  847 
  848 sub defineProblemEnvir {
  849     my ($mode,$probNum,$psvn,$courseName,$refSubmittedAnswers)      =   @_;
  850     my %envir=();
  851     my $loginName = &getStudentLogin($psvn);
  852   ##how to put an array submittedAnswers in a hash??
  853     $envir{'refSubmittedAnswers'}   =   $refSubmittedAnswers if defined($refSubmittedAnswers);
  854     $envir{'psvnNumber'}        =   $psvn;
  855     $envir{'psvn'}            =   $psvn;
  856     $envir{'studentName'}       =   &CL_getStudentName($loginName);
  857   $envir{'studentLogin'}        = $loginName;
  858   $envir{'sectionName'}       = &CL_getClassSection($loginName);
  859   $envir{'sectionNumber'}       = &CL_getClassSection($loginName);
  860   $envir{'recitationName'}      = &CL_getClassRecitation($loginName);
  861   $envir{'recitationNumber'}      = &CL_getClassRecitation($loginName);
  862   $envir{'setNumber'}         = &getSetNumber($psvn);
  863   $envir{'questionNumber'}        = $probNum;
  864   $envir{'probNum'}           = $probNum;
  865   $envir{'openDate'}          = &getOpenDate($psvn);
  866   $envir{'formatedOpenDate'}      = &formatDateAndTime(&getOpenDate($psvn));
  867   $envir{'formattedOpenDate'}     = &formatDateAndTime(&getOpenDate($psvn));
  868   $envir{'dueDate'}           = &getDueDate($psvn);
  869   $envir{'formatedDueDate'}       = &formatDateAndTime(&getDueDate($psvn));
  870   $envir{'formattedDueDate'}      = &formatDateAndTime(&getDueDate($psvn));
  871   $envir{'answerDate'}        = &getAnswerDate($psvn);
  872   $envir{'formatedAnswerDate'}    = &formatDateAndTime(&getAnswerDate($psvn));
  873   $envir{'formattedAnswerDate'}   = &formatDateAndTime(&getAnswerDate($psvn));
  874   $envir{'problemValue'}        = &getProblemValue($probNum,$psvn);
  875   $envir{'fileName'}          = &getProblemFileName($probNum,$psvn);
  876   $envir{'probFileName'}        = &getProblemFileName($probNum,$psvn);
  877   $envir{'languageMode'}        = $mode;
  878   $envir{'displayMode'}       = $mode;
  879   $envir{'outputMode'}        = $mode;
  880   $envir{'courseName'}        = $courseName;
  881   $envir{'sessionKey'}        = ( defined($inputs{'key'}) ) ?$inputs{'key'} : " ";
  882 
  883   # initialize constants for PGanswermacros.pl
  884   $envir{'numRelPercentTolDefault'}   =     getNumRelPercentTolDefault();
  885   $envir{'numZeroLevelDefault'}   =     getNumZeroLevelDefault();
  886   $envir{'numZeroLevelTolDefault'}  =     getNumZeroLevelTolDefault();
  887   $envir{'numAbsTolDefault'}      =     getNumAbsTolDefault();
  888   $envir{'numFormatDefault'}      =     getNumFormatDefault();
  889   $envir{'functRelPercentTolDefault'} =     getFunctRelPercentTolDefault();
  890   $envir{'functZeroLevelDefault'}   =     getFunctZeroLevelDefault();
  891   $envir{'functZeroLevelTolDefault'}  =     getFunctZeroLevelTolDefault();
  892   $envir{'functAbsTolDefault'}    =     getFunctAbsTolDefault();
  893   $envir{'functNumOfPoints'}      =     getFunctNumOfPoints();
  894   $envir{'functVarDefault'}       =     getFunctVarDefault();
  895   $envir{'functLLimitDefault'}    =     getFunctLLimitDefault();
  896   $envir{'functULimitDefault'}    =     getFunctULimitDefault();
  897   $envir{'functMaxConstantOfIntegration'} = getFunctMaxConstantOfIntegration();
  898   $envir{'numOfAttempts'}             =     undef(); # this is defined only for problems
  899 
  900   # defining directorys and URLs
  901   $envir{'templateDirectory'}       = &getCourseTemplateDirectory();
  902   $envir{'classDirectory'}        = $Global::classDirectory;
  903   $envir{'cgiDirectory'}        = $Global::cgiDirectory;
  904   $envir{'macroDirectory'}        = getCourseMacroDirectory();
  905   $envir{'courseScriptsDirectory'}    = getCourseScriptsDirectory();
  906   $envir{'htmlDirectory'}             =   getCourseHtmlDirectory();
  907   $envir{'htmlURL'}           = getCourseHtmlURL();
  908   $envir{'tempDirectory'}             =   getCourseTempDirectory();
  909   $envir{'tempURL'}                   =   getCourseTempURL();
  910   $envir{'scriptDirectory'}       = $Global::scriptDirectory;
  911   $envir{'webworkDocsURL'}        = $Global::webworkDocsURL;
  912   $envir{'externalTTHPath'}       = $Global::externalTTHPath;
  913 
  914 
  915   $envir{'inputs_ref'}                =  \%inputs;
  916 
  917 
  918   my $seed    = &getProblemSeed($probNum, $psvn);
  919   $seed     = 1111 unless defined($seed);
  920   $envir{'problemSeed'}     =   $seed if defined($seed);
  921 
  922   # here is a way to pass environment variables defined in webworkCourse.ph
  923   my $k;
  924   foreach $k (keys %Global::PG_environment ) {
  925     $envir{$k} = $Global::PG_environment{$k};
  926   }
  927   %envir;
  928 }
  929 
  930 BEGIN {
  931 
  932 
  933 # This subroutine cleans up temporary files after the postscript copy has been created.
  934 #
  935   sub cleanup_downloadPS {
  936 
  937       unless (defined($action ) and ($action eq 'Do problem set' or $action eq 'Do_problem_set')) {
  938           my $ERRORS = $save_errors;
  939           unless ($debugON) {  #clean up the directory
  940           eval {
  941                   chdir $tempDirectory;
  942                   unlink(
  943                     "#tempTexFileBaseName.dvi",
  944                     "$tempTexFileBaseName.ps",
  945                     "$tempTexFileBaseName.pdf",
  946                   "$tempTexFileBaseName.log",
  947                   "$tempTexFileBaseName.aux",
  948                   "$tempTexFileBaseName.tex"
  949                 );
  950                   unlink("${tempDirectory}eps/${login_name_for_psvn}*.eps");
  951               };  # clean up
  952           $ERRORS .= $ERRORS . $@;
  953         }
  954         my $query =   query_string();
  955         $query = "" unless defined($query);
  956         wwerror("$0", "ERROR: in downloadPS subroutine of welcomeAction.pl $ERRORS","","",$query) if $ERRORS;
  957       }
  958   }
  959 }
  960 
  961 END {
  962    if (defined($main::SIG_TIME_OUT) && $main::SIG_TIME_OUT == 1) {
  963       alarm(0);  # turn off the alarm
  964   my $hard_copy_message = qq{Content-type: text/html\n\n
  965       <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
  966       <HTML><BODY BGCOLOR = "FF99CC">
  967       <BLOCKQUOTE><H3>WeBWorK hard copy download time out.</H3>\n
  968     <H4>This download was cancelled because it took more than $main::TIME_OUT_CONSTANT seconds.</H4>  This may be because the
  969     WeBWorK server is extraordinarily busy, or because there was an error in the problem,
  970     or because you tried to download a set with too many  problems (more than 50).<P>\n
  971         Use the back button to return to the previous page and try again.<BR>\n
  972         If the problem is repeated you can report this to your instructor using the feedback button.
  973         <P>
  974         Because the WeBWorK server at the Unversity of Rochester is experiencing heavy use we have made downloading
  975         hard copies a low priority during the times of very heavy useage.  It will be helpful if you
  976         download hard copies during times when the load is not too heavy.
  977         <P>
  978         The load is usually heaviest in the evenings , particularly a few hours before assignments
  979         are due.  The best times to download hard copies are in the morning and afternoon
  980          -- or an hour after the due date and time of the previous assignment -- nobody is using the system then :-)
  981          </BLOCKQUOTE></BODY></HTML>
  982         };
  983      my $do_problem_message = qq{Content-type: text/html\n\n
  984         <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
  985         <HTML><BODY BGCOLOR = "FF99CC">
  986       <BLOCKQUOTE><H3>WeBWorK heavy useage time out.</H3>\n
  987     <H4>Your request (action = $action) was cancelled because it took more than $main::TIME_OUT_CONSTANT seconds.</H4>
  988     This is probably because the
  989     WeBWorK server is extraordinarily busy.<P>\n
  990     You should be warned that WeBWorK response will be unusually slow.  If possible you should try
  991     to use WeBWorK at another time when the load is not as high.  The highest useage periods are in the
  992     evening, particularly in the two hours before assignments are due.<P>\n
  993         Use the back button to return to the previous page and try again.<P>\n
  994         If the high useage problem continues you can report this to your instructor using the feedback button.
  995         <P>
  996 
  997          </BLOCKQUOTE></BODY></HTML>
  998         };
  999      if ($action eq 'Get hard copy' or $action eq 'Get_hard_copy')  {
 1000       print $hard_copy_message;
 1001      } else{
 1002       print $do_problem_message;
 1003      }
 1004 
 1005 
 1006    }
 1007 
 1008   # begin Timing code
 1009   if( $main::logTimingData == 1 ) {
 1010     my $endTime = new Benchmark;
 1011     my $error_str='';
 1012 
 1013     if ($main::SIGPIPE) {
 1014       $error_str = 'broken PIPE--';
 1015     }
 1016     elsif ($main::SIG_TIME_OUT) {
 1017       $error_str = "TIME_OUT after $main::TIME_OUT_CONSTANT secs --";
 1018     }
 1019     elsif ($action eq 'Get hard copy' or $action eq 'Get_hard_copy') {
 1020       $error_str = 'successful download --  ';
 1021     }
 1022 
 1023     &Global::logTimingInfo($main::beginTime,$endTime,$error_str.'welcomeAction.pl',$Course,$User);
 1024   }
 1025   # end Timing code
 1026   cleanup_downloadPS();
 1027 }
 1028 
 1029 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9