[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 526 - (download) (as text) (annotate)
Thu Aug 29 19:56:24 2002 UTC (10 years, 9 months ago) by sh002i
File size: 18223 byte(s)
HTML_img mode ("images" mode in the HTML interface) now uses dvipng to
generate images. ProblemSet now has a link to Hardcopy.
-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::Temp qw(tempdir);
   17 use WeBWorK::DB::Classlist;
   18 use WeBWorK::DB::WW;
   19 use WeBWorK::PG::Translator;
   20 use WeBWorK::Problem;
   21 use WeBWorK::Utils qw(readFile formatDateTime);
   22 
   23 sub new($$$$$$$$) {
   24   my $invocant = shift;
   25   my $class = ref($invocant) || $invocant;
   26   my (
   27     $courseEnv,
   28     $user,
   29     $key,
   30     $set,
   31     $problem,
   32     $psvn,
   33     $formFields, # in CGI::Vars format
   34     $translationOptions, # hashref containing options for the
   35                          # translator, such as whether to show
   36              # hints and the display mode to use
   37   ) = @_;
   38 
   39 # # get database information
   40 # my $classlist = WeBWorK::DB::Classlist->new($courseEnv);
   41 # my $wwdb = WeBWorK::DB::WW->new($courseEnv);
   42 # my $user = $classlist->getUser($userName);
   43 # my $set = $wwdb->getSet($userName, $setName);
   44 # my $psvn = $wwdb->getPSVN($userName, $setName);
   45 #
   46 # my $problem;
   47 # if ($problemNumber =~ /^\d+$/) {
   48 #   $problem = $wwdb->getProblem($userName, $setName, $problemNumber);
   49 # } else {
   50 #   # This is the fun part: if $problemNumber is NON-NUMERIC, the
   51 #   # user wants to specify a PG file directly. We manufacture a
   52 #   # Problem object using fake data and the specified source file.
   53 #   # This is potentially dangerous since an untrusted user is
   54 #   # allowed to specifiy an arbitrary file to be evaluated as PG.
   55 #   # A user of PG.pm MUST MAKE SURE that if $problemNumber is
   56 #   # supplied by an untrusted source (i.e. the Apache request),
   57 #   # it is numberic. A simple
   58 #   #
   59 #   #   die unless $problemNumber =~ /^\d+$/;
   60 #   #
   61 #   # should suffice.
   62 #   $problem = WeBWorK::Problem->new(
   63 #     id => 0,
   64 #     set_id => $set->id,
   65 #     login_id => $user->id,
   66 #     source_file => $problemNumber,
   67 #     # the rest of Problem's fields are not needed
   68 #   );
   69 # }
   70 
   71   # create a Translator
   72   warn "PG: creating a Translator\n";
   73   my $translator = WeBWorK::PG::Translator->new;
   74 
   75   # set the directory hash
   76   warn "PG: setting the directory hash\n";
   77   $translator->rh_directories({
   78     courseScriptsDirectory => $courseEnv->{webworkDirs}->{macros},
   79     macroDirectory         => $courseEnv->{courseDirs}->{macros},
   80     templateDirectory      => $courseEnv->{courseDirs}->{templates},
   81     tempDirectory          => $courseEnv->{courseDirs}->{html_temp},
   82   });
   83 
   84   # evaluate modules and "extra packages"
   85   warn "PG: evaluating modules and \"extra packages\"\n";
   86   my @modules = @{ $courseEnv->{pg}->{modules} };
   87   foreach my $module_packages_ref (@modules) {
   88     my ($module, @extra_packages) = @$module_packages_ref;
   89     # the first item is the main package
   90     $translator->evaluate_modules($module);
   91     # the remaining items are "extra" packages
   92     $translator->load_extra_packages(@extra_packages);
   93   }
   94 
   95   # set the environment (from defineProblemEnvir)
   96   warn "PG: setting the environment (from defineProblemEnvir)\n";
   97   $translator->environment(defineProblemEnvir(
   98     $courseEnv,
   99     $user,
  100     $key,
  101     $set,
  102     $problem,
  103     $psvn,
  104     $formFields,
  105     $translationOptions,
  106   ));
  107 
  108   # initialize the Translator
  109   warn "PG: initializing the Translator\n";
  110   $translator->initialize();
  111 
  112   # load PG.pl and dangerousMacros.pl using unrestricted_load
  113   # i'd like to change this at some point to have the same sort of interface to global.conf
  114   # that the module loading does -- have a list of macros to load unrestrictedly.
  115   warn "PG: loading PG.pl and dangerousMacros.pl using unrestricted_load\n";
  116   my $pg_pl = $courseEnv->{webworkDirs}->{macros} . "/PG.pl";
  117   my $dangerousMacros_pl = $courseEnv->{webworkDirs}->{macros} . "/dangerousMacros.pl";
  118   my $err = $translator->unrestricted_load($pg_pl);
  119   warn "Error while loading $pg_pl: $err" if $err;
  120   $err = $translator->unrestricted_load($dangerousMacros_pl);
  121   warn "Error while loading $dangerousMacros_pl: $err" if $err;
  122 
  123   # set the opcode mask (using default values)
  124   warn "PG: setting the opcode mask (using default values)\n";
  125   $translator->set_mask();
  126 
  127   # store the problem source
  128   warn "PG: storing the problem source\n";
  129   my $sourceFile = $problem->source_file;
  130   $sourceFile = $courseEnv->{courseDirs}->{templates}."/".$sourceFile
  131     unless ($sourceFile =~ /^\//);
  132   eval { $translator->source_string(readFile($sourceFile)) };
  133   if ($@) {
  134     # well, we couldn't get the problem source, for some reason.
  135     return bless {
  136       translator => $translator,
  137       head_text  => "",
  138       body_text  => <<EOF,
  139 WeBWorK::Utils::readFile($sourceFile) says:
  140 $@
  141 EOF
  142       answers    => {},
  143       result     => {},
  144       state      => {},
  145       errors     => "Failed to read the problem source file.",
  146       warnings   => undef,
  147       flags      => {error_flag => 1},
  148     }, $class;
  149   }
  150 
  151   # install a safety filter (&safetyFilter)
  152   warn "PG: installing a safety filter\n";
  153   $translator->rf_safety_filter(\&safetyFilter);
  154 
  155   # translate the PG source into text
  156   warn "PG: translating the PG source into text\n";
  157   $translator->translate();
  158 
  159   my ($result, $state); # we'll need these on the other side of the if block!
  160   if ($translationOptions->{processAnswers}) {
  161 
  162     # process student answers
  163     warn "PG: processing student answers\n";
  164     $translator->process_answers($formFields);
  165 
  166     # retrieve the problem state and give it to the translator
  167     warn "PG: retrieving the problem state and giving it to the translator\n";
  168     $translator->rh_problem_state({
  169       recorded_score =>       $problem->status,
  170       num_of_correct_ans =>   $problem->num_correct,
  171       num_of_incorrect_ans => $problem->num_incorrect,
  172     });
  173 
  174     # determine an entry order -- the ANSWER_ENTRY_ORDER flag is built by
  175     # the PG macro package (PG.pl)
  176     warn "PG: determining an entry order\n";
  177     my @answerOrder =
  178       $translator->rh_flags->{ANSWER_ENTRY_ORDER}
  179         ? @{ $translator->rh_flags->{ANSWER_ENTRY_ORDER} }
  180         : keys %{ $translator->rh_evaluated_answers };
  181 
  182     # install a grader -- use the one specified in the problem,
  183     # or fall back on the default from the course environment.
  184     # (two magic strings are accepted, to avoid having to
  185     # reference code when it would be difficult.)
  186     warn "PG: installing a grader\n";
  187     my $grader = $translator->rh_flags->{PROBLEM_GRADER_TO_USE}
  188       || $courseEnv->{pg}->{options}->{grader};
  189     $grader = $translator->rf_std_problem_grader
  190       if $grader eq "std_problem_grader";
  191     $grader = $translator->rf_avg_problem_grader
  192       if $grader eq "avg_problem_grader";
  193     die "Problem grader $grader is not a CODE reference."
  194       unless ref $grader eq "CODE";
  195     $translator->rf_problem_grader($grader);
  196 
  197     # grade the problem
  198     warn "PG: grading the problem\n";
  199     ($result, $state) = $translator->grade_problem(
  200       answers_submitted  => $translationOptions->{processAnswers},
  201       ANSWER_ENTRY_ORDER => \@answerOrder,
  202     );
  203 
  204   }
  205 
  206   # return an object which contains the translator and the results of
  207   # the translation process. this is DIFFERENT from the "format expected
  208   # by Webwork.pm (and I believe processProblem8, but check.)"
  209   return bless {
  210     translator => $translator,
  211     head_text  => ${ $translator->r_header },
  212     body_text  => ${ $translator->r_text   },
  213     answers    => $translator->rh_evaluated_answers,
  214     result     => $result,
  215     state      => $state,
  216     errors     => $translator->errors, # *** what is this doing?
  217     warnings   => undef, # *** gotta catch warnings eventually...
  218     flags      => $translator->rh_flags,
  219   }, $class;
  220 }
  221 
  222 # -----
  223 
  224 sub defineProblemEnvir($$$$$$$) {
  225   my (
  226     $courseEnv,
  227     $user,
  228     $key,
  229     $set,
  230     $problem,
  231     $psvn,
  232     $formFields,
  233     $options,
  234   ) = @_;
  235 
  236   my %envir;
  237 
  238   # PG environment variables
  239   # from docs/pglanguage/pgreference/environmentvariables as of 06/25/2002
  240   # any changes are noted by "ADDED:" or "REMOVED:"
  241 
  242   # Vital state information
  243   # ADDED: displayHintsQ, displaySolutionsQ, refreshMath2img
  244 
  245   $envir{psvn}              = $psvn;
  246   $envir{psvnNumber}        = $envir{psvn};
  247   $envir{probNum}           = $problem->id;
  248   $envir{questionNumber}    = $envir{probNum};
  249   $envir{fileName}          = $problem->source_file;
  250   $envir{probFileName}      = $envir{fileName};
  251   $envir{problemSeed}       = $problem->problem_seed;
  252   $envir{displayMode}       = translateDisplayModeNames($options->{displayMode});
  253   $envir{languageMode}      = $envir{displayMode};
  254   $envir{outputMode}        = $envir{displayMode};
  255   $envir{displayHintsQ}     = $options->{hints};
  256   $envir{displaySolutionsQ} = $options->{solutions};
  257   $envir{refreshMath2img}   = $options->{refreshMath2img};
  258 
  259   # Problem Information
  260   # ADDED: courseName
  261 
  262   $envir{openDate}            = $set->open_date;
  263   $envir{formattedOpenDate}   = formatDateTime($envir{openDate});
  264   $envir{dueDate}             = $set->due_date;
  265   $envir{formattedDueDate}    = formatDateTime($envir{dueDate});
  266   $envir{answerDate}          = $set->answer_date;
  267   $envir{formattedAnswerDate} = formatDateTime($envir{answerDate});
  268   $envir{numOfAttempts}       = $problem->num_correct + $problem->num_incorrect;
  269   $envir{problemValue}        = $problem->value;
  270   $envir{sessionKey}          = $key;
  271   $envir{courseName}          = $courseEnv->{courseName};
  272 
  273   # Student Information
  274   # ADDED: studentID
  275 
  276   $envir{sectionName}      = $user->section;
  277   $envir{sectionNumber}    = $envir{sectionName};
  278   $envir{recitationName}   = $user->recitation;
  279   $envir{recitationNumber} = $envir{recitationName};
  280   $envir{setNumber}        = $set->id;
  281   $envir{studentLogin}     = $user->id;
  282   $envir{studentName}      = $user->first_name . " " . $user->last_name;
  283   $envir{studentID}        = $user->student_id;
  284 
  285   # Answer Information
  286   # REMOVED: refSubmittedAnswers
  287 
  288   $envir{inputs_ref} = $formFields;
  289 
  290   # External Programs
  291   # ADDED: externalLaTeXPath, externalDvipngPath, externalMath2imgPath
  292 
  293   $envir{externalTTHPath}      = $courseEnv->{externalPrograms}->{tth};
  294   $envir{externalLaTeXPath}    = $courseEnv->{externalPrograms}->{latex};
  295   $envir{externalDvipngPath}   = $courseEnv->{externalPrograms}->{dvipng};
  296   $envir{externalMath2imgPath} = $courseEnv->{externalPrograms}->{math2img};
  297 
  298   # Directories and URLs
  299   # REMOVED: courseName
  300   # ADDED: dvipngTempDir
  301 
  302 
  303   $envir{cgiDirectory}           = undef;
  304   $envir{cgiURL}                 = undef;
  305   $envir{classDirectory}         = undef;
  306   $envir{courseScriptsDirectory} = $courseEnv->{webworkDirs}->{macros}."/";
  307   $envir{htmlDirectory}          = $courseEnv->{courseDirs}->{html}."/";
  308   $envir{htmlURL}                = $courseEnv->{courseURLs}->{html};
  309   $envir{macroDirectory}         = $courseEnv->{courseDirs}->{macros}."/";
  310   $envir{templateDirectory}      = $courseEnv->{courseDirs}->{templates}."/";
  311   $envir{tempDirectory}          = $courseEnv->{courseDirs}->{html_temp}."/";
  312   $envir{tempURL}                = $courseEnv->{courseURLs}->{html_temp};
  313   $envir{scriptDirectory}        = undef;
  314   $envir{webworkDocsURL}         = $courseEnv->{webworkURLs}->{docs};
  315   $envir{dvipngTempDir}          = tempdir("webwork-dvipng-XXXXXXXX", TMPDIR => 1);
  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