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