Parent Directory
|
Revision Log
Allow commas, [ ] and | in answers (changes to Safety filter.) --MIke
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::PG::Translator; 19 use WeBWorK::Utils qw(readFile formatDateTime writeTimingLogEntry); 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 translator. 143 # for example, 'images' mode uses a tempdir for dvipng's temp files. We have 144 # to remove it. 145 if ($translationOptions->{displayMode} eq 'images' && $envir->{dvipngTempDir}) { 146 rmtree($envir->{dvipngTempDir}, 0, 0); 147 } 148 149 my ($result, $state); # we'll need these on the other side of the if block! 150 if ($translationOptions->{processAnswers}) { 151 152 # process student answers 153 #warn "PG: processing student answers\n"; 154 $translator->process_answers($formFields); 155 156 # retrieve the problem state and give it to the translator 157 #warn "PG: retrieving the problem state and giving it to the translator\n"; 158 $translator->rh_problem_state({ 159 recorded_score => $problem->status, 160 num_of_correct_ans => $problem->num_correct, 161 num_of_incorrect_ans => $problem->num_incorrect, 162 }); 163 164 # determine an entry order -- the ANSWER_ENTRY_ORDER flag is built by 165 # the PG macro package (PG.pl) 166 #warn "PG: determining an entry order\n"; 167 my @answerOrder = 168 $translator->rh_flags->{ANSWER_ENTRY_ORDER} 169 ? @{ $translator->rh_flags->{ANSWER_ENTRY_ORDER} } 170 : keys %{ $translator->rh_evaluated_answers }; 171 172 # install a grader -- use the one specified in the problem, 173 # or fall back on the default from the course environment. 174 # (two magic strings are accepted, to avoid having to 175 # reference code when it would be difficult.) 176 #warn "PG: installing a grader\n"; 177 my $grader = $translator->rh_flags->{PROBLEM_GRADER_TO_USE} 178 || $courseEnv->{pg}->{options}->{grader}; 179 $grader = $translator->rf_std_problem_grader 180 if $grader eq "std_problem_grader"; 181 $grader = $translator->rf_avg_problem_grader 182 if $grader eq "avg_problem_grader"; 183 die "Problem grader $grader is not a CODE reference." 184 unless ref $grader eq "CODE"; 185 $translator->rf_problem_grader($grader); 186 187 # grade the problem 188 #warn "PG: grading the problem\n"; 189 ($result, $state) = $translator->grade_problem( 190 answers_submitted => $translationOptions->{processAnswers}, 191 ANSWER_ENTRY_ORDER => \@answerOrder, 192 ); 193 194 } 195 196 # write timing log entry 197 writeTimingLogEntry($courseEnv, "WeBWorK::PG::new", "", "end"); 198 199 # return an object which contains the translator and the results of 200 # the translation process. this is DIFFERENT from the "format expected 201 # by Webwork.pm (and I believe processProblem8, but check.)" 202 return bless { 203 translator => $translator, 204 head_text => ${ $translator->r_header }, 205 body_text => ${ $translator->r_text }, 206 answers => $translator->rh_evaluated_answers, 207 result => $result, 208 state => $state, 209 errors => $translator->errors, 210 warnings => $warnings, 211 flags => $translator->rh_flags, 212 }, $class; 213 } 214 215 # ----- 216 217 sub defineProblemEnvir($$$$$$$) { 218 my ( 219 $courseEnv, 220 $user, 221 $key, 222 $set, 223 $problem, 224 $psvn, 225 $formFields, 226 $options, 227 ) = @_; 228 229 my %envir; 230 231 # PG environment variables 232 # from docs/pglanguage/pgreference/environmentvariables as of 06/25/2002 233 # any changes are noted by "ADDED:" or "REMOVED:" 234 235 # Vital state information 236 # ADDED: displayHintsQ, displaySolutionsQ, refreshMath2img, 237 # texDisposition 238 239 $envir{psvn} = $psvn; 240 $envir{psvnNumber} = $envir{psvn}; 241 $envir{probNum} = $problem->problem_id; 242 $envir{questionNumber} = $envir{probNum}; 243 $envir{fileName} = $problem->source_file; 244 $envir{probFileName} = $envir{fileName}; 245 $envir{problemSeed} = (defined($options->{override_seed}) ) ? $options->{override_seed} :$problem->problem_seed; 246 $envir{displayMode} = translateDisplayModeNames($options->{displayMode}); 247 $envir{languageMode} = $envir{displayMode}; 248 $envir{outputMode} = $envir{displayMode}; 249 $envir{displayHintsQ} = $options->{showHints}; 250 $envir{displaySolutionsQ} = $options->{showSolutions}; 251 $envir{refreshMath2img} = $options->{refreshMath2img}; 252 $envir{texDisposition} = "pdf"; # in webwork-modperl, we use pdflatex 253 254 # Problem Information 255 # ADDED: courseName, formatedDueDate 256 257 $envir{openDate} = $set->open_date; 258 $envir{formattedOpenDate} = formatDateTime($envir{openDate}); 259 $envir{dueDate} = $set->due_date; 260 $envir{formattedDueDate} = formatDateTime($envir{dueDate}); 261 $envir{formatedDueDate} = $envir{formattedDueDate}; # typo in many header files 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->set_id; 277 $envir{studentLogin} = $user->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 $envir{externalGif2PngPath} = $courseEnv->{externalPrograms}->{gif2png}; 296 297 # Directories and URLs 298 # REMOVED: courseName 299 # ADDED: dvipngTempDir 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 # Information for sending mail 318 319 $envir{mailSmtpServer} = $courseEnv->{mail}->{smtpServer}; 320 $envir{mailSmtpSender} = $courseEnv->{mail}->{smtpSender}; 321 $envir{ALLOW_MAIL_TO} = $courseEnv->{mail}->{allowedRecipients}; 322 323 # Default values for evaluating answers 324 325 my $ansEvalDefaults = $courseEnv->{pg}->{ansEvalDefaults}; 326 $envir{$_} = $ansEvalDefaults->{$_} foreach (keys %$ansEvalDefaults); 327 328 # Other things... 329 330 $envir{PROBLEM_GRADER_TO_USE} = $courseEnv->{pg}->{options}->{grader}; 331 332 return \%envir; 333 } 334 335 sub translateDisplayModeNames($) { 336 my $name = shift; 337 return { 338 tex => "TeX", 339 plainText => "HTML", 340 formattedText => "HTML_tth", 341 images => "HTML_img" 342 }->{$name}; 343 } 344 345 sub safetyFilter { 346 my $answer = shift; # accepts one answer and checks it 347 my $submittedAnswer = $answer; 348 $answer = '' unless defined $answer; 349 my ($errorno); 350 $answer =~ tr/\000-\037/ /; 351 # Return if answer field is empty 352 unless ($answer =~ /\S/) { 353 #$errorno = "<BR>No answer was submitted."; 354 $errorno = 0; ## don't report blank answer as error 355 return ($answer,$errorno); 356 } 357 # replace ^ with ** (for exponentiation) 358 # $answer =~ s/\^/**/g; 359 # Return if forbidden characters are found 360 unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\[\]\(\)\,\|]+$/ ) { 361 $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c; 362 $errorno = "<BR>There are forbidden characters in your answer: $submittedAnswer<BR>"; 363 return ($answer,$errorno); 364 } 365 $errorno = 0; 366 return($answer, $errorno); 367 } 368 369 1; 370 371 __END__ 372 373 =head1 SYNOPSIS 374 375 $pg = WeBWorK::PG->new( 376 $courseEnv, # a WeBWorK::CourseEnvironment object 377 $user, # a WeBWorK::DB::Record::User object 378 $sessionKey, 379 $set, # a WeBWorK::DB::Record::UserSet object 380 $problem, # a WeBWorK::DB::Record::UserProblem object 381 $psvn, 382 $formFields # in &WeBWorK::Form::Vars format 383 { # translation options 384 displayMode => "images", # (plainText|formattedText|images) 385 showHints => 1, # (0|1) 386 showSolutions => 0, # (0|1) 387 refreshMath2img => 0, # (0|1) 388 processAnswers => 1, # (0|1) 389 }, 390 ); 391 392 $translator = $pg->{translator}; # WeBWorK::PG::Translator 393 $body = $pg->{body_text}; # text string 394 $header = $pg->{head_text}; # text string 395 $answerHash = $pg->{answers}; # WeBWorK::PG::AnswerHash 396 $result = $pg->{result}; # hash reference 397 $state = $pg->{state}; # hash reference 398 $errors = $pg->{errors}; # text string 399 $warnings = $pg->{warnings}; # text string 400 $flags = $pg->{flags}; # hash reference 401 402 =head1 DESCRIPTION 403 404 WeBWorK::PG encapsulates the PG translation process, making multiple calls to 405 WeBWorK::PG::Translator. Much of the flexibility of the Translator is hidden, 406 instead making choices that are appropriate for the webwork-modperl system. 407 408 =head1 CONSTRUCTION 409 410 =over 411 412 =item new (ENVIRONMENT, USER, KEY, SET, PROBLEM, PSVN, FIELDS, OPTIONS) 413 414 The C<new> method creates a translator, initializes it using the parameters 415 specified, translates a PG file, and processes answers. It returns a reference 416 to a blessed hash containing the results of the translation process. 417 418 =back 419 420 =head2 Parameters 421 422 =over 423 424 =item ENVIRONMENT 425 426 a WeBWorK::CourseEnvironment object 427 428 =item USER 429 430 a WeBWorK::User object 431 432 =item KEY 433 434 the session key of the current session 435 436 =item SET 437 438 a WeBWorK::Set object 439 440 =item PROBLEM 441 442 a WeBWorK::DB::Record::UserProblem object. The contents of the source_file 443 field can specify a PG file either by absolute path or path relative to the 444 "templates" directory. I<The caller should remove taint from this value before 445 passing!> 446 447 =item PSVN 448 449 the problem set version number 450 451 =item FIELDS 452 453 a reference to a hash (as returned by &WeBWorK::Form::Vars) containing form 454 fields submitted by a problem processor. The translator will look for fields 455 like "AnSwEr[0-9]" containing submitted student answers. 456 457 =item OPTIONS 458 459 a reference to a hash containing the following data: 460 461 =over 462 463 =item displayMode 464 465 one of "plainText", "formattedText", or "images" 466 467 =item showHints 468 469 boolean, render hints 470 471 =item showSolutions 472 473 boolean, render solutions 474 475 =item refreshMath2img 476 477 boolean, force images created by math2img (in "images" mode) to be recreated, 478 even if the PG source has not been updated. 479 480 =item processAnswers 481 482 boolean, call answer evaluators and graders 483 484 =back 485 486 =back 487 488 =head2 RETURN VALUE 489 490 The C<new> method returns a blessed hash reference containing the following 491 fields. More information can be found in the documentation for 492 WeBWorK::PG::Translator. 493 494 =over 495 496 =item translator 497 498 The WeBWorK::PG::Translator object used to render the problem. 499 500 =item head_text 501 502 HTML code for the E<lt>headE<gt> block of an resulting web page. Used for 503 JavaScript features. 504 505 =item body_text 506 507 HTML code for the E<lt>bodyE<gt> block of an resulting web page. 508 509 =item answers 510 511 An C<AnswerHash> object containing submitted answers, and results of answer 512 evaluation. 513 514 =item result 515 516 A hash containing the results of grading the problem. 517 518 =item state 519 520 A hash containing the new problem state. 521 522 =item errors 523 524 A string containing any errors encountered while rendering the problem. 525 526 =item warnings 527 528 A string containing any warnings encountered while rendering the problem. 529 530 =item flags 531 532 A hash containing PG_flags (see the Translator docs). 533 534 =back 535 536 =head1 OPERATION 537 538 WeBWorK::PG goes through the following operations when constructed: 539 540 =over 541 542 =item Get database information 543 544 Retrieve information about the current user, set, and problem from the 545 database. 546 547 =item Create a translator 548 549 Instantiate a WeBWorK::PG::Translator object. 550 551 =item Set the directory hash 552 553 Set the translator's directory hash (courseScripts, macros, templates, and temp 554 directories) from the course environment. 555 556 =item Evaluate PG modules 557 558 Using the module list from the course environment (pg->modules), perform a 559 "use"-like operation to evaluate modules at runtime. 560 561 =item Set the problem environment 562 563 Use data from the user, set, and problem, as well as the course environemnt and 564 translation options, to set the problem environment. 565 566 =item Initialize the translator 567 568 Call &WeBWorK::PG::Translator::initialize. What more do you want? 569 570 =item Load PG.pl and dangerousMacros.pl 571 572 These macros must be loaded without opcode masking, so they are loaded here. 573 574 =item Set the opcode mask 575 576 Set the opcode mask to the default specified by WeBWorK::PG::Translator. 577 578 =item Load the problem source 579 580 Give the problem source to the translator. 581 582 =item Install a safety filter 583 584 The safety filter is used to preprocess student input before evaluation. The 585 default safety filter, &WeBWorK::PG::safetyFilter, is used. 586 587 =item Translate the problem source 588 589 Call &WeBWorK::PG::Translator::translate to render the problem source into the 590 format given by the display mode. 591 592 =item Process student answers 593 594 Use form field inputs to evaluate student answers. 595 596 =item Load the problem state 597 598 Use values from the database to initialize the problem state, so that the 599 grader will have a point of reference. 600 601 =item Determine an entry order 602 603 Use the ANSWER_ENTRY_ORDER flag to determine the order of answers in the 604 problem. This is important for problems with dependancies among parts. 605 606 =item Install a grader 607 608 Use the PROBLEM_GRADER_TO_USE flag, or a default from the course environment, 609 to install a grader. 610 611 =item Grade the problem 612 613 Use the selected grader to grade the problem. 614 615 =back 616 617 =head1 AUTHOR 618 619 Written by Sam Hathaway, sh002i (at) math.rochester.edu. 620 621 =cut
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |