[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 3073 - (download) (as text) (annotate)
Sat Jan 1 22:35:10 2005 UTC (8 years, 4 months ago) by gage
File size: 14011 byte(s)
Changes made to simplify the implementation of XMLRPC access to
the functionality of WeBWorK.

There is still a problem with making sure that the proper warning
mechanism is passed into the problem rendering compartment.
The proper permissions for creating directories also does not
get transmitted properly.

I would like to change the way that the defineEnvirVars subroutine is
passed into the Local.pm object.  I'd like to either pass the
environment as a hash or explicitly pass a reference to define
EnvirVars rather than inherit it from PG.  In particular I'd like
to be able to define environment variables directly in RenderProblem.pm
instead of faking it by defining fake problem objects.

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9