[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 558 - (download) (as text) (annotate)
Fri Sep 20 22:47:22 2002 UTC (10 years, 8 months ago) by sh002i
Original Path: trunk/webwork2/lib/WeBWorK/PG.pm
File size: 17555 byte(s)
* fixed multiple-calls-to-&handler problem
* fixed if-else-endif code in &template
* added code to catch warnings in PG evaluation
* added "pink screen" and warning reporting
* started work on logging code (see Utils.pm, commented out)
-sam & dennis

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9