[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 4195 - (download) (as text) (annotate)
Wed Jul 5 18:24:43 2006 UTC (6 years, 10 months ago) by sh002i
File size: 14385 byte(s)
don't load Apache::Log via global.conf anymore -- special-case it in
WeBWorK::PG::Local instead. this allows for selective loading of
Apache::Log for Apache1 or Apache2::Log and APR::Table for Apache2.

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/PG/Local.pm,v 1.20 2006/05/21 00:50:04 gage Exp $
    5 #
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of either: (a) the GNU General Public License as published by the
    8 # Free Software Foundation; either version 2, or (at your option) any later
    9 # version, or (b) the "Artistic License" which comes with this package.
   10 #
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   14 # Artistic License for more details.
   15 ################################################################################
   16 
   17 package WeBWorK::PG::Local;
   18 use base qw(WeBWorK::PG);
   19 
   20 =head1 NAME
   21 
   22 WeBWorK::PG::Local - Use the WeBWorK::PG API to invoke a local
   23 WeBWorK::PG::Translator object.
   24 
   25 =head1 DESCRIPTION
   26 
   27 WeBWorK::PG::Local encapsulates the PG translation process, making multiple
   28 calls to WeBWorK::PG::Translator. Much of the flexibility of the Translator is
   29 hidden, instead making choices that are appropriate for the webwork2
   30 system
   31 
   32 It implements the WeBWorK::PG interface and uses a local
   33 WeBWorK::PG::Translator to perform problem rendering. See the documentation for
   34 the WeBWorK::PG module for information about the API.
   35 
   36 =cut
   37 
   38 use strict;
   39 use warnings;
   40 use WeBWorK::Constants;
   41 use File::Path qw(rmtree);
   42 use WeBWorK::PG::Translator;
   43 use WeBWorK::Utils qw(readFile writeTimingLogEntry);
   44 
   45 use mod_perl;
   46 use constant MP2 => ( exists $ENV{MOD_PERL_API_VERSION} and $ENV{MOD_PERL_API_VERSION} >= 2 );
   47 
   48 # Problem processing will time out after this number of seconds.
   49 use constant TIMEOUT => $WeBWorK::PG::Local::TIMEOUT || 10;
   50 
   51 BEGIN {
   52   # This safe compartment is used to read the large macro files such as
   53   # PG.pl, PGbasicmacros.pl and PGanswermacros and cache the results so that
   54   # future calls have preloaded versions of these large files. This saves a
   55   # significant amount of time.
   56   $WeBWorK::PG::Local::safeCache = new Safe;
   57 }
   58 
   59 sub new {
   60   my $invocant = shift;
   61   local $SIG{ALRM} = \&alarm_handler;
   62   alarm TIMEOUT;
   63   my $result = eval { $invocant->new_helper(@_) };
   64   alarm 0;
   65   die $@ if $@;
   66   return $result;
   67 }
   68 
   69 sub alarm_handler {
   70   my $msg = "Timeout after processing this problem for ". TIMEOUT. " seconds. Check for infinite loops in problem source.\n";
   71   warn $msg;
   72   die $msg;
   73 
   74 }
   75 sub new_helper {
   76   my $invocant = shift;
   77   my $class = ref($invocant) || $invocant;
   78   my (
   79     $ce,
   80     $user,
   81     $key,
   82     $set,
   83     $problem,
   84     $psvn,
   85     $formFields, # in CGI::Vars format
   86     $translationOptions, # hashref containing options for the
   87                          # translator, such as whether to show
   88                          # hints and the display mode to use
   89   ) = @_;
   90 
   91   # write timing log entry
   92 #   writeTimingLogEntry($ce, "WeBWorK::PG::new",
   93 #     "user=".$user->user_id.",problem=".$ce->{courseName}."/".$set->set_id."/".$problem->problem_id.",mode=".$translationOptions->{displayMode},
   94 #     "begin");
   95 
   96   # install a local warn handler to collect warnings
   97   my $warnings = "";
   98   local $SIG{__WARN__} = sub { $warnings .= shift }
   99     if $ce->{pg}->{options}->{catchWarnings};
  100 
  101   # create a Translator
  102   #warn "PG: creating a Translator\n";
  103   my $translator = WeBWorK::PG::Translator->new;
  104 
  105   # set the directory hash
  106   #warn "PG: setting the directory hash\n";
  107   $translator->rh_directories({
  108     macrosPath             => $ce->{courseDirs}->{macrosPath},
  109     templateDirectory      => $ce->{courseDirs}->{templates},
  110     tempDirectory          => $ce->{courseDirs}->{html_temp},
  111   });
  112 
  113   # evaluate modules and "extra packages"
  114   #warn "PG: evaluating modules and \"extra packages\"\n";
  115   my @modules = @{ $ce->{pg}->{modules} };
  116   # HACK for apache2
  117   if (MP2) {
  118     push @modules, ["Apache2::Log"], ["APR::Table"];
  119   } else {
  120     push @modules, ["Apache::Log"];
  121   }
  122   foreach my $module_packages_ref (@modules) {
  123     my ($module, @extra_packages) = @$module_packages_ref;
  124     # the first item is the main package
  125     $translator->evaluate_modules($module);
  126     # the remaining items are "extra" packages
  127     $translator->load_extra_packages(@extra_packages);
  128   }
  129 
  130   # set the environment (from defineProblemEnvir)
  131   #warn "PG: setting the environment (from defineProblemEnvir)\n";
  132   my $envir = $class->defineProblemEnvir(
  133     $ce,
  134     $user,
  135     $key,
  136     $set,
  137     $problem,
  138     $psvn,
  139     $formFields,
  140     $translationOptions,
  141   );
  142   $translator->environment($envir);
  143 
  144   # initialize the Translator
  145   #warn "PG: initializing the Translator\n";
  146   $translator->initialize();
  147 
  148   # Preload the macros files which are used routinely: PG.pl,
  149   # dangerousMacros.pl, IO.pl, PGbasicmacros.pl, and PGanswermacros.pl
  150   # (Preloading the last two files safes a significant amount of time.)
  151   #
  152   # IO.pl, PG.pl, and dangerousMacros.pl are loaded using
  153   # unrestricted_load This is hard wired into the
  154   # Translator::pre_load_macro_files subroutine. I'd like to change this
  155   # at some point to have the same sort of interface to global.conf that
  156   # the module loading does -- have a list of macros to load
  157   # unrestrictedly.
  158   #
  159   # This has been replaced by the pre_load_macro_files subroutine.  It
  160   # loads AND caches the files. While PG.pl and dangerousMacros are not
  161   # large, they are referred to by PGbasicmacros and PGanswermacros.
  162   # Because these are loaded into the cached name space (e.g.
  163   # Safe::Root1::) all calls to, say NEW_ANSWER_NAME are actually calls
  164   # to Safe::Root1::NEW_ANSWER_NAME.  It is useful to have these names
  165   # inside the Safe::Root1: cached safe compartment.  (NEW_ANSWER_NAME
  166   # and all other subroutine names are also automatically exported into
  167   # the current safe compartment Safe::Rootx::
  168   #
  169   # The headers of both PGbasicmacros and PGanswermacros has code that
  170   # insures that the constants used are imported into the current safe
  171   # compartment.  This involves evaluating references to, say
  172   # $main::displayMode, at runtime to insure that main refers to
  173   # Safe::Rootx:: and NOT to Safe::Root1::, which is the value of main::
  174   # at compile time.
  175   #
  176   # TO ENABLE CACHEING UNCOMMENT THE FOLLOWING:
  177   eval{$translator->pre_load_macro_files(
  178     $WeBWorK::PG::Local::safeCache,
  179     $ce->{pg}->{directories}->{macros},
  180     'PG.pl', 'dangerousMacros.pl','IO.pl','PGbasicmacros.pl','PGanswermacros.pl'
  181   )};
  182     warn "Error while preloading macro files: $@" if $@;
  183 
  184   # STANDARD LOADING CODE: for cached script files, this merely
  185   # initializes the constants.
  186   foreach (qw(PG.pl dangerousMacros.pl IO.pl)) {
  187     my $macroPath = $ce->{pg}->{directories}->{macros} . "/$_";
  188     my $err = $translator->unrestricted_load($macroPath);
  189     warn "Error while loading $macroPath: $err" if $err;
  190   }
  191 
  192   # set the opcode mask (using default values)
  193   #warn "PG: setting the opcode mask (using default values)\n";
  194   $translator->set_mask();
  195 
  196   # store the problem source
  197   #warn "PG: storing the problem source\n";
  198   my $source ='';
  199   my $sourceFilePath = '';
  200   my $readErrors = undef;
  201   if (ref($translationOptions->{r_source}) ) {
  202     # the source for the problem is already given to us as a reference to a string
  203     $source = ${$translationOptions->{r_source}};
  204   } else {
  205       # the source isn't given to us so we need to read it
  206       # from a file defined by the problem
  207 
  208     # we  grab the sourceFilePath from the problem
  209     $sourceFilePath = $problem->source_file;
  210 
  211       # the path to the source file is usually given relative to the
  212       # the templates directory. Unless the path starts with / assume
  213       # that it is relative to the templates directory
  214 
  215       $sourceFilePath = $ce->{courseDirs}->{templates}."/"
  216                  .$sourceFilePath unless ($sourceFilePath =~ /^\//);
  217       #now grab the source
  218     eval {$source = readFile($sourceFilePath) };
  219     $readErrors = $@ if $@;
  220    }
  221     # put the source into the translator object
  222   eval { $translator->source_string( $source ) } unless $readErrors;
  223   $readErrors .="\n  $@ " if $@;
  224   if ($readErrors) {
  225     # well, we couldn't get the problem source, for some reason.
  226     return bless {
  227       translator => $translator,
  228       head_text  => "",
  229       body_text  => <<EOF,
  230 WeBWorK::Utils::readFile($sourceFilePath) says:
  231 $@
  232 EOF
  233       answers    => {},
  234       result     => {},
  235       state      => {},
  236       errors     => "Failed to read the problem source file.",
  237       warnings   => $warnings,
  238       flags      => {error_flag => 1},
  239     }, $class;
  240   }
  241 
  242   # install a safety filter
  243   #warn "PG: installing a safety filter\n";
  244   #$translator->rf_safety_filter(\&oldSafetyFilter);
  245   $translator->rf_safety_filter(\&WeBWorK::PG::nullSafetyFilter);
  246 
  247   # write timing log entry -- the translator is now all set up
  248 #   writeTimingLogEntry($ce, "WeBWorK::PG::new",
  249 #     "initialized",
  250 #     "intermediate");
  251 
  252   # translate the PG source into text
  253   #warn "PG: translating the PG source into text\n";
  254   $translator->translate();
  255 
  256   # after we're done translating, we may have to clean up after the
  257   # translator:
  258 
  259   # for example, HTML_img mode uses a tempdir for dvipng's temp files.\
  260   # We have to remove it.
  261   if ($envir->{dvipngTempDir}) {
  262     rmtree($envir->{dvipngTempDir}, 0, 0);
  263   }
  264 
  265   # HTML_dpng, on the other hand, uses an ImageGenerator. We have to
  266   # render the queued equations.
  267   my $body_text_ref  = $translator->r_text;
  268   if ($envir->{imagegen}) {
  269     my $sourceFile = $ce->{courseDirs}->{templates} . "/" . $problem->source_file;
  270     my %mtimeOption = -e $sourceFile
  271       ? (mtime => (stat $sourceFile)[9])
  272       : ();
  273 
  274     $envir->{imagegen}->render(
  275       refresh => $translationOptions->{refreshMath2img},
  276       %mtimeOption,
  277       body_text => $body_text_ref,
  278     );
  279   }
  280 
  281   my ($result, $state); # we'll need these on the other side of the if block!
  282   if ($translationOptions->{processAnswers}) {
  283 
  284     # process student answers
  285     #warn "PG: processing student answers\n";
  286     $translator->process_answers($formFields);
  287 
  288     # retrieve the problem state and give it to the translator
  289     #warn "PG: retrieving the problem state and giving it to the translator\n";
  290     $translator->rh_problem_state({
  291       recorded_score =>       $problem->status,
  292       num_of_correct_ans =>   $problem->num_correct,
  293       num_of_incorrect_ans => $problem->num_incorrect,
  294     });
  295 
  296     # determine an entry order -- the ANSWER_ENTRY_ORDER flag is built by
  297     # the PG macro package (PG.pl)
  298     #warn "PG: determining an entry order\n";
  299     my @answerOrder =
  300       $translator->rh_flags->{ANSWER_ENTRY_ORDER}
  301         ? @{ $translator->rh_flags->{ANSWER_ENTRY_ORDER} }
  302         : keys %{ $translator->rh_evaluated_answers };
  303 
  304     # install a grader -- use the one specified in the problem,
  305     # or fall back on the default from the course environment.
  306     # (two magic strings are accepted, to avoid having to
  307     # reference code when it would be difficult.)
  308     #warn "PG: installing a grader\n";
  309     my $grader = $translator->rh_flags->{PROBLEM_GRADER_TO_USE}
  310       || $ce->{pg}->{options}->{grader};
  311     $grader = $translator->rf_std_problem_grader
  312       if $grader eq "std_problem_grader";
  313     $grader = $translator->rf_avg_problem_grader
  314       if $grader eq "avg_problem_grader";
  315     die "Problem grader $grader is not a CODE reference."
  316       unless ref $grader eq "CODE";
  317     $translator->rf_problem_grader($grader);
  318 
  319     # grade the problem
  320     #warn "PG: grading the problem\n";
  321     ($result, $state) = $translator->grade_problem(
  322       answers_submitted  => $translationOptions->{processAnswers},
  323       ANSWER_ENTRY_ORDER => \@answerOrder,
  324       %{$formFields},  #FIXME?  this is used by sequentialGrader is there a better way?
  325     );
  326 
  327   }
  328 
  329   # write timing log entry
  330 #   writeTimingLogEntry($ce, "WeBWorK::PG::new", "", "end");
  331 
  332   # return an object which contains the translator and the results of
  333   # the translation process. this is DIFFERENT from the "format expected
  334   # by Webwork.pm (and I believe processProblem8, but check.)"
  335   return bless {
  336     translator => $translator,
  337     head_text  => ${ $translator->r_header },
  338     body_text  => ${ $body_text_ref },
  339     answers    => $translator->rh_evaluated_answers,
  340     result     => $result,
  341     state      => $state,
  342     errors     => $translator->errors,
  343     warnings   => $warnings,
  344     flags      => $translator->rh_flags,
  345   }, $class;
  346 }
  347 
  348 1;
  349 
  350 __END__
  351 
  352 =head1 OPERATION
  353 
  354 WeBWorK::PG::Local goes through the following operations when constructed:
  355 
  356 =over
  357 
  358 =item Create a translator
  359 
  360 Instantiate a WeBWorK::PG::Translator object.
  361 
  362 =item Set the directory hash
  363 
  364 Set the translator's directory hash (courseScripts, macros, templates, and temp
  365 directories) from the course environment.
  366 
  367 =item Evaluate PG modules
  368 
  369 Using the module list from the course environment (pg->modules), perform a
  370 "use"-like operation to evaluate modules at runtime.
  371 
  372 =item Set the problem environment
  373 
  374 Use data from the user, set, and problem, as well as the course
  375 environemnt and translation options, to set the problem environment. The
  376 default subroutine, &WeBWorK::PG::defineProblemEnvir, is used.
  377 
  378 =item Initialize the translator
  379 
  380 Call &WeBWorK::PG::Translator::initialize. What more do you want?
  381 
  382 =item Load IO.pl, PG.pl and dangerousMacros.pl
  383 
  384 These macros must be loaded without opcode masking, so they are loaded here.
  385 
  386 =item Set the opcode mask
  387 
  388 Set the opcode mask to the default specified by WeBWorK::PG::Translator.
  389 
  390 =item Load the problem source
  391 
  392 Give the problem source to the translator.
  393 
  394 =item Install a safety filter
  395 
  396 The safety filter is used to preprocess student input before evaluation. The
  397 default safety filter, &WeBWorK::PG::safetyFilter, is used.
  398 
  399 =item Translate the problem source
  400 
  401 Call &WeBWorK::PG::Translator::translate to render the problem source into the
  402 format given by the display mode.
  403 
  404 =item Process student answers
  405 
  406 Use form field inputs to evaluate student answers.
  407 
  408 =item Load the problem state
  409 
  410 Use values from the database to initialize the problem state, so that the
  411 grader will have a point of reference.
  412 
  413 =item Determine an entry order
  414 
  415 Use the ANSWER_ENTRY_ORDER flag to determine the order of answers in the
  416 problem. This is important for problems with dependancies among parts.
  417 
  418 =item Install a grader
  419 
  420 Use the PROBLEM_GRADER_TO_USE flag, or a default from the course environment,
  421 to install a grader.
  422 
  423 =item Grade the problem
  424 
  425 Use the selected grader to grade the problem.
  426 
  427 =back
  428 
  429 =head1 AUTHOR
  430 
  431 Written by Sam Hathaway, sh002i (at) math.rochester.edu.
  432 
  433 =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9