[system] / trunk / webwork2 / lib / WeBWorK / PG.pm Repository:
ViewVC logotype

View of /trunk/webwork2/lib/WeBWorK/PG.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 699 - (download) (as text) (annotate)
Thu Jan 9 19:03:54 2003 UTC (10 years, 4 months ago) by sh002i
File size: 18599 byte(s)
send_mail_to now works.
-sam

    1 ################################################################################
    2 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project
    3 # $Id$
    4 ################################################################################
    5 
    6 package WeBWorK::PG;
    7 
    8 =head1 NAME
    9 
   10 WeBWorK::PG - Wrap the action of the PG Translator in an easy-to-use API.
   11 
   12 =cut
   13 
   14 use strict;
   15 use warnings;
   16 use File::Path qw(rmtree);
   17 use File::Temp qw(tempdir);
   18 use WeBWorK::DB::Classlist;
   19 use WeBWorK::DB::WW;
   20 use WeBWorK::PG::Translator;
   21 use WeBWorK::Problem;
   22 use WeBWorK::Utils qw(readFile formatDateTime writeTimingLogEntry);
   23 
   24 sub new($$$$$$$$) {
   25   my $invocant = shift;
   26   my $class = ref($invocant) || $invocant;
   27   my (
   28     $courseEnv,
   29     $user,
   30     $key,
   31     $set,
   32     $problem,
   33     $psvn,
   34     $formFields, # in CGI::Vars format
   35     $translationOptions, # hashref containing options for the
   36                          # translator, such as whether to show
   37              # hints and the display mode to use
   38   ) = @_;
   39 
   40   # write timing log entry
   41   writeTimingLogEntry($courseEnv, "WeBWorK::PG::new",
   42     "user=".$user->id.",problem=".$courseEnv->{courseName}."/".$set->id."/".$problem->id.",mode=".$translationOptions->{displayMode},
   43     "begin");
   44 
   45   # install a local warn handler to collect warnings
   46   my $warnings = "";
   47   local $SIG{__WARN__} = sub { $warnings .= shift }
   48     if $courseEnv->{pg}->{options}->{catchWarnings};
   49 
   50   # create a Translator
   51   #warn "PG: creating a Translator\n";
   52   my $translator = WeBWorK::PG::Translator->new;
   53 
   54   # set the directory hash
   55   #warn "PG: setting the directory hash\n";
   56   $translator->rh_directories({
   57     courseScriptsDirectory => $courseEnv->{webworkDirs}->{macros},
   58     macroDirectory         => $courseEnv->{courseDirs}->{macros},
   59     templateDirectory      => $courseEnv->{courseDirs}->{templates},
   60     tempDirectory          => $courseEnv->{courseDirs}->{html_temp},
   61   });
   62 
   63   # evaluate modules and "extra packages"
   64   #warn "PG: evaluating modules and \"extra packages\"\n";
   65   my @modules = @{ $courseEnv->{pg}->{modules} };
   66   foreach my $module_packages_ref (@modules) {
   67     my ($module, @extra_packages) = @$module_packages_ref;
   68     # the first item is the main package
   69     $translator->evaluate_modules($module);
   70     # the remaining items are "extra" packages
   71     $translator->load_extra_packages(@extra_packages);
   72   }
   73 
   74   # set the environment (from defineProblemEnvir)
   75   #warn "PG: setting the environment (from defineProblemEnvir)\n";
   76   my $envir = defineProblemEnvir(
   77     $courseEnv,
   78     $user,
   79     $key,
   80     $set,
   81     $problem,
   82     $psvn,
   83     $formFields,
   84     $translationOptions,
   85   );
   86   $translator->environment($envir);
   87 
   88   # initialize the Translator
   89   #warn "PG: initializing the Translator\n";
   90   $translator->initialize();
   91 
   92   # load IO.pl, PG.pl, and dangerousMacros.pl using unrestricted_load
   93   # i'd like to change this at some point to have the same sort of interface to global.conf
   94   # that the module loading does -- have a list of macros to load unrestrictedly.
   95   #warn "PG: loading IO.pl, PG.pl, and dangerousMacros.pl using unrestricted_load\n";
   96   foreach (qw(IO.pl PG.pl dangerousMacros.pl)) {
   97     my $macroPath = $courseEnv->{webworkDirs}->{macros} . "/$_";
   98     my $err = $translator->unrestricted_load($macroPath);
   99     warn "Error while loading $macroPath: $err" if $err;
  100   }
  101 
  102   # set the opcode mask (using default values)
  103   #warn "PG: setting the opcode mask (using default values)\n";
  104   $translator->set_mask();
  105 
  106   # store the problem source
  107   #warn "PG: storing the problem source\n";
  108   my $sourceFile = $problem->source_file;
  109   $sourceFile = $courseEnv->{courseDirs}->{templates}."/".$sourceFile
  110     unless ($sourceFile =~ /^\//);
  111   eval { $translator->source_string(readFile($sourceFile)) };
  112   if ($@) {
  113     # well, we couldn't get the problem source, for some reason.
  114     return bless {
  115       translator => $translator,
  116       head_text  => "",
  117       body_text  => <<EOF,
  118 WeBWorK::Utils::readFile($sourceFile) says:
  119 $@
  120 EOF
  121       answers    => {},
  122       result     => {},
  123       state      => {},
  124       errors     => "Failed to read the problem source file.",
  125       warnings   => $warnings,
  126       flags      => {error_flag => 1},
  127     }, $class;
  128   }
  129 
  130   # install a safety filter (&safetyFilter)
  131   #warn "PG: installing a safety filter\n";
  132   $translator->rf_safety_filter(\&safetyFilter);
  133 
  134   # write timing log entry -- the translator is now all set up
  135   writeTimingLogEntry($courseEnv, "WeBWorK::PG::new",
  136     "initialized",
  137     "intermediate");
  138 
  139   # translate the PG source into text
  140   #warn "PG: translating the PG source into text\n";
  141   $translator->translate();
  142 
  143   # after we're done translating, we may have to clean up after the translator.
  144   # for example, 'images' mode uses a tempdir for dvipng's temp files. We have
  145   # to remove it.
  146   if ($translationOptions->{displayMode} eq 'images' && $envir->{dvipngTempDir}) {
  147     rmtree($envir->{dvipngTempDir}, 0, 0);
  148   }
  149 
  150   my ($result, $state); # we'll need these on the other side of the if block!
  151   if ($translationOptions->{processAnswers}) {
  152 
  153     # process student answers
  154     #warn "PG: processing student answers\n";
  155     $translator->process_answers($formFields);
  156 
  157     # retrieve the problem state and give it to the translator
  158     #warn "PG: retrieving the problem state and giving it to the translator\n";
  159     $translator->rh_problem_state({
  160       recorded_score =>       $problem->status,
  161       num_of_correct_ans =>   $problem->num_correct,
  162       num_of_incorrect_ans => $problem->num_incorrect,
  163     });
  164 
  165     # determine an entry order -- the ANSWER_ENTRY_ORDER flag is built by
  166     # the PG macro package (PG.pl)
  167     #warn "PG: determining an entry order\n";
  168     my @answerOrder =
  169       $translator->rh_flags->{ANSWER_ENTRY_ORDER}
  170         ? @{ $translator->rh_flags->{ANSWER_ENTRY_ORDER} }
  171         : keys %{ $translator->rh_evaluated_answers };
  172 
  173     # install a grader -- use the one specified in the problem,
  174     # or fall back on the default from the course environment.
  175     # (two magic strings are accepted, to avoid having to
  176     # reference code when it would be difficult.)
  177     #warn "PG: installing a grader\n";
  178     my $grader = $translator->rh_flags->{PROBLEM_GRADER_TO_USE}
  179       || $courseEnv->{pg}->{options}->{grader};
  180     $grader = $translator->rf_std_problem_grader
  181       if $grader eq "std_problem_grader";
  182     $grader = $translator->rf_avg_problem_grader
  183       if $grader eq "avg_problem_grader";
  184     die "Problem grader $grader is not a CODE reference."
  185       unless ref $grader eq "CODE";
  186     $translator->rf_problem_grader($grader);
  187 
  188     # grade the problem
  189     #warn "PG: grading the problem\n";
  190     ($result, $state) = $translator->grade_problem(
  191       answers_submitted  => $translationOptions->{processAnswers},
  192       ANSWER_ENTRY_ORDER => \@answerOrder,
  193     );
  194 
  195   }
  196 
  197   # write timing log entry
  198   writeTimingLogEntry($courseEnv, "WeBWorK::PG::new", "", "end");
  199 
  200   # return an object which contains the translator and the results of
  201   # the translation process. this is DIFFERENT from the "format expected
  202   # by Webwork.pm (and I believe processProblem8, but check.)"
  203   return bless {
  204     translator => $translator,
  205     head_text  => ${ $translator->r_header },
  206     body_text  => ${ $translator->r_text   },
  207     answers    => $translator->rh_evaluated_answers,
  208     result     => $result,
  209     state      => $state,
  210     errors     => $translator->errors,
  211     warnings   => $warnings,
  212     flags      => $translator->rh_flags,
  213   }, $class;
  214 }
  215 
  216 # -----
  217 
  218 sub defineProblemEnvir($$$$$$$) {
  219   my (
  220     $courseEnv,
  221     $user,
  222     $key,
  223     $set,
  224     $problem,
  225     $psvn,
  226     $formFields,
  227     $options,
  228   ) = @_;
  229 
  230   my %envir;
  231 
  232   # PG environment variables
  233   # from docs/pglanguage/pgreference/environmentvariables as of 06/25/2002
  234   # any changes are noted by "ADDED:" or "REMOVED:"
  235 
  236   # Vital state information
  237   # ADDED: displayHintsQ, displaySolutionsQ, refreshMath2img,
  238   #        texDisposition
  239 
  240   $envir{psvn}              = $psvn;
  241   $envir{psvnNumber}        = $envir{psvn};
  242   $envir{probNum}           = $problem->id;
  243   $envir{questionNumber}    = $envir{probNum};
  244   $envir{fileName}          = $problem->source_file;
  245   $envir{probFileName}      = $envir{fileName};
  246   $envir{problemSeed}       = $problem->problem_seed;
  247   $envir{displayMode}       = translateDisplayModeNames($options->{displayMode});
  248   $envir{languageMode}      = $envir{displayMode};
  249   $envir{outputMode}        = $envir{displayMode};
  250   $envir{displayHintsQ}     = $options->{showHints};
  251   $envir{displaySolutionsQ} = $options->{showSolutions};
  252   $envir{refreshMath2img}   = $options->{refreshMath2img};
  253   $envir{texDisposition}    = "pdf"; # in webwork-modperl, we use pdflatex
  254 
  255   # Problem Information
  256   # ADDED: courseName, formatedDueDate
  257 
  258   $envir{openDate}            = $set->open_date;
  259   $envir{formattedOpenDate}   = formatDateTime($envir{openDate});
  260   $envir{dueDate}             = $set->due_date;
  261   $envir{formattedDueDate}    = formatDateTime($envir{dueDate});
  262   $envir{formatedDueDate}     = $envir{formattedDueDate}; # typo in many header files
  263   $envir{answerDate}          = $set->answer_date;
  264   $envir{formattedAnswerDate} = formatDateTime($envir{answerDate});
  265   $envir{numOfAttempts}       = ($problem->num_correct || 0) + ($problem->num_incorrect || 0);
  266   $envir{problemValue}        = $problem->value;
  267   $envir{sessionKey}          = $key;
  268   $envir{courseName}          = $courseEnv->{courseName};
  269 
  270   # Student Information
  271   # ADDED: studentID
  272 
  273   $envir{sectionName}      = $user->section;
  274   $envir{sectionNumber}    = $envir{sectionName};
  275   $envir{recitationName}   = $user->recitation;
  276   $envir{recitationNumber} = $envir{recitationName};
  277   $envir{setNumber}        = $set->id;
  278   $envir{studentLogin}     = $user->id;
  279   $envir{studentName}      = $user->first_name . " " . $user->last_name;
  280   $envir{studentID}        = $user->student_id;
  281 
  282   # Answer Information
  283   # REMOVED: refSubmittedAnswers
  284 
  285   $envir{inputs_ref} = $formFields;
  286 
  287   # External Programs
  288   # ADDED: externalLaTeXPath, externalDvipngPath,
  289   #        externalGif2EpsPath, externalPng2EpsPath
  290 
  291   $envir{externalTTHPath}      = $courseEnv->{externalPrograms}->{tth};
  292   $envir{externalLaTeXPath}    = $courseEnv->{externalPrograms}->{latex};
  293   $envir{externalDvipngPath}   = $courseEnv->{externalPrograms}->{dvipng};
  294   $envir{externalGif2EpsPath}  = $courseEnv->{externalPrograms}->{gif2eps};
  295   $envir{externalPng2EpsPath}  = $courseEnv->{externalPrograms}->{png2eps};
  296   $envir{externalGif2PngPath}  = $courseEnv->{externalPrograms}->{gif2png};
  297 
  298   # Directories and URLs
  299   # REMOVED: courseName
  300   # ADDED: dvipngTempDir
  301 
  302   $envir{cgiDirectory}           = undef;
  303   $envir{cgiURL}                 = undef;
  304   $envir{classDirectory}         = undef;
  305   $envir{courseScriptsDirectory} = $courseEnv->{webworkDirs}->{macros}."/";
  306   $envir{htmlDirectory}          = $courseEnv->{courseDirs}->{html}."/";
  307   $envir{htmlURL}                = $courseEnv->{courseURLs}->{html}."/";
  308   $envir{macroDirectory}         = $courseEnv->{courseDirs}->{macros}."/";
  309   $envir{templateDirectory}      = $courseEnv->{courseDirs}->{templates}."/";
  310   $envir{tempDirectory}          = $courseEnv->{courseDirs}->{html_temp}."/";
  311   $envir{tempURL}                = $courseEnv->{courseURLs}->{html_temp}."/";
  312   $envir{scriptDirectory}        = undef;
  313   $envir{webworkDocsURL}         = $courseEnv->{webworkURLs}->{docs}."/";
  314   $envir{dvipngTempDir}          = $options->{displayMode} eq 'images'
  315     ? tempdir("webwork-dvipng-XXXXXXXX", DIR => $envir{tempDirectory})
  316     : undef;
  317 
  318   # Information for sending mail
  319 
  320   $envir{mailSmtpServer} = $courseEnv->{mail}->{smtpServer};
  321   $envir{mailSmtpSender} = $courseEnv->{mail}->{smtpSender};
  322   $envir{ALLOW_MAIL_TO}  = $courseEnv->{mail}->{allowedRecipients};
  323 
  324   # Default values for evaluating answers
  325 
  326   my $ansEvalDefaults = $courseEnv->{pg}->{ansEvalDefaults};
  327   $envir{$_} = $ansEvalDefaults->{$_} foreach (keys %$ansEvalDefaults);
  328 
  329   # Other things...
  330 
  331   $envir{PROBLEM_GRADER_TO_USE} = $courseEnv->{pg}->{options}->{grader};
  332 
  333   return \%envir;
  334 }
  335 
  336 sub translateDisplayModeNames($) {
  337   my $name = shift;
  338   return {
  339     tex           => "TeX",
  340     plainText     => "HTML",
  341     formattedText => "HTML_tth",
  342     images        => "HTML_img"
  343   }->{$name};
  344 }
  345 
  346 sub safetyFilter {
  347   my $answer = shift; # accepts one answer and checks it
  348   my $submittedAnswer = $answer;
  349   $answer = '' unless defined $answer;
  350   my ($errorno);
  351   $answer =~ tr/\000-\037/ /;
  352   # Return if answer field is empty
  353   unless ($answer =~ /\S/) {
  354     #$errorno = "<BR>No answer was submitted.";
  355     $errorno = 0;  ## don't report blank answer as error
  356     return ($answer,$errorno);
  357   }
  358   # replace ^ with **    (for exponentiation)
  359   # $answer =~ s/\^/**/g;
  360   # Return if forbidden characters are found
  361   unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)]+$/ )  {
  362     $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c;
  363     $errorno = "<BR>There are forbidden characters in your answer: $submittedAnswer<BR>";
  364     return ($answer,$errorno);
  365   }
  366   $errorno = 0;
  367   return($answer, $errorno);
  368 }
  369 
  370 1;
  371 
  372 __END__
  373 
  374 =head1 SYNOPSIS
  375 
  376  $pg = WeBWorK::PG->new(
  377    $courseEnv,  # a WeBWorK::CourseEnvironment object
  378    $user,       # a WeBWorK::User object
  379    $sessionKey,
  380    $set,        # a WeBWorK::Set object
  381    $problem,    # a WeBWorK::Problem object
  382    $psvn,
  383    $formFields  # in &WeBWorK::Form::Vars format
  384    { # translation options
  385      displayMode     => "images", # (plainText|formattedText|images)
  386      showHints       => 1,        # (0|1)
  387      showSolutions   => 0,        # (0|1)
  388      refreshMath2img => 0,        # (0|1)
  389      processAnswers  => 1,        # (0|1)
  390    },
  391  );
  392 
  393  $translator = $pg->{translator}; # WeBWorK::PG::Translator
  394  $body       = $pg->{body_text};  # text string
  395  $header     = $pg->{head_text};  # text string
  396  $answerHash = $pg->{answers};    # WeBWorK::PG::AnswerHash
  397  $result     = $pg->{result};     # hash reference
  398  $state      = $pg->{state};      # hash reference
  399  $errors     = $pg->{errors};     # text string
  400  $warnings   = $pg->{warnings};   # text string
  401  $flags      = $pg->{flags};      # hash reference
  402 
  403 =head1 DESCRIPTION
  404 
  405 WeBWorK::PG encapsulates the PG translation process, making multiple calls to
  406 WeBWorK::PG::Translator. Much of the flexibility of the Translator is hidden,
  407 instead making choices that are appropriate for the webwork-modperl system.
  408 
  409 =head1 CONSTRUCTION
  410 
  411 =over
  412 
  413 =item new (ENVIRONMENT, USER, KEY, SET, PROBLEM, PSVN, FIELDS, OPTIONS)
  414 
  415 The C<new> method creates a translator, initializes it using the parameters
  416 specified, translates a PG file, and processes answers. It returns a reference
  417 to a blessed hash containing the results of the translation process.
  418 
  419 =back
  420 
  421 =head2 Parameters
  422 
  423 =over
  424 
  425 =item ENVIRONMENT
  426 
  427 a WeBWorK::CourseEnvironment object
  428 
  429 =item USER
  430 
  431 a WeBWorK::User object
  432 
  433 =item KEY
  434 
  435 the session key of the current session
  436 
  437 =item SET
  438 
  439 a WeBWorK::Set object
  440 
  441 =item PROBLEM
  442 
  443 a WeBWorK::Problem object. The contents of the source_file field can specify a
  444 PG file either by absolute path or path relative to the "templates" directory.
  445 I<The caller should remove taint from this value before passing!>
  446 
  447 =item PSVN
  448 
  449 the problem set version number
  450 
  451 =item FIELDS
  452 
  453 a reference to a hash (as returned by &WeBWorK::Form::Vars) containing form
  454 fields submitted by a problem processor. The translator will look for fields
  455 like "AnSwEr[0-9]" containing submitted student answers.
  456 
  457 =item OPTIONS
  458 
  459 a reference to a hash containing the following data:
  460 
  461 =over
  462 
  463 =item displayMode
  464 
  465 one of "plainText", "formattedText", or "images"
  466 
  467 =item showHints
  468 
  469 boolean, render hints
  470 
  471 =item showSolutions
  472 
  473 boolean, render solutions
  474 
  475 =item refreshMath2img
  476 
  477 boolean, force images created by math2img (in "images" mode) to be recreated,
  478 even if the PG source has not been updated.
  479 
  480 =item processAnswers
  481 
  482 boolean, call answer evaluators and graders
  483 
  484 =back
  485 
  486 =back
  487 
  488 =head2 RETURN VALUE
  489 
  490 The C<new> method returns a blessed hash reference containing the following
  491 fields. More information can be found in the documentation for
  492 WeBWorK::PG::Translator.
  493 
  494 =over
  495 
  496 =item translator
  497 
  498 The WeBWorK::PG::Translator object used to render the problem.
  499 
  500 =item head_text
  501 
  502 HTML code for the E<lt>headE<gt> block of an resulting web page. Used for
  503 JavaScript features.
  504 
  505 =item body_text
  506 
  507 HTML code for the E<lt>bodyE<gt> block of an resulting web page.
  508 
  509 =item answers
  510 
  511 An C<AnswerHash> object containing submitted answers, and results of answer
  512 evaluation.
  513 
  514 =item result
  515 
  516 A hash containing the results of grading the problem.
  517 
  518 =item state
  519 
  520 A hash containing the new problem state.
  521 
  522 =item errors
  523 
  524 A string containing any errors encountered while rendering the problem.
  525 
  526 =item warnings
  527 
  528 A string containing any warnings encountered while rendering the problem.
  529 
  530 =item flags
  531 
  532 A hash containing PG_flags (see the Translator docs).
  533 
  534 =back
  535 
  536 =head1 OPERATION
  537 
  538 WeBWorK::PG goes through the following operations when constructed:
  539 
  540 =over
  541 
  542 =item Get database information
  543 
  544 Retrieve information about the current user, set, and problem from the
  545 database.
  546 
  547 =item Create a translator
  548 
  549 Instantiate a WeBWorK::PG::Translator object.
  550 
  551 =item Set the directory hash
  552 
  553 Set the translator's directory hash (courseScripts, macros, templates, and temp
  554 directories) from the course environment.
  555 
  556 =item Evaluate PG modules
  557 
  558 Using the module list from the course environment (pg->modules), perform a
  559 "use"-like operation to evaluate modules at runtime.
  560 
  561 =item Set the problem environment
  562 
  563 Use data from the user, set, and problem, as well as the course environemnt and
  564 translation options, to set the problem environment.
  565 
  566 =item Initialize the translator
  567 
  568 Call &WeBWorK::PG::Translator::initialize. What more do you want?
  569 
  570 =item Load PG.pl and dangerousMacros.pl
  571 
  572 These macros must be loaded without opcode masking, so they are loaded here.
  573 
  574 =item Set the opcode mask
  575 
  576 Set the opcode mask to the default specified by WeBWorK::PG::Translator.
  577 
  578 =item Load the problem source
  579 
  580 Give the problem source to the translator.
  581 
  582 =item Install a safety filter
  583 
  584 The safety filter is used to preprocess student input before evaluation. The
  585 default safety filter, &WeBWorK::PG::safetyFilter, is used.
  586 
  587 =item Translate the problem source
  588 
  589 Call &WeBWorK::PG::Translator::translate to render the problem source into the
  590 format given by the display mode.
  591 
  592 =item Process student answers
  593 
  594 Use form field inputs to evaluate student answers.
  595 
  596 =item Load the problem state
  597 
  598 Use values from the database to initialize the problem state, so that the
  599 grader will have a point of reference.
  600 
  601 =item Determine an entry order
  602 
  603 Use the ANSWER_ENTRY_ORDER flag to determine the order of answers in the
  604 problem. This is important for problems with dependancies among parts.
  605 
  606 =item Install a grader
  607 
  608 Use the PROBLEM_GRADER_TO_USE flag, or a default from the course environment,
  609 to install a grader.
  610 
  611 =item Grade the problem
  612 
  613 Use the selected grader to grade the problem.
  614 
  615 =back
  616 
  617 =head1 AUTHOR
  618 
  619 Written by Sam Hathaway, sh002i (at) math.rochester.edu.
  620 
  621 =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9