[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 927 - (download) (as text) (annotate)
Wed May 28 03:31:28 2003 UTC (9 years, 11 months ago) by gage
File size: 18812 byte(s)
Problem Editor now creates a temporary file to edit (
adding .tmp to the file name)
Refresh causes a redirect to the Problem.pm with a GET command.
Some of the arguments are unnecessary and need to be cleaned up.

The seed and display mode in the problem editor are not properly
initialized from the problem set.

Problem.pm has been modified to check for these arguments and to read
the temporary source file, seed and displayMode.  These are passed
through to a PG.pm object.

PG also needed to be modified so that the source_file and seed values
are overridden

    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::PG::Translator;
   19 use WeBWorK::Utils qw(readFile formatDateTime writeTimingLogEntry);
   20 
   21 sub new($$$$$$$$) {
   22   my $invocant = shift;
   23   my $class = ref($invocant) || $invocant;
   24   my (
   25     $courseEnv,
   26     $user,
   27     $key,
   28     $set,
   29     $problem,
   30     $psvn,
   31     $formFields, # in CGI::Vars format
   32     $translationOptions, # hashref containing options for the
   33                          # translator, such as whether to show
   34              # hints and the display mode to use
   35   ) = @_;
   36 
   37   # write timing log entry
   38   writeTimingLogEntry($courseEnv, "WeBWorK::PG::new",
   39     "user=".$user->user_id.",problem=".$courseEnv->{courseName}."/".$set->set_id."/".$problem->problem_id.",mode=".$translationOptions->{displayMode},
   40     "begin");
   41 
   42   # install a local warn handler to collect warnings
   43   my $warnings = "";
   44   local $SIG{__WARN__} = sub { $warnings .= shift }
   45     if $courseEnv->{pg}->{options}->{catchWarnings};
   46 
   47   # create a Translator
   48   #warn "PG: creating a Translator\n";
   49   my $translator = WeBWorK::PG::Translator->new;
   50 
   51   # set the directory hash
   52   #warn "PG: setting the directory hash\n";
   53   $translator->rh_directories({
   54     courseScriptsDirectory => $courseEnv->{webworkDirs}->{macros},
   55     macroDirectory         => $courseEnv->{courseDirs}->{macros},
   56     templateDirectory      => $courseEnv->{courseDirs}->{templates},
   57     tempDirectory          => $courseEnv->{courseDirs}->{html_temp},
   58   });
   59 
   60   # evaluate modules and "extra packages"
   61   #warn "PG: evaluating modules and \"extra packages\"\n";
   62   my @modules = @{ $courseEnv->{pg}->{modules} };
   63   foreach my $module_packages_ref (@modules) {
   64     my ($module, @extra_packages) = @$module_packages_ref;
   65     # the first item is the main package
   66     $translator->evaluate_modules($module);
   67     # the remaining items are "extra" packages
   68     $translator->load_extra_packages(@extra_packages);
   69   }
   70 
   71   # set the environment (from defineProblemEnvir)
   72   #warn "PG: setting the environment (from defineProblemEnvir)\n";
   73   my $envir = defineProblemEnvir(
   74     $courseEnv,
   75     $user,
   76     $key,
   77     $set,
   78     $problem,
   79     $psvn,
   80     $formFields,
   81     $translationOptions,
   82   );
   83   $translator->environment($envir);
   84 
   85   # initialize the Translator
   86   #warn "PG: initializing the Translator\n";
   87   $translator->initialize();
   88 
   89   # load IO.pl, PG.pl, and dangerousMacros.pl using unrestricted_load
   90   # i'd like to change this at some point to have the same sort of interface to global.conf
   91   # that the module loading does -- have a list of macros to load unrestrictedly.
   92   #warn "PG: loading IO.pl, PG.pl, and dangerousMacros.pl using unrestricted_load\n";
   93   foreach (qw(IO.pl PG.pl dangerousMacros.pl)) {
   94     my $macroPath = $courseEnv->{webworkDirs}->{macros} . "/$_";
   95     my $err = $translator->unrestricted_load($macroPath);
   96     warn "Error while loading $macroPath: $err" if $err;
   97   }
   98 
   99   # set the opcode mask (using default values)
  100   #warn "PG: setting the opcode mask (using default values)\n";
  101   $translator->set_mask();
  102 
  103   # store the problem source
  104   #warn "PG: storing the problem source\n";
  105   my $sourceFile = ( defined($translationOptions->{override_problem_source}) ) ?
  106             $translationOptions->{override_problem_source} :
  107             $problem->source_file;
  108   $sourceFile = $courseEnv->{courseDirs}->{templates}."/".$sourceFile
  109     unless ($sourceFile =~ /^\//);
  110   eval { $translator->source_string(readFile($sourceFile)) };
  111   if ($@) {
  112     # well, we couldn't get the problem source, for some reason.
  113     return bless {
  114       translator => $translator,
  115       head_text  => "",
  116       body_text  => <<EOF,
  117 WeBWorK::Utils::readFile($sourceFile) says:
  118 $@
  119 EOF
  120       answers    => {},
  121       result     => {},
  122       state      => {},
  123       errors     => "Failed to read the problem source file.",
  124       warnings   => $warnings,
  125       flags      => {error_flag => 1},
  126     }, $class;
  127   }
  128 
  129   # install a safety filter (&safetyFilter)
  130   #warn "PG: installing a safety filter\n";
  131   $translator->rf_safety_filter(\&safetyFilter);
  132 
  133   # write timing log entry -- the translator is now all set up
  134   writeTimingLogEntry($courseEnv, "WeBWorK::PG::new",
  135     "initialized",
  136     "intermediate");
  137 
  138   # translate the PG source into text
  139   #warn "PG: translating the PG source into text\n";
  140   $translator->translate();
  141 
  142   # after we're done translating, we may have to clean up after the translator.
  143   # for example, 'images' mode uses a tempdir for dvipng's temp files. We have
  144   # to remove it.
  145   if ($translationOptions->{displayMode} eq 'images' && $envir->{dvipngTempDir}) {
  146     rmtree($envir->{dvipngTempDir}, 0, 0);
  147   }
  148 
  149   my ($result, $state); # we'll need these on the other side of the if block!
  150   if ($translationOptions->{processAnswers}) {
  151 
  152     # process student answers
  153     #warn "PG: processing student answers\n";
  154     $translator->process_answers($formFields);
  155 
  156     # retrieve the problem state and give it to the translator
  157     #warn "PG: retrieving the problem state and giving it to the translator\n";
  158     $translator->rh_problem_state({
  159       recorded_score =>       $problem->status,
  160       num_of_correct_ans =>   $problem->num_correct,
  161       num_of_incorrect_ans => $problem->num_incorrect,
  162     });
  163 
  164     # determine an entry order -- the ANSWER_ENTRY_ORDER flag is built by
  165     # the PG macro package (PG.pl)
  166     #warn "PG: determining an entry order\n";
  167     my @answerOrder =
  168       $translator->rh_flags->{ANSWER_ENTRY_ORDER}
  169         ? @{ $translator->rh_flags->{ANSWER_ENTRY_ORDER} }
  170         : keys %{ $translator->rh_evaluated_answers };
  171 
  172     # install a grader -- use the one specified in the problem,
  173     # or fall back on the default from the course environment.
  174     # (two magic strings are accepted, to avoid having to
  175     # reference code when it would be difficult.)
  176     #warn "PG: installing a grader\n";
  177     my $grader = $translator->rh_flags->{PROBLEM_GRADER_TO_USE}
  178       || $courseEnv->{pg}->{options}->{grader};
  179     $grader = $translator->rf_std_problem_grader
  180       if $grader eq "std_problem_grader";
  181     $grader = $translator->rf_avg_problem_grader
  182       if $grader eq "avg_problem_grader";
  183     die "Problem grader $grader is not a CODE reference."
  184       unless ref $grader eq "CODE";
  185     $translator->rf_problem_grader($grader);
  186 
  187     # grade the problem
  188     #warn "PG: grading the problem\n";
  189     ($result, $state) = $translator->grade_problem(
  190       answers_submitted  => $translationOptions->{processAnswers},
  191       ANSWER_ENTRY_ORDER => \@answerOrder,
  192     );
  193 
  194   }
  195 
  196   # write timing log entry
  197   writeTimingLogEntry($courseEnv, "WeBWorK::PG::new", "", "end");
  198 
  199   # return an object which contains the translator and the results of
  200   # the translation process. this is DIFFERENT from the "format expected
  201   # by Webwork.pm (and I believe processProblem8, but check.)"
  202   return bless {
  203     translator => $translator,
  204     head_text  => ${ $translator->r_header },
  205     body_text  => ${ $translator->r_text   },
  206     answers    => $translator->rh_evaluated_answers,
  207     result     => $result,
  208     state      => $state,
  209     errors     => $translator->errors,
  210     warnings   => $warnings,
  211     flags      => $translator->rh_flags,
  212   }, $class;
  213 }
  214 
  215 # -----
  216 
  217 sub defineProblemEnvir($$$$$$$) {
  218   my (
  219     $courseEnv,
  220     $user,
  221     $key,
  222     $set,
  223     $problem,
  224     $psvn,
  225     $formFields,
  226     $options,
  227   ) = @_;
  228 
  229   my %envir;
  230 
  231   # PG environment variables
  232   # from docs/pglanguage/pgreference/environmentvariables as of 06/25/2002
  233   # any changes are noted by "ADDED:" or "REMOVED:"
  234 
  235   # Vital state information
  236   # ADDED: displayHintsQ, displaySolutionsQ, refreshMath2img,
  237   #        texDisposition
  238 
  239   $envir{psvn}              = $psvn;
  240   $envir{psvnNumber}        = $envir{psvn};
  241   $envir{probNum}           = $problem->problem_id;
  242   $envir{questionNumber}    = $envir{probNum};
  243   $envir{fileName}          = $problem->source_file;
  244   $envir{probFileName}      = $envir{fileName};
  245   $envir{problemSeed}       = (defined($options->{override_seed}) ) ? $options->{override_seed} :$problem->problem_seed;
  246   $envir{displayMode}       = translateDisplayModeNames($options->{displayMode});
  247   $envir{languageMode}      = $envir{displayMode};
  248   $envir{outputMode}        = $envir{displayMode};
  249   $envir{displayHintsQ}     = $options->{showHints};
  250   $envir{displaySolutionsQ} = $options->{showSolutions};
  251   $envir{refreshMath2img}   = $options->{refreshMath2img};
  252   $envir{texDisposition}    = "pdf"; # in webwork-modperl, we use pdflatex
  253 
  254   # Problem Information
  255   # ADDED: courseName, formatedDueDate
  256 
  257   $envir{openDate}            = $set->open_date;
  258   $envir{formattedOpenDate}   = formatDateTime($envir{openDate});
  259   $envir{dueDate}             = $set->due_date;
  260   $envir{formattedDueDate}    = formatDateTime($envir{dueDate});
  261   $envir{formatedDueDate}     = $envir{formattedDueDate}; # typo in many header files
  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->set_id;
  277   $envir{studentLogin}     = $user->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   $envir{externalGif2PngPath}  = $courseEnv->{externalPrograms}->{gif2png};
  296 
  297   # Directories and URLs
  298   # REMOVED: courseName
  299   # ADDED: dvipngTempDir
  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   # Information for sending mail
  318 
  319   $envir{mailSmtpServer} = $courseEnv->{mail}->{smtpServer};
  320   $envir{mailSmtpSender} = $courseEnv->{mail}->{smtpSender};
  321   $envir{ALLOW_MAIL_TO}  = $courseEnv->{mail}->{allowedRecipients};
  322 
  323   # Default values for evaluating answers
  324 
  325   my $ansEvalDefaults = $courseEnv->{pg}->{ansEvalDefaults};
  326   $envir{$_} = $ansEvalDefaults->{$_} foreach (keys %$ansEvalDefaults);
  327 
  328   # Other things...
  329 
  330   $envir{PROBLEM_GRADER_TO_USE} = $courseEnv->{pg}->{options}->{grader};
  331 
  332   return \%envir;
  333 }
  334 
  335 sub translateDisplayModeNames($) {
  336   my $name = shift;
  337   return {
  338     tex           => "TeX",
  339     plainText     => "HTML",
  340     formattedText => "HTML_tth",
  341     images        => "HTML_img"
  342   }->{$name};
  343 }
  344 
  345 sub safetyFilter {
  346   my $answer = shift; # accepts one answer and checks it
  347   my $submittedAnswer = $answer;
  348   $answer = '' unless defined $answer;
  349   my ($errorno);
  350   $answer =~ tr/\000-\037/ /;
  351   # Return if answer field is empty
  352   unless ($answer =~ /\S/) {
  353     #$errorno = "<BR>No answer was submitted.";
  354     $errorno = 0;  ## don't report blank answer as error
  355     return ($answer,$errorno);
  356   }
  357   # replace ^ with **    (for exponentiation)
  358   # $answer =~ s/\^/**/g;
  359   # Return if forbidden characters are found
  360   unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)]+$/ )  {
  361     $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c;
  362     $errorno = "<BR>There are forbidden characters in your answer: $submittedAnswer<BR>";
  363     return ($answer,$errorno);
  364   }
  365   $errorno = 0;
  366   return($answer, $errorno);
  367 }
  368 
  369 1;
  370 
  371 __END__
  372 
  373 =head1 SYNOPSIS
  374 
  375  $pg = WeBWorK::PG->new(
  376    $courseEnv,  # a WeBWorK::CourseEnvironment object
  377    $user,       # a WeBWorK::DB::Record::User object
  378    $sessionKey,
  379    $set,        # a WeBWorK::DB::Record::UserSet object
  380    $problem,    # a WeBWorK::DB::Record::UserProblem object
  381    $psvn,
  382    $formFields  # in &WeBWorK::Form::Vars format
  383    { # translation options
  384      displayMode     => "images", # (plainText|formattedText|images)
  385      showHints       => 1,        # (0|1)
  386      showSolutions   => 0,        # (0|1)
  387      refreshMath2img => 0,        # (0|1)
  388      processAnswers  => 1,        # (0|1)
  389    },
  390  );
  391 
  392  $translator = $pg->{translator}; # WeBWorK::PG::Translator
  393  $body       = $pg->{body_text};  # text string
  394  $header     = $pg->{head_text};  # text string
  395  $answerHash = $pg->{answers};    # WeBWorK::PG::AnswerHash
  396  $result     = $pg->{result};     # hash reference
  397  $state      = $pg->{state};      # hash reference
  398  $errors     = $pg->{errors};     # text string
  399  $warnings   = $pg->{warnings};   # text string
  400  $flags      = $pg->{flags};      # hash reference
  401 
  402 =head1 DESCRIPTION
  403 
  404 WeBWorK::PG encapsulates the PG translation process, making multiple calls to
  405 WeBWorK::PG::Translator. Much of the flexibility of the Translator is hidden,
  406 instead making choices that are appropriate for the webwork-modperl system.
  407 
  408 =head1 CONSTRUCTION
  409 
  410 =over
  411 
  412 =item new (ENVIRONMENT, USER, KEY, SET, PROBLEM, PSVN, FIELDS, OPTIONS)
  413 
  414 The C<new> method creates a translator, initializes it using the parameters
  415 specified, translates a PG file, and processes answers. It returns a reference
  416 to a blessed hash containing the results of the translation process.
  417 
  418 =back
  419 
  420 =head2 Parameters
  421 
  422 =over
  423 
  424 =item ENVIRONMENT
  425 
  426 a WeBWorK::CourseEnvironment object
  427 
  428 =item USER
  429 
  430 a WeBWorK::User object
  431 
  432 =item KEY
  433 
  434 the session key of the current session
  435 
  436 =item SET
  437 
  438 a WeBWorK::Set object
  439 
  440 =item PROBLEM
  441 
  442 a WeBWorK::DB::Record::UserProblem object. The contents of the source_file
  443 field can specify a PG file either by absolute path or path relative to the
  444 "templates" directory. I<The caller should remove taint from this value before
  445 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