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

Annotation of /trunk/webwork2/lib/WeBWorK/PG/Local.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3073 - (view) (download) (as text)

1 : gage 1247 ################################################################################
2 : sh002i 1663 # WeBWorK Online Homework Delivery System
3 :     # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4 : gage 3073 # $CVSHeader: webwork-modperl/lib/WeBWorK/PG/Local.pm,v 1.15 2004/10/15 20:33: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 :     use File::Path qw(rmtree);
41 :     use WeBWorK::PG::Translator;
42 : sh002i 1558 use WeBWorK::Utils qw(readFile writeTimingLogEntry);
43 : gage 1247
44 : sh002i 2268 # Problem processing will time out after this number of seconds.
45 :     use constant TIMEOUT => 5*60;
46 :    
47 : sh002i 1558 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 : gage 1249 $WeBWorK::PG::Local::safeCache = new Safe;
53 :     }
54 : sh002i 1558
55 : gage 1247 sub new {
56 :     my $invocant = shift;
57 : sh002i 2268 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 : gage 1247 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 : sh002i 1558 # hints and the display mode to use
79 : gage 1247 ) = @_;
80 :    
81 :     # write timing log entry
82 : gage 2929 # 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 : gage 1247
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 : gage 1249 #warn "PG: creating a Translator\n";
93 : gage 1247 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 : sh002i 1558 my $envir = $class->defineProblemEnvir(
118 : gage 1247 $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 : sh002i 1558
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 : gage 1249
169 : sh002i 1558 # STANDARD LOADING CODE: for cached script files, this merely
170 :     # initializes the constants.
171 :     foreach (qw(PG.pl dangerousMacros.pl IO.pl)) {
172 : gage 1247 my $macroPath = $ce->{pg}->{directories}->{macros} . "/$_";
173 :     my $err = $translator->unrestricted_load($macroPath);
174 : sh002i 1558 warn "Error while loading $macroPath: $err" if $err;
175 : gage 1247 }
176 : sh002i 1558
177 : gage 1247 # 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 : gage 3073 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 : gage 1247 # well, we couldn't get the problem source, for some reason.
211 :     return bless {
212 :     translator => $translator,
213 : gage 1249 head_text => "",
214 : gage 1247 body_text => <<EOF,
215 : gage 3073 WeBWorK::Utils::readFile($sourceFilePath) says:
216 : gage 1247 $@
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 : sh002i 1537 # install a safety filter
228 : gage 1247 #warn "PG: installing a safety filter\n";
229 : sh002i 1537 #$translator->rf_safety_filter(\&oldSafetyFilter);
230 : gage 1563 $translator->rf_safety_filter(\&WeBWorK::PG::nullSafetyFilter);
231 : gage 1247
232 :     # write timing log entry -- the translator is now all set up
233 : gage 2929 # writeTimingLogEntry($ce, "WeBWorK::PG::new",
234 :     # "initialized",
235 :     # "intermediate");
236 : gage 1247
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 : jj 2418 my $body_text_ref = $translator->r_text;
253 : gage 1247 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 : jj 2418 body_text => $body_text_ref,
263 : gage 1247 );
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 : gage 2929 # writeTimingLogEntry($ce, "WeBWorK::PG::new", "", "end");
315 : gage 1247
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 : jj 2418 body_text => ${ $body_text_ref },
323 : gage 1247 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 : sh002i 1558 WeBWorK::PG::Local goes through the following operations when constructed:
339 : gage 1247
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 : sh002i 1558 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 : gage 1247
362 :     =item Initialize the translator
363 :    
364 :     Call &WeBWorK::PG::Translator::initialize. What more do you want?
365 :    
366 : sh002i 1558 =item Load IO.pl, PG.pl and dangerousMacros.pl
367 : gage 1247
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