[system] / branches / rel-2-4-patches / webwork2 / lib / WeBWorK / PG.pm Repository:
ViewVC logotype

View of /branches/rel-2-4-patches/webwork2/lib/WeBWorK/PG.pm

Parent Directory Parent Directory | Revision Log Revision Log


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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9