[system] / trunk / webwork-modperl / lib / WeBWorK / PG.pm Repository:
ViewVC logotype

View of /trunk/webwork-modperl/lib/WeBWorK/PG.pm

Parent Directory Parent Directory | Revision Log Revision Log


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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9