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