[system] / trunk / webwork-modperl / lib / WeBWorK / PG / Local.pm Repository:
ViewVC logotype

View of /trunk/webwork-modperl/lib/WeBWorK/PG/Local.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1530 - (download) (as text) (annotate)
Thu Sep 25 02:03:43 2003 UTC (9 years, 7 months ago) by sh002i
File size: 19255 byte(s)
removed unneeded arguments from calls to ImageGenerator

    1 ################################################################################
    2 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project
    3 # $Id$
    4 ################################################################################
    5 
    6 package WeBWorK::PG::Local;
    7 
    8 =head1 NAME
    9 
   10 WeBWorK::PG::Local - Use the WeBWorK::PG API to invoke a local
   11 WeBWorK::PG::Translator object.
   12 
   13 =head1 DESCRIPTION
   14 
   15 WeBWorK::PG::Local encapsulates the PG translation process, making multiple
   16 calls to WeBWorK::PG::Translator. Much of the flexibility of the Translator is
   17 hidden, instead making choices that are appropriate for the webwork-modperl
   18 system
   19 
   20 It implements the WeBWorK::PG interface and uses a local
   21 WeBWorK::PG::Translator to perform problem rendering. See the documentation for
   22 the WeBWorK::PG module for information about the API.
   23 
   24 =cut
   25 
   26 use strict;
   27 use warnings;
   28 use File::Path qw(rmtree);
   29 use WeBWorK::PG::ImageGenerator;
   30 use WeBWorK::PG::Translator;
   31 use WeBWorK::Utils qw(readFile formatDateTime writeTimingLogEntry makeTempDirectory);
   32 BEGIN {
   33 
   34 # This safe compartment is used to read the large macro files such as
   35 #   PG.pl, PGbasicmacros.pl and PGanswermacros and cache the results so that
   36 #   future calls have preloaded versions of these large files.
   37 #   This saves a significant amount of time.
   38 
   39   $WeBWorK::PG::Local::safeCache = new Safe;
   40 # warn "Creating new Safe cache compartment ".$WeBWorK::PG::Local::safeCache->root;
   41 }
   42 sub new {
   43   my $invocant = shift;
   44   my $class = ref($invocant) || $invocant;
   45   my (
   46     $ce,
   47     $user,
   48     $key,
   49     $set,
   50     $problem,
   51     $psvn,
   52     $formFields, # in CGI::Vars format
   53     $translationOptions, # hashref containing options for the
   54                          # translator, such as whether to show
   55              # hints and the display mode to use
   56   ) = @_;
   57 
   58   # write timing log entry
   59   writeTimingLogEntry($ce, "WeBWorK::PG::new",
   60     "user=".$user->user_id.",problem=".$ce->{courseName}."/".$set->set_id."/".$problem->problem_id.",mode=".$translationOptions->{displayMode},
   61     "begin");
   62 
   63   # install a local warn handler to collect warnings
   64   my $warnings = "";
   65   local $SIG{__WARN__} = sub { $warnings .= shift }
   66     if $ce->{pg}->{options}->{catchWarnings};
   67 
   68   # create a Translator
   69   #warn "PG: creating a Translator\n";
   70   my $translator = WeBWorK::PG::Translator->new;
   71 
   72   # set the directory hash
   73   #warn "PG: setting the directory hash\n";
   74   $translator->rh_directories({
   75     courseScriptsDirectory => $ce->{pg}->{directories}->{macros},
   76     macroDirectory         => $ce->{courseDirs}->{macros},
   77     templateDirectory      => $ce->{courseDirs}->{templates},
   78     tempDirectory          => $ce->{courseDirs}->{html_temp},
   79   });
   80 
   81   # evaluate modules and "extra packages"
   82   #warn "PG: evaluating modules and \"extra packages\"\n";
   83   my @modules = @{ $ce->{pg}->{modules} };
   84   foreach my $module_packages_ref (@modules) {
   85     my ($module, @extra_packages) = @$module_packages_ref;
   86     # the first item is the main package
   87     $translator->evaluate_modules($module);
   88     # the remaining items are "extra" packages
   89     $translator->load_extra_packages(@extra_packages);
   90   }
   91 
   92   # set the environment (from defineProblemEnvir)
   93   #warn "PG: setting the environment (from defineProblemEnvir)\n";
   94   my $envir = defineProblemEnvir(
   95     $ce,
   96     $user,
   97     $key,
   98     $set,
   99     $problem,
  100     $psvn,
  101     $formFields,
  102     $translationOptions,
  103   );
  104   $translator->environment($envir);
  105 
  106   # initialize the Translator
  107   #warn "PG: initializing the Translator\n";
  108   $translator->initialize();
  109 # $translator->dumpSafe;   # debugging code
  110 ###############################################################################
  111 #   Preload the macros files which are used routinely:  PG.pl, dangerousMacros.pl, IO.pl
  112 #   PGbasicmacros.pl and PGanswermacros.pl
  113 #   Preloading the last two files safes a significant amount of time.
  114 ###############################################################################
  115 
  116   #  IO.pl, PG.pl, and dangerousMacros.pl are loaded using unrestricted_load
  117   # This is hard wired into the Translator::pre_load_macro_files subroutine
  118   # I'd like to change this at some point to have the same sort of interface to global.conf
  119   # that the module loading does -- have a list of macros to load unrestrictedly.
  120 
  121 #    This has been replaced by the pre_load_macro_files subroutine.  It loads AND caches the files.
  122 #   While PG.pl and dangerousMacros are not large, they are referred to by PGbasicmacros and PGanswermacros.
  123 #   Because these are loaded into the cached name space (e.g. Safe::Root1::) all calls to, say NEW_ANSWER_NAME
  124 #   are actually calls to Safe::Root1::NEW_ANSWER_NAME.  It is useful to have these names inside the Safe::Root1:
  125 #   cached safe compartment.  (NEW_ANSWER_NAME and all other subroutine names are also automatically exported into
  126 #   the current safe compartment Safe::Rootx::
  127 
  128 #   The headers of both PGbasicmacros and PGanswermacros has code that insures that the constants used are imported into
  129 #   the current safe compartment.  This involves evaluating references to, say $main::displayMode, at runtime to insure that main
  130 #   refers to Safe::Rootx:: and NOT to Safe::Root1::, which is the value of main:: at compile time.
  131 
  132 
  133 ###############################################################################
  134 #   TO ENABLE CACHEING UNCOMMENT THE CACHEING CODE
  135 #   On webwork3  cached code is .2 seconds faster than non-cached code for an existing child.
  136 
  137 #   CACHING CODE:
  138     eval{
  139   $translator->pre_load_macro_files($WeBWorK::PG::Local::safeCache, $ce->{pg}->{directories}->{macros},
  140       'PG.pl', 'dangerousMacros.pl','IO.pl','PGbasicmacros.pl','PGanswermacros.pl');};
  141     warn "Error while preloading macro files:  $@" if $@;
  142 
  143 #   STANDARD LOADING CODE: for cached script files this merely initializes the constants.
  144   foreach (qw( PG.pl dangerousMacros.pl IO.pl)) {
  145     my $macroPath = $ce->{pg}->{directories}->{macros} . "/$_";
  146     my $err = $translator->unrestricted_load($macroPath);
  147     warn "Error while loading $macroPath: |$err|" if $err;
  148   }
  149 ###############################################################################
  150 
  151   # set the opcode mask (using default values)
  152   #warn "PG: setting the opcode mask (using default values)\n";
  153   $translator->set_mask();
  154 
  155   # store the problem source
  156   #warn "PG: storing the problem source\n";
  157   my $sourceFile = $problem->source_file;
  158   $sourceFile = $ce->{courseDirs}->{templates}."/".$sourceFile
  159     unless ($sourceFile =~ /^\//);
  160   eval { $translator->source_string(readFile($sourceFile)) };
  161   if ($@) {
  162     # well, we couldn't get the problem source, for some reason.
  163     return bless {
  164       translator => $translator,
  165       head_text  => "",
  166       body_text  => <<EOF,
  167 WeBWorK::Utils::readFile($sourceFile) says:
  168 $@
  169 EOF
  170       answers    => {},
  171       result     => {},
  172       state      => {},
  173       errors     => "Failed to read the problem source file.",
  174       warnings   => $warnings,
  175       flags      => {error_flag => 1},
  176     }, $class;
  177   }
  178 
  179   # install a safety filter (&safetyFilter)
  180   #warn "PG: installing a safety filter\n";
  181   $translator->rf_safety_filter(\&safetyFilter);
  182 
  183   # write timing log entry -- the translator is now all set up
  184   writeTimingLogEntry($ce, "WeBWorK::PG::new",
  185     "initialized",
  186     "intermediate");
  187 
  188   # translate the PG source into text
  189   #warn "PG: translating the PG source into text\n";
  190   $translator->translate();
  191 
  192   # after we're done translating, we may have to clean up after the
  193   # translator:
  194 
  195   # for example, HTML_img mode uses a tempdir for dvipng's temp files.\
  196   # We have to remove it.
  197   if ($envir->{dvipngTempDir}) {
  198     rmtree($envir->{dvipngTempDir}, 0, 0);
  199   }
  200 
  201   # HTML_dpng, on the other hand, uses an ImageGenerator. We have to
  202   # render the queued equations.
  203   if ($envir->{imagegen}) {
  204     my $sourceFile = $ce->{courseDirs}->{templates} . "/" . $problem->source_file;
  205     my %mtimeOption = -e $sourceFile
  206       ? (mtime => (stat $sourceFile)[9])
  207       : ();
  208 
  209     $envir->{imagegen}->render(
  210       refresh => $translationOptions->{refreshMath2img},
  211       %mtimeOption,
  212     );
  213   }
  214 
  215   my ($result, $state); # we'll need these on the other side of the if block!
  216   if ($translationOptions->{processAnswers}) {
  217 
  218     # process student answers
  219     #warn "PG: processing student answers\n";
  220     $translator->process_answers($formFields);
  221 
  222     # retrieve the problem state and give it to the translator
  223     #warn "PG: retrieving the problem state and giving it to the translator\n";
  224     $translator->rh_problem_state({
  225       recorded_score =>       $problem->status,
  226       num_of_correct_ans =>   $problem->num_correct,
  227       num_of_incorrect_ans => $problem->num_incorrect,
  228     });
  229 
  230     # determine an entry order -- the ANSWER_ENTRY_ORDER flag is built by
  231     # the PG macro package (PG.pl)
  232     #warn "PG: determining an entry order\n";
  233     my @answerOrder =
  234       $translator->rh_flags->{ANSWER_ENTRY_ORDER}
  235         ? @{ $translator->rh_flags->{ANSWER_ENTRY_ORDER} }
  236         : keys %{ $translator->rh_evaluated_answers };
  237 
  238     # install a grader -- use the one specified in the problem,
  239     # or fall back on the default from the course environment.
  240     # (two magic strings are accepted, to avoid having to
  241     # reference code when it would be difficult.)
  242     #warn "PG: installing a grader\n";
  243     my $grader = $translator->rh_flags->{PROBLEM_GRADER_TO_USE}
  244       || $ce->{pg}->{options}->{grader};
  245     $grader = $translator->rf_std_problem_grader
  246       if $grader eq "std_problem_grader";
  247     $grader = $translator->rf_avg_problem_grader
  248       if $grader eq "avg_problem_grader";
  249     die "Problem grader $grader is not a CODE reference."
  250       unless ref $grader eq "CODE";
  251     $translator->rf_problem_grader($grader);
  252 
  253     # grade the problem
  254     #warn "PG: grading the problem\n";
  255     ($result, $state) = $translator->grade_problem(
  256       answers_submitted  => $translationOptions->{processAnswers},
  257       ANSWER_ENTRY_ORDER => \@answerOrder,
  258     );
  259 
  260   }
  261 
  262   # write timing log entry
  263   writeTimingLogEntry($ce, "WeBWorK::PG::new", "", "end");
  264 
  265   # return an object which contains the translator and the results of
  266   # the translation process. this is DIFFERENT from the "format expected
  267   # by Webwork.pm (and I believe processProblem8, but check.)"
  268   return bless {
  269     translator => $translator,
  270     head_text  => ${ $translator->r_header },
  271     body_text  => ${ $translator->r_text   },
  272     answers    => $translator->rh_evaluated_answers,
  273     result     => $result,
  274     state      => $state,
  275     errors     => $translator->errors,
  276     warnings   => $warnings,
  277     flags      => $translator->rh_flags,
  278   }, $class;
  279 }
  280 
  281 # -----
  282 
  283 sub defineProblemEnvir {
  284   my (
  285     $ce,
  286     $user,
  287     $key,
  288     $set,
  289     $problem,
  290     $psvn,
  291     $formFields,
  292     $options,
  293   ) = @_;
  294 
  295   my %envir;
  296 
  297   # ----------------------------------------------------------------------
  298 
  299   # PG environment variables
  300   # from docs/pglanguage/pgreference/environmentvariables as of 06/25/2002
  301   # any changes are noted by "ADDED:" or "REMOVED:"
  302 
  303   # Vital state information
  304   # ADDED: displayHintsQ, displaySolutionsQ, refreshMath2img,
  305   #        texDisposition
  306 
  307   $envir{psvn}              = $set->psvn;
  308   $envir{psvnNumber}        = $envir{psvn};
  309   $envir{probNum}           = $problem->problem_id;
  310   $envir{questionNumber}    = $envir{probNum};
  311   $envir{fileName}          = $problem->source_file;
  312   $envir{probFileName}      = $envir{fileName};
  313   $envir{problemSeed}       = $problem->problem_seed;
  314   $envir{displayMode}       = translateDisplayModeNames($options->{displayMode});
  315   $envir{languageMode}      = $envir{displayMode};
  316   $envir{outputMode}        = $envir{displayMode};
  317   $envir{displayHintsQ}     = $options->{showHints};
  318   $envir{displaySolutionsQ} = $options->{showSolutions};
  319   # FIXME: this is HTML_img specific
  320   #$envir{refreshMath2img}   = $options->{refreshMath2img};
  321   $envir{texDisposition}    = "pdf"; # in webwork-modperl, we use pdflatex
  322 
  323   # Problem Information
  324   # ADDED: courseName, formatedDueDate
  325 
  326   $envir{openDate}            = $set->open_date;
  327   $envir{formattedOpenDate}   = formatDateTime($envir{openDate});
  328   $envir{dueDate}             = $set->due_date;
  329   $envir{formattedDueDate}    = formatDateTime($envir{dueDate});
  330   $envir{formatedDueDate}     = $envir{formattedDueDate}; # typo in many header files
  331   $envir{answerDate}          = $set->answer_date;
  332   $envir{formattedAnswerDate} = formatDateTime($envir{answerDate});
  333   $envir{numOfAttempts}       = ($problem->num_correct || 0) + ($problem->num_incorrect || 0);
  334   $envir{problemValue}        = $problem->value;
  335   $envir{sessionKey}          = $key;
  336   $envir{courseName}          = $ce->{courseName};
  337 
  338   # Student Information
  339   # ADDED: studentID
  340 
  341   $envir{sectionName}      = $user->section;
  342   $envir{sectionNumber}    = $envir{sectionName};
  343   $envir{recitationName}   = $user->recitation;
  344   $envir{recitationNumber} = $envir{recitationName};
  345   $envir{setNumber}        = $set->set_id;
  346   $envir{studentLogin}     = $user->user_id;
  347   $envir{studentName}      = $user->first_name . " " . $user->last_name;
  348   $envir{studentID}        = $user->student_id;
  349 
  350   # Answer Information
  351   # REMOVED: refSubmittedAnswers
  352 
  353   $envir{inputs_ref} = $formFields;
  354 
  355   # External Programs
  356   # ADDED: externalLaTeXPath, externalDvipngPath,
  357   #        externalGif2EpsPath, externalPng2EpsPath
  358 
  359   $envir{externalTTHPath}      = $ce->{externalPrograms}->{tth};
  360   $envir{externalLaTeXPath}    = $ce->{externalPrograms}->{latex};
  361   $envir{externalDvipngPath}   = $ce->{externalPrograms}->{dvipng};
  362   $envir{externalGif2EpsPath}  = $ce->{externalPrograms}->{gif2eps};
  363   $envir{externalPng2EpsPath}  = $ce->{externalPrograms}->{png2eps};
  364   $envir{externalGif2PngPath}  = $ce->{externalPrograms}->{gif2png};
  365 
  366   # Directories and URLs
  367   # REMOVED: courseName
  368   # ADDED: dvipngTempDir
  369 
  370   $envir{cgiDirectory}           = undef;
  371   $envir{cgiURL}                 = undef;
  372   $envir{classDirectory}         = undef;
  373   $envir{courseScriptsDirectory} = $ce->{pg}->{directories}->{macros}."/";
  374   $envir{htmlDirectory}          = $ce->{courseDirs}->{html}."/";
  375   $envir{htmlURL}                = $ce->{courseURLs}->{html}."/";
  376   $envir{macroDirectory}         = $ce->{courseDirs}->{macros}."/";
  377   $envir{templateDirectory}      = $ce->{courseDirs}->{templates}."/";
  378   $envir{tempDirectory}          = $ce->{courseDirs}->{html_temp}."/";
  379   $envir{tempURL}                = $ce->{courseURLs}->{html_temp}."/";
  380   $envir{scriptDirectory}        = undef;
  381   $envir{webworkDocsURL}         = $ce->{webworkURLs}->{docs}."/";
  382   # FIXME: this is HTML_img mode-specific
  383   #$envir{dvipngTempDir}          = $options->{displayMode} eq 'images'
  384   # ? makeTempDirectory($envir{tempDirectory}, "webwork-dvipng")
  385   # : undef;
  386 
  387   # Information for sending mail
  388 
  389   $envir{mailSmtpServer} = $ce->{mail}->{smtpServer};
  390   $envir{mailSmtpSender} = $ce->{mail}->{smtpSender};
  391   $envir{ALLOW_MAIL_TO}  = $ce->{mail}->{allowedRecipients};
  392 
  393   # Default values for evaluating answers
  394 
  395   my $ansEvalDefaults = $ce->{pg}->{ansEvalDefaults};
  396   $envir{$_} = $ansEvalDefaults->{$_} foreach (keys %$ansEvalDefaults);
  397 
  398   # ----------------------------------------------------------------------
  399 
  400   my $basename = "equation-$envir{psvn}.$envir{probNum}";
  401   $basename .= ".$envir{problemSeed}" if $envir{problemSeed};
  402 
  403   # Object for generating equation images
  404   $envir{imagegen} = WeBWorK::PG::ImageGenerator->new(
  405     tempDir  => $ce->{webworkDirs}->{tmp}, # global temp dir
  406     latex  => $envir{externalLaTeXPath},
  407     dvipng   => $envir{externalDvipngPath},
  408     useCache => 1,
  409     cacheDir => $ce->{webworkDirs}->{equationCache},
  410     cacheURL => $ce->{webworkURLs}->{equationCache},
  411     cacheDB  => $ce->{webworkFiles}->{equationCacheDB},
  412   );
  413 
  414   # Other things...
  415   $envir{QUIZ_PREFIX}              = $options->{QUIZ_PREFIX}; # used by quizzes
  416   $envir{PROBLEM_GRADER_TO_USE}    = $ce->{pg}->{options}->{grader};
  417   $envir{PRINT_FILE_NAMES_FOR}     = $ce->{pg}->{specialPGEnvironmentVars}->{PRINT_FILE_NAMES_FOR};
  418 
  419   # variables for interpreting capa problems.
  420   $envir{CAPA_Tools}               = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_Tools};
  421   $envir{CAPA_MCTools}             = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_MCTools};
  422   $envir{CAPA_Graphics_URL}        = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_Graphics_URL};
  423   $envir{CAPA_GraphicsDirectory}   = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_GraphicsDirectory};
  424 
  425   return \%envir;
  426 }
  427 
  428 sub translateDisplayModeNames($) {
  429   my $name = shift;
  430   return {
  431     tex           => "TeX",
  432     plainText     => "HTML",
  433     formattedText => "HTML_tth",
  434     images        => "HTML_dpng", # "HTML_img",
  435   }->{$name};
  436 }
  437 
  438 sub safetyFilter {
  439   my $answer = shift; # accepts one answer and checks it
  440   my $submittedAnswer = $answer;
  441   $answer = '' unless defined $answer;
  442   my ($errorno);
  443   $answer =~ tr/\000-\037/ /;
  444   # Return if answer field is empty
  445   unless ($answer =~ /\S/) {
  446     #$errorno = "<BR>No answer was submitted.";
  447     $errorno = 0;  ## don't report blank answer as error
  448     return ($answer,$errorno);
  449   }
  450   # replace ^ with **    (for exponentiation)
  451   # $answer =~ s/\^/**/g;
  452   # Return if forbidden characters are found
  453   unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\[\]\(\)\,\|]+$/ )  {
  454     $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c;
  455     $errorno = "<BR>There are forbidden characters in your answer: $submittedAnswer<BR>";
  456     return ($answer,$errorno);
  457   }
  458   $errorno = 0;
  459   return($answer, $errorno);
  460 }
  461 
  462 1;
  463 
  464 __END__
  465 
  466 =head1 OPERATION
  467 
  468 WeBWorK::PG goes through the following operations when constructed:
  469 
  470 =over
  471 
  472 =item Get database information
  473 
  474 Retrieve information about the current user, set, and problem from the
  475 database.
  476 
  477 =item Create a translator
  478 
  479 Instantiate a WeBWorK::PG::Translator object.
  480 
  481 =item Set the directory hash
  482 
  483 Set the translator's directory hash (courseScripts, macros, templates, and temp
  484 directories) from the course environment.
  485 
  486 =item Evaluate PG modules
  487 
  488 Using the module list from the course environment (pg->modules), perform a
  489 "use"-like operation to evaluate modules at runtime.
  490 
  491 =item Set the problem environment
  492 
  493 Use data from the user, set, and problem, as well as the course environemnt and
  494 translation options, to set the problem environment.
  495 
  496 =item Initialize the translator
  497 
  498 Call &WeBWorK::PG::Translator::initialize. What more do you want?
  499 
  500 =item Load PG.pl and dangerousMacros.pl
  501 
  502 These macros must be loaded without opcode masking, so they are loaded here.
  503 
  504 =item Set the opcode mask
  505 
  506 Set the opcode mask to the default specified by WeBWorK::PG::Translator.
  507 
  508 =item Load the problem source
  509 
  510 Give the problem source to the translator.
  511 
  512 =item Install a safety filter
  513 
  514 The safety filter is used to preprocess student input before evaluation. The
  515 default safety filter, &WeBWorK::PG::safetyFilter, is used.
  516 
  517 =item Translate the problem source
  518 
  519 Call &WeBWorK::PG::Translator::translate to render the problem source into the
  520 format given by the display mode.
  521 
  522 =item Process student answers
  523 
  524 Use form field inputs to evaluate student answers.
  525 
  526 =item Load the problem state
  527 
  528 Use values from the database to initialize the problem state, so that the
  529 grader will have a point of reference.
  530 
  531 =item Determine an entry order
  532 
  533 Use the ANSWER_ENTRY_ORDER flag to determine the order of answers in the
  534 problem. This is important for problems with dependancies among parts.
  535 
  536 =item Install a grader
  537 
  538 Use the PROBLEM_GRADER_TO_USE flag, or a default from the course environment,
  539 to install a grader.
  540 
  541 =item Grade the problem
  542 
  543 Use the selected grader to grade the problem.
  544 
  545 =back
  546 
  547 =head1 AUTHOR
  548 
  549 Written by Sam Hathaway, sh002i (at) math.rochester.edu.
  550 
  551 =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9