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

Diff of /trunk/webwork2/lib/WeBWorK/PG.pm

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

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

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9