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