[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 492 - (download) (as text) (annotate)
Wed Aug 21 15:34:35 2002 UTC (10 years, 9 months ago) by sh002i
File size: 17147 byte(s)
made progress towards working hardcopy generation. next, i have to fix
the call to pdflatex. screw pip. it's not working right, and it doesn't
give me any speed gain, since latex has to goofily write the WHOLE DAMN
FILE in whatever order it pleases before letting me have it.
-sam

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9