[system] / branches / rel-2-4-patches / webwork2 / lib / WeBWorK / PG.pm Repository:
ViewVC logotype

Diff of /branches/rel-2-4-patches/webwork2/lib/WeBWorK/PG.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

Revision 623 Revision 1244
5 5
6package WeBWorK::PG; 6package WeBWorK::PG;
7 7
8=head1 NAME 8=head1 NAME
9 9
10WeBWorK::PG - Wrap the action of the PG Translator in an easy-to-use API. 10WeBWorK::PG - Invoke one of several PG rendering methods using an easy-to-use
11API.
11 12
12=cut 13=cut
13 14
14use strict; 15use strict;
15use warnings; 16use warnings;
16use File::Path qw(rmtree); 17use WeBWorK::Utils qw(runtime_use);
17use File::Temp qw(tempdir);
18use WeBWorK::DB::Classlist;
19use WeBWorK::DB::WW;
20use WeBWorK::PG::Translator;
21use WeBWorK::Problem;
22use WeBWorK::Utils qw(readFile formatDateTime writeTimingLogEntry);
23 18
24sub new($$$$$$$$) { 19sub new {
25 my $invocant = shift; 20 shift; # throw away invocant -- we don't need it
26 my $class = ref($invocant) || $invocant; 21 my ($ce, $user, $key, $set, $problem, $psvn, $formFields,
27 my ( 22 $translationOptions) = @_;
28 $courseEnv,
29 $user,
30 $key,
31 $set,
32 $problem,
33 $psvn,
34 $formFields, # in CGI::Vars format
35 $translationOptions, # hashref containing options for the
36 # translator, such as whether to show
37 # hints and the display mode to use
38 ) = @_;
39 23
40 # write timing log entry 24 my $renderer = $ce->{pg}->{renderer};
41 writeTimingLogEntry($courseEnv, "WeBWorK::PG::new",
42 "user=".$user->id.",problem=".$courseEnv->{courseName}."/".$set->id."/".$problem->id.",mode=".$translationOptions->{displayMode},
43 "begin");
44 25
45 # install a local warn handler to collect warnings 26 runtime_use $renderer;
46 my $warnings = "";
47 if ($courseEnv->{pg}->{options}->{catchWarnings}) {
48 local $SIG{__WARN__} = sub { $warnings .= shift };
49 }
50 27
51 # create a Translator 28 return $renderer->new(@_);
52 #warn "PG: creating a Translator\n";
53 my $translator = WeBWorK::PG::Translator->new;
54
55 # set the directory hash
56 #warn "PG: setting the directory hash\n";
57 $translator->rh_directories({
58 courseScriptsDirectory => $courseEnv->{webworkDirs}->{macros},
59 macroDirectory => $courseEnv->{courseDirs}->{macros},
60 templateDirectory => $courseEnv->{courseDirs}->{templates},
61 tempDirectory => $courseEnv->{courseDirs}->{html_temp},
62 });
63
64 # evaluate modules and "extra packages"
65 #warn "PG: evaluating modules and \"extra packages\"\n";
66 my @modules = @{ $courseEnv->{pg}->{modules} };
67 foreach my $module_packages_ref (@modules) {
68 my ($module, @extra_packages) = @$module_packages_ref;
69 # the first item is the main package
70 $translator->evaluate_modules($module);
71 # the remaining items are "extra" packages
72 $translator->load_extra_packages(@extra_packages);
73 }
74
75 # set the environment (from defineProblemEnvir)
76 #warn "PG: setting the environment (from defineProblemEnvir)\n";
77 my $envir = defineProblemEnvir(
78 $courseEnv,
79 $user,
80 $key,
81 $set,
82 $problem,
83 $psvn,
84 $formFields,
85 $translationOptions,
86 );
87 $translator->environment($envir);
88
89 # initialize the Translator
90 #warn "PG: initializing the Translator\n";
91 $translator->initialize();
92
93 # load IO.pl, PG.pl, and dangerousMacros.pl using unrestricted_load
94 # i'd like to change this at some point to have the same sort of interface to global.conf
95 # that the module loading does -- have a list of macros to load unrestrictedly.
96 #warn "PG: loading IO.pl, PG.pl, and dangerousMacros.pl using unrestricted_load\n";
97 foreach (qw(IO.pl PG.pl dangerousMacros.pl)) {
98 my $macroPath = $courseEnv->{webworkDirs}->{macros} . "/$_";
99 my $err = $translator->unrestricted_load($macroPath);
100 warn "Error while loading $macroPath: $err" if $err;
101 }
102 #my $pg_pl = $courseEnv->{webworkDirs}->{macros} . "/PG.pl";
103 #my $dangerousMacros_pl = $courseEnv->{webworkDirs}->{macros} . "/dangerousMacros.pl";
104 #my $io_pl = $courseEnv->{webworkDirs}->{macros} . "/IO.pl";
105 #my $err = $translator->unrestricted_load($pg_pl);
106 #warn "Error while loading $pg_pl: $err" if $err;
107 #$err = $translator->unrestricted_load($dangerousMacros_pl);
108 #warn "Error while loading $dangerousMacros_pl: $err" if $err;
109 #$err = $translator->unrestricted_load($io_pl);
110 #warn "Error while loading $io_pl: $err" if $err;
111
112 # set the opcode mask (using default values)
113 #warn "PG: setting the opcode mask (using default values)\n";
114 $translator->set_mask();
115
116 # store the problem source
117 #warn "PG: storing the problem source\n";
118 my $sourceFile = $problem->source_file;
119 $sourceFile = $courseEnv->{courseDirs}->{templates}."/".$sourceFile
120 unless ($sourceFile =~ /^\//);
121 eval { $translator->source_string(readFile($sourceFile)) };
122 if ($@) {
123 # well, we couldn't get the problem source, for some reason.
124 return bless {
125 translator => $translator,
126 head_text => "",
127 body_text => <<EOF,
128WeBWorK::Utils::readFile($sourceFile) says:
129$@
130EOF
131 answers => {},
132 result => {},
133 state => {},
134 errors => "Failed to read the problem source file.",
135 warnings => $warnings,
136 flags => {error_flag => 1},
137 }, $class;
138 }
139
140 # install a safety filter (&safetyFilter)
141 #warn "PG: installing a safety filter\n";
142 $translator->rf_safety_filter(\&safetyFilter);
143
144 # translate the PG source into text
145 #warn "PG: translating the PG source into text\n";
146 $translator->translate();
147
148 # after we're done translating, we may have to clean up after the translator.
149 # for example, 'images' mode uses a tempdir for dvipng's temp files. We have
150 # to remove it.
151 if ($translationOptions->{displayMode} eq 'images' && $envir->{dvipngTempDir}) {
152 rmtree($envir->{dvipngTempDir}, 0, 0);
153 }
154
155 my ($result, $state); # we'll need these on the other side of the if block!
156 if ($translationOptions->{processAnswers}) {
157
158 # process student answers
159 #warn "PG: processing student answers\n";
160 $translator->process_answers($formFields);
161
162 # retrieve the problem state and give it to the translator
163 #warn "PG: retrieving the problem state and giving it to the translator\n";
164 $translator->rh_problem_state({
165 recorded_score => $problem->status,
166 num_of_correct_ans => $problem->num_correct,
167 num_of_incorrect_ans => $problem->num_incorrect,
168 });
169
170 # determine an entry order -- the ANSWER_ENTRY_ORDER flag is built by
171 # the PG macro package (PG.pl)
172 #warn "PG: determining an entry order\n";
173 my @answerOrder =
174 $translator->rh_flags->{ANSWER_ENTRY_ORDER}
175 ? @{ $translator->rh_flags->{ANSWER_ENTRY_ORDER} }
176 : keys %{ $translator->rh_evaluated_answers };
177
178 # install a grader -- use the one specified in the problem,
179 # or fall back on the default from the course environment.
180 # (two magic strings are accepted, to avoid having to
181 # reference code when it would be difficult.)
182 #warn "PG: installing a grader\n";
183 my $grader = $translator->rh_flags->{PROBLEM_GRADER_TO_USE}
184 || $courseEnv->{pg}->{options}->{grader};
185 $grader = $translator->rf_std_problem_grader
186 if $grader eq "std_problem_grader";
187 $grader = $translator->rf_avg_problem_grader
188 if $grader eq "avg_problem_grader";
189 die "Problem grader $grader is not a CODE reference."
190 unless ref $grader eq "CODE";
191 $translator->rf_problem_grader($grader);
192
193 # grade the problem
194 #warn "PG: grading the problem\n";
195 ($result, $state) = $translator->grade_problem(
196 answers_submitted => $translationOptions->{processAnswers},
197 ANSWER_ENTRY_ORDER => \@answerOrder,
198 );
199
200 }
201
202 # write timing log entry
203 writeTimingLogEntry($courseEnv, "WeBWorK::PG::new", "", "end");
204
205 # return an object which contains the translator and the results of
206 # the translation process. this is DIFFERENT from the "format expected
207 # by Webwork.pm (and I believe processProblem8, but check.)"
208 return bless {
209 translator => $translator,
210 head_text => ${ $translator->r_header },
211 body_text => ${ $translator->r_text },
212 answers => $translator->rh_evaluated_answers,
213 result => $result,
214 state => $state,
215 errors => $translator->errors,
216 warnings => $warnings,
217 flags => $translator->rh_flags,
218 }, $class;
219} 29}
220 30
221# -----
222
223sub defineProblemEnvir($$$$$$$) {
224 my (
225 $courseEnv,
226 $user,
227 $key,
228 $set,
229 $problem,
230 $psvn,
231 $formFields,
232 $options,
233 ) = @_;
234
235 my %envir;
236
237 # PG environment variables
238 # from docs/pglanguage/pgreference/environmentvariables as of 06/25/2002
239 # any changes are noted by "ADDED:" or "REMOVED:"
240
241 # Vital state information
242 # ADDED: displayHintsQ, displaySolutionsQ, refreshMath2img,
243 # texDisposition
244
245 $envir{psvn} = $psvn;
246 $envir{psvnNumber} = $envir{psvn};
247 $envir{probNum} = $problem->id;
248 $envir{questionNumber} = $envir{probNum};
249 $envir{fileName} = $problem->source_file;
250 $envir{probFileName} = $envir{fileName};
251 $envir{problemSeed} = $problem->problem_seed;
252 $envir{displayMode} = translateDisplayModeNames($options->{displayMode});
253 $envir{languageMode} = $envir{displayMode};
254 $envir{outputMode} = $envir{displayMode};
255 $envir{displayHintsQ} = $options->{hints};
256 $envir{displaySolutionsQ} = $options->{solutions};
257 $envir{refreshMath2img} = $options->{refreshMath2img};
258 $envir{texDisposition} = "pdf"; # in webwork-modperl, we use pdflatex
259
260 # Problem Information
261 # ADDED: courseName
262
263 $envir{openDate} = $set->open_date;
264 $envir{formattedOpenDate} = formatDateTime($envir{openDate});
265 $envir{dueDate} = $set->due_date;
266 $envir{formattedDueDate} = formatDateTime($envir{dueDate});
267 $envir{answerDate} = $set->answer_date;
268 $envir{formattedAnswerDate} = formatDateTime($envir{answerDate});
269 $envir{numOfAttempts} = ($problem->num_correct || 0) + ($problem->num_incorrect || 0);
270 $envir{problemValue} = $problem->value;
271 $envir{sessionKey} = $key;
272 $envir{courseName} = $courseEnv->{courseName};
273
274 # Student Information
275 # ADDED: studentID
276
277 $envir{sectionName} = $user->section;
278 $envir{sectionNumber} = $envir{sectionName};
279 $envir{recitationName} = $user->recitation;
280 $envir{recitationNumber} = $envir{recitationName};
281 $envir{setNumber} = $set->id;
282 $envir{studentLogin} = $user->id;
283 $envir{studentName} = $user->first_name . " " . $user->last_name;
284 $envir{studentID} = $user->student_id;
285
286 # Answer Information
287 # REMOVED: refSubmittedAnswers
288
289 $envir{inputs_ref} = $formFields;
290
291 # External Programs
292 # ADDED: externalLaTeXPath, externalDvipngPath,
293 # externalGif2EpsPath, externalPng2EpsPath
294
295 $envir{externalTTHPath} = $courseEnv->{externalPrograms}->{tth};
296 $envir{externalLaTeXPath} = $courseEnv->{externalPrograms}->{latex};
297 $envir{externalDvipngPath} = $courseEnv->{externalPrograms}->{dvipng};
298 $envir{externalGif2EpsPath} = $courseEnv->{externalPrograms}->{gif2eps};
299 $envir{externalPng2EpsPath} = $courseEnv->{externalPrograms}->{png2eps};
300 $envir{externalGif2PngPath} = $courseEnv->{externalPrograms}->{gif2png};
301
302 # Directories and URLs
303 # REMOVED: courseName
304 # ADDED: dvipngTempDir
305
306 $envir{cgiDirectory} = undef;
307 $envir{cgiURL} = undef;
308 $envir{classDirectory} = undef;
309 $envir{courseScriptsDirectory} = $courseEnv->{webworkDirs}->{macros}."/";
310 $envir{htmlDirectory} = $courseEnv->{courseDirs}->{html}."/";
311 $envir{htmlURL} = $courseEnv->{courseURLs}->{html}."/";
312 $envir{macroDirectory} = $courseEnv->{courseDirs}->{macros}."/";
313 $envir{templateDirectory} = $courseEnv->{courseDirs}->{templates}."/";
314 $envir{tempDirectory} = $courseEnv->{courseDirs}->{html_temp}."/";
315 $envir{tempURL} = $courseEnv->{courseURLs}->{html_temp}."/";
316 $envir{scriptDirectory} = undef;
317 $envir{webworkDocsURL} = $courseEnv->{webworkURLs}->{docs}."/";
318 $envir{dvipngTempDir} = $options->{displayMode} eq 'images'
319 ? tempdir("webwork-dvipng-XXXXXXXX", DIR => $envir{tempDirectory})
320 : undef;
321
322 # Information for sending mail
323
324 $envir{mailSmtpServer} = $courseEnv->{mail}->{smtpServer};
325 $envir{mailSmtpSender} = $courseEnv->{mail}->{smtpSender};
326
327 # Default values for evaluating answers
328
329 my $ansEvalDefaults = $courseEnv->{pg}->{ansEvalDefaults};
330 $envir{$_} = $ansEvalDefaults->{$_} foreach (keys %$ansEvalDefaults);
331
332 # Other things...
333
334 $envir{PROBLEM_GRADER_TO_USE} = $courseEnv->{pg}->{options}->{grader};
335
336 return \%envir;
337}
338
339sub translateDisplayModeNames($) {
340 my $name = shift;
341 return {
342 tex => "TeX",
343 plainText => "HTML",
344 formattedText => "HTML_tth",
345 images => "HTML_img"
346 }->{$name};
347}
348
349sub safetyFilter {
350 my $answer = shift; # accepts one answer and checks it
351 my $submittedAnswer = $answer;
352 $answer = '' unless defined $answer;
353 my ($errorno);
354 $answer =~ tr/\000-\037/ /;
355 # Return if answer field is empty
356 unless ($answer =~ /\S/) {
357 #$errorno = "<BR>No answer was submitted.";
358 $errorno = 0; ## don't report blank answer as error
359 return ($answer,$errorno);
360 }
361 # replace ^ with ** (for exponentiation)
362 # $answer =~ s/\^/**/g;
363 # Return if forbidden characters are found
364 unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)]+$/ ) {
365 $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c;
366 $errorno = "<BR>There are forbidden characters in your answer: $submittedAnswer<BR>";
367 return ($answer,$errorno);
368 }
369 $errorno = 0;
370 return($answer, $errorno);
371}
372
3731; 311;
374 32
375__END__ 33__END__
376 34
377=head1 SYNOPSIS 35=head1 SYNOPSIS
378 36
379 $pg = WeBWorK::PG->new( 37 $pg = WeBWorK::PG->new(
380 $courseEnv, # a WeBWorK::CourseEnvironment object 38 $ce, # a WeBWorK::CourseEnvironment object
381 $user, # a WeBWorK::User object 39 $user, # a WeBWorK::DB::Record::User object
382 $sessionKey, 40 $sessionKey,
383 $set, # a WeBWorK::Set object 41 $set, # a WeBWorK::DB::Record::UserSet object
384 $problem, # a WeBWorK::Problem object 42 $problem, # a WeBWorK::DB::Record::UserProblem object
385 $psvn, 43 $psvn,
386 $formFields # in &WeBWorK::Form::Vars format 44 $formFields # in &WeBWorK::Form::Vars format
387 { # translation options 45 { # translation options
388 displayMode => "images", # (plainText|formattedText|images) 46 displayMode => "images", # (plainText|formattedText|images)
389 showHints => 1, # (0|1) 47 showHints => 1, # (0|1)
403 $warnings = $pg->{warnings}; # text string 61 $warnings = $pg->{warnings}; # text string
404 $flags = $pg->{flags}; # hash reference 62 $flags = $pg->{flags}; # hash reference
405 63
406=head1 DESCRIPTION 64=head1 DESCRIPTION
407 65
408WeBWorK::PG encapsulates the PG translation process, making multiple calls to 66WeBWorK::PG is a factory for modules which use the WeBWorK::PG API. Notable
409WeBWorK::PG::Translator. Much of the flexibility of the Translator is hidden, 67modules which use this API (and exist) are WeBWorK::PG::Local and
410instead making choices that are appropriate for the webwork-modperl system. 68WeBWorK::PG::Remote. The course environment key $pg{renderer} is consulted to
69determine which render to use.
411 70
412=head1 CONSTRUCTION 71=head1 THE WEBWORK::PG API
72
73Modules which support this API must implement the following method:
413 74
414=over 75=over
415 76
416=item new (ENVIRONMENT, USER, KEY, SET, PROBLEM, PSVN, FIELDS, OPTIONS) 77=item new (ENVIRONMENT, USER, KEY, SET, PROBLEM, PSVN, FIELDS, OPTIONS)
417 78
441 102
442a WeBWorK::Set object 103a WeBWorK::Set object
443 104
444=item PROBLEM 105=item PROBLEM
445 106
446a WeBWorK::Problem object. The contents of the source_file field can specify a 107a WeBWorK::DB::Record::UserProblem object. The contents of the source_file
447PG file either by absolute path or path relative to the "templates" directory. 108field can specify a PG file either by absolute path or path relative to the
448I<The caller should remove taint from this value before passing!> 109"templates" directory. I<The caller should remove taint from this value before
110passing!>
449 111
450=item PSVN 112=item PSVN
451 113
452the problem set version number 114the problem set version number
453 115
476boolean, render solutions 138boolean, render solutions
477 139
478=item refreshMath2img 140=item refreshMath2img
479 141
480boolean, force images created by math2img (in "images" mode) to be recreated, 142boolean, force images created by math2img (in "images" mode) to be recreated,
481even if the PG source has not been updated. 143even if the PG source has not been updated. FIXME: remove this option.
482 144
483=item processAnswers 145=item processAnswers
484 146
485boolean, call answer evaluators and graders 147boolean, call answer evaluators and graders
486 148
534 196
535A hash containing PG_flags (see the Translator docs). 197A hash containing PG_flags (see the Translator docs).
536 198
537=back 199=back
538 200
539=head1 OPERATION
540
541WeBWorK::PG goes through the following operations when constructed:
542
543=over
544
545=item Get database information
546
547Retrieve information about the current user, set, and problem from the
548database.
549
550=item Create a translator
551
552Instantiate a WeBWorK::PG::Translator object.
553
554=item Set the directory hash
555
556Set the translator's directory hash (courseScripts, macros, templates, and temp
557directories) from the course environment.
558
559=item Evaluate PG modules
560
561Using the module list from the course environment (pg->modules), perform a
562"use"-like operation to evaluate modules at runtime.
563
564=item Set the problem environment
565
566Use data from the user, set, and problem, as well as the course environemnt and
567translation options, to set the problem environment.
568
569=item Initialize the translator
570
571Call &WeBWorK::PG::Translator::initialize. What more do you want?
572
573=item Load PG.pl and dangerousMacros.pl
574
575These macros must be loaded without opcode masking, so they are loaded here.
576
577=item Set the opcode mask
578
579Set the opcode mask to the default specified by WeBWorK::PG::Translator.
580
581=item Load the problem source
582
583Give the problem source to the translator.
584
585=item Install a safety filter
586
587The safety filter is used to preprocess student input before evaluation. The
588default safety filter, &WeBWorK::PG::safetyFilter, is used.
589
590=item Translate the problem source
591
592Call &WeBWorK::PG::Translator::translate to render the problem source into the
593format given by the display mode.
594
595=item Process student answers
596
597Use form field inputs to evaluate student answers.
598
599=item Load the problem state
600
601Use values from the database to initialize the problem state, so that the
602grader will have a point of reference.
603
604=item Determine an entry order
605
606Use the ANSWER_ENTRY_ORDER flag to determine the order of answers in the
607problem. This is important for problems with dependancies among parts.
608
609=item Install a grader
610
611Use the PROBLEM_GRADER_TO_USE flag, or a default from the course environment,
612to install a grader.
613
614=item Grade the problem
615
616Use the selected grader to grade the problem.
617
618=back
619
620=head1 AUTHOR 201=head1 AUTHOR
621 202
622Written by Sam Hathaway, sh002i (at) math.rochester.edu. 203Written by Sam Hathaway, sh002i (at) math.rochester.edu.
623 204
624=cut 205=cut

Legend:
Removed from v.623  
changed lines
  Added in v.1244

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9