[system] / branches / gage_dev / webwork2 / lib / WeBWorK / PG / Local.pm Repository:
ViewVC logotype

Annotation of /branches/gage_dev/webwork2/lib/WeBWorK/PG/Local.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4195 - (view) (download) (as text)
Original Path: trunk/webwork2/lib/WeBWorK/PG/Local.pm

1 : gage 1247 ################################################################################
2 : sh002i 1663 # WeBWorK Online Homework Delivery System
3 : sh002i 3973 # Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/
4 : sh002i 4195 # $CVSHeader: webwork2/lib/WeBWorK/PG/Local.pm,v 1.20 2006/05/21 00:50:04 gage Exp $
5 : sh002i 1663 #
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 : gage 1247 ################################################################################
16 :    
17 :     package WeBWorK::PG::Local;
18 : sh002i 1558 use base qw(WeBWorK::PG);
19 : gage 1247
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 : sh002i 1703 hidden, instead making choices that are appropriate for the webwork2
30 : gage 1247 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 : gage 3311 use WeBWorK::Constants;
41 : gage 1247 use File::Path qw(rmtree);
42 :     use WeBWorK::PG::Translator;
43 : sh002i 1558 use WeBWorK::Utils qw(readFile writeTimingLogEntry);
44 : gage 1247
45 : sh002i 4195 use mod_perl;
46 :     use constant MP2 => ( exists $ENV{MOD_PERL_API_VERSION} and $ENV{MOD_PERL_API_VERSION} >= 2 );
47 :    
48 : sh002i 2268 # Problem processing will time out after this number of seconds.
49 : gage 3311 use constant TIMEOUT => $WeBWorK::PG::Local::TIMEOUT || 10;
50 : sh002i 2268
51 : sh002i 1558 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 : gage 1249 $WeBWorK::PG::Local::safeCache = new Safe;
57 :     }
58 : sh002i 1558
59 : gage 1247 sub new {
60 :     my $invocant = shift;
61 : gage 3311 local $SIG{ALRM} = \&alarm_handler;
62 : sh002i 2268 alarm TIMEOUT;
63 :     my $result = eval { $invocant->new_helper(@_) };
64 :     alarm 0;
65 :     die $@ if $@;
66 :     return $result;
67 :     }
68 :    
69 : gage 3311 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 : sh002i 2268 sub new_helper {
76 :     my $invocant = shift;
77 : gage 1247 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 : sh002i 1558 # hints and the display mode to use
89 : gage 1247 ) = @_;
90 :    
91 :     # write timing log entry
92 : gage 2929 # 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 : gage 1247
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 : gage 1249 #warn "PG: creating a Translator\n";
103 : gage 1247 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 : dpvc 3224 macrosPath => $ce->{courseDirs}->{macrosPath},
109 : gage 1247 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 : sh002i 4195 # HACK for apache2
117 :     if (MP2) {
118 :     push @modules, ["Apache2::Log"], ["APR::Table"];
119 :     } else {
120 :     push @modules, ["Apache::Log"];
121 :     }
122 : gage 1247 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 : sh002i 1558 my $envir = $class->defineProblemEnvir(
133 : gage 1247 $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 : sh002i 1558
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 : gage 1249
184 : sh002i 1558 # STANDARD LOADING CODE: for cached script files, this merely
185 :     # initializes the constants.
186 :     foreach (qw(PG.pl dangerousMacros.pl IO.pl)) {
187 : gage 1247 my $macroPath = $ce->{pg}->{directories}->{macros} . "/$_";
188 :     my $err = $translator->unrestricted_load($macroPath);
189 : sh002i 1558 warn "Error while loading $macroPath: $err" if $err;
190 : gage 1247 }
191 : sh002i 1558
192 : gage 1247 # 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 : gage 3073 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 : gage 1247 # well, we couldn't get the problem source, for some reason.
226 :     return bless {
227 :     translator => $translator,
228 : gage 1249 head_text => "",
229 : gage 1247 body_text => <<EOF,
230 : gage 3073 WeBWorK::Utils::readFile($sourceFilePath) says:
231 : gage 1247 $@
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 : sh002i 1537 # install a safety filter
243 : gage 1247 #warn "PG: installing a safety filter\n";
244 : sh002i 1537 #$translator->rf_safety_filter(\&oldSafetyFilter);
245 : gage 1563 $translator->rf_safety_filter(\&WeBWorK::PG::nullSafetyFilter);
246 : gage 1247
247 :     # write timing log entry -- the translator is now all set up
248 : gage 2929 # writeTimingLogEntry($ce, "WeBWorK::PG::new",
249 :     # "initialized",
250 :     # "intermediate");
251 : gage 1247
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 : jj 2418 my $body_text_ref = $translator->r_text;
268 : gage 1247 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 : jj 2418 body_text => $body_text_ref,
278 : gage 1247 );
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 : gage 4089 %{$formFields}, #FIXME? this is used by sequentialGrader is there a better way?
325 : gage 1247 );
326 :    
327 :     }
328 :    
329 :     # write timing log entry
330 : gage 2929 # writeTimingLogEntry($ce, "WeBWorK::PG::new", "", "end");
331 : gage 1247
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 : jj 2418 body_text => ${ $body_text_ref },
339 : gage 1247 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 : sh002i 1558 WeBWorK::PG::Local goes through the following operations when constructed:
355 : gage 1247
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 : sh002i 1558 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 : gage 1247
378 :     =item Initialize the translator
379 :    
380 :     Call &WeBWorK::PG::Translator::initialize. What more do you want?
381 :    
382 : sh002i 1558 =item Load IO.pl, PG.pl and dangerousMacros.pl
383 : gage 1247
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