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