Parent Directory
|
Revision Log
yo shout! answer previewing is working. -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 } 50 51 # create a Translator 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, 128 WeBWorK::Utils::readFile($sourceFile) says: 129 $@ 130 EOF 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 } 220 221 # ----- 222 223 sub 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 339 sub 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 349 sub 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 373 1; 374 375 __END__ 376 377 =head1 SYNOPSIS 378 379 $pg = WeBWorK::PG->new( 380 $courseEnv, # a WeBWorK::CourseEnvironment object 381 $user, # a WeBWorK::User object 382 $sessionKey, 383 $set, # a WeBWorK::Set object 384 $problem, # a WeBWorK::Problem object 385 $psvn, 386 $formFields # in &WeBWorK::Form::Vars format 387 { # translation options 388 displayMode => "images", # (plainText|formattedText|images) 389 showHints => 1, # (0|1) 390 showSolutions => 0, # (0|1) 391 refreshMath2img => 0, # (0|1) 392 processAnswers => 1, # (0|1) 393 }, 394 ); 395 396 $translator = $pg->{translator}; # WeBWorK::PG::Translator 397 $body = $pg->{body_text}; # text string 398 $header = $pg->{head_text}; # text string 399 $answerHash = $pg->{answers}; # WeBWorK::PG::AnswerHash 400 $result = $pg->{result}; # hash reference 401 $state = $pg->{state}; # hash reference 402 $errors = $pg->{errors}; # text string 403 $warnings = $pg->{warnings}; # text string 404 $flags = $pg->{flags}; # hash reference 405 406 =head1 DESCRIPTION 407 408 WeBWorK::PG encapsulates the PG translation process, making multiple calls to 409 WeBWorK::PG::Translator. Much of the flexibility of the Translator is hidden, 410 instead making choices that are appropriate for the webwork-modperl system. 411 412 =head1 CONSTRUCTION 413 414 =over 415 416 =item new (ENVIRONMENT, USER, KEY, SET, PROBLEM, PSVN, FIELDS, OPTIONS) 417 418 The C<new> method creates a translator, initializes it using the parameters 419 specified, translates a PG file, and processes answers. It returns a reference 420 to a blessed hash containing the results of the translation process. 421 422 =back 423 424 =head2 Parameters 425 426 =over 427 428 =item ENVIRONMENT 429 430 a WeBWorK::CourseEnvironment object 431 432 =item USER 433 434 a WeBWorK::User object 435 436 =item KEY 437 438 the session key of the current session 439 440 =item SET 441 442 a WeBWorK::Set object 443 444 =item PROBLEM 445 446 a WeBWorK::Problem object. The contents of the source_file field can specify a 447 PG file either by absolute path or path relative to the "templates" directory. 448 I<The caller should remove taint from this value before passing!> 449 450 =item PSVN 451 452 the problem set version number 453 454 =item FIELDS 455 456 a reference to a hash (as returned by &WeBWorK::Form::Vars) containing form 457 fields submitted by a problem processor. The translator will look for fields 458 like "AnSwEr[0-9]" containing submitted student answers. 459 460 =item OPTIONS 461 462 a reference to a hash containing the following data: 463 464 =over 465 466 =item displayMode 467 468 one of "plainText", "formattedText", or "images" 469 470 =item showHints 471 472 boolean, render hints 473 474 =item showSolutions 475 476 boolean, render solutions 477 478 =item refreshMath2img 479 480 boolean, force images created by math2img (in "images" mode) to be recreated, 481 even if the PG source has not been updated. 482 483 =item processAnswers 484 485 boolean, call answer evaluators and graders 486 487 =back 488 489 =back 490 491 =head2 RETURN VALUE 492 493 The C<new> method returns a blessed hash reference containing the following 494 fields. More information can be found in the documentation for 495 WeBWorK::PG::Translator. 496 497 =over 498 499 =item translator 500 501 The WeBWorK::PG::Translator object used to render the problem. 502 503 =item head_text 504 505 HTML code for the E<lt>headE<gt> block of an resulting web page. Used for 506 JavaScript features. 507 508 =item body_text 509 510 HTML code for the E<lt>bodyE<gt> block of an resulting web page. 511 512 =item answers 513 514 An C<AnswerHash> object containing submitted answers, and results of answer 515 evaluation. 516 517 =item result 518 519 A hash containing the results of grading the problem. 520 521 =item state 522 523 A hash containing the new problem state. 524 525 =item errors 526 527 A string containing any errors encountered while rendering the problem. 528 529 =item warnings 530 531 A string containing any warnings encountered while rendering the problem. 532 533 =item flags 534 535 A hash containing PG_flags (see the Translator docs). 536 537 =back 538 539 =head1 OPERATION 540 541 WeBWorK::PG goes through the following operations when constructed: 542 543 =over 544 545 =item Get database information 546 547 Retrieve information about the current user, set, and problem from the 548 database. 549 550 =item Create a translator 551 552 Instantiate a WeBWorK::PG::Translator object. 553 554 =item Set the directory hash 555 556 Set the translator's directory hash (courseScripts, macros, templates, and temp 557 directories) from the course environment. 558 559 =item Evaluate PG modules 560 561 Using 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 566 Use data from the user, set, and problem, as well as the course environemnt and 567 translation options, to set the problem environment. 568 569 =item Initialize the translator 570 571 Call &WeBWorK::PG::Translator::initialize. What more do you want? 572 573 =item Load PG.pl and dangerousMacros.pl 574 575 These macros must be loaded without opcode masking, so they are loaded here. 576 577 =item Set the opcode mask 578 579 Set the opcode mask to the default specified by WeBWorK::PG::Translator. 580 581 =item Load the problem source 582 583 Give the problem source to the translator. 584 585 =item Install a safety filter 586 587 The safety filter is used to preprocess student input before evaluation. The 588 default safety filter, &WeBWorK::PG::safetyFilter, is used. 589 590 =item Translate the problem source 591 592 Call &WeBWorK::PG::Translator::translate to render the problem source into the 593 format given by the display mode. 594 595 =item Process student answers 596 597 Use form field inputs to evaluate student answers. 598 599 =item Load the problem state 600 601 Use values from the database to initialize the problem state, so that the 602 grader will have a point of reference. 603 604 =item Determine an entry order 605 606 Use the ANSWER_ENTRY_ORDER flag to determine the order of answers in the 607 problem. This is important for problems with dependancies among parts. 608 609 =item Install a grader 610 611 Use the PROBLEM_GRADER_TO_USE flag, or a default from the course environment, 612 to install a grader. 613 614 =item Grade the problem 615 616 Use the selected grader to grade the problem. 617 618 =back 619 620 =head1 AUTHOR 621 622 Written by Sam Hathaway, sh002i (at) math.rochester.edu. 623 624 =cut
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |