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