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