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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9