Parent Directory
|
Revision Log
Modified ImageGenerator to use new EquationImage module. Added arguments to ImageGenerator call to support global image cache.
1 ################################################################################ 2 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project 3 # $Id$ 4 ################################################################################ 5 6 package WeBWorK::PG::Local; 7 8 =head1 NAME 9 10 WeBWorK::PG::Local - Use the WeBWorK::PG API to invoke a local 11 WeBWorK::PG::Translator object. 12 13 =head1 DESCRIPTION 14 15 WeBWorK::PG::Local encapsulates the PG translation process, making multiple 16 calls to WeBWorK::PG::Translator. Much of the flexibility of the Translator is 17 hidden, instead making choices that are appropriate for the webwork-modperl 18 system 19 20 It implements the WeBWorK::PG interface and uses a local 21 WeBWorK::PG::Translator to perform problem rendering. See the documentation for 22 the WeBWorK::PG module for information about the API. 23 24 =cut 25 26 use strict; 27 use warnings; 28 use File::Path qw(rmtree); 29 use WeBWorK::PG::ImageGenerator; 30 use WeBWorK::PG::Translator; 31 use WeBWorK::Utils qw(readFile formatDateTime writeTimingLogEntry makeTempDirectory); 32 BEGIN { 33 34 # This safe compartment is used to read the large macro files such as 35 # PG.pl, PGbasicmacros.pl and PGanswermacros and cache the results so that 36 # future calls have preloaded versions of these large files. 37 # This saves a significant amount of time. 38 39 $WeBWorK::PG::Local::safeCache = new Safe; 40 # warn "Creating new Safe cache compartment ".$WeBWorK::PG::Local::safeCache->root; 41 } 42 sub new { 43 my $invocant = shift; 44 my $class = ref($invocant) || $invocant; 45 my ( 46 $ce, 47 $user, 48 $key, 49 $set, 50 $problem, 51 $psvn, 52 $formFields, # in CGI::Vars format 53 $translationOptions, # hashref containing options for the 54 # translator, such as whether to show 55 # hints and the display mode to use 56 ) = @_; 57 58 # write timing log entry 59 writeTimingLogEntry($ce, "WeBWorK::PG::new", 60 "user=".$user->user_id.",problem=".$ce->{courseName}."/".$set->set_id."/".$problem->problem_id.",mode=".$translationOptions->{displayMode}, 61 "begin"); 62 63 # install a local warn handler to collect warnings 64 my $warnings = ""; 65 local $SIG{__WARN__} = sub { $warnings .= shift } 66 if $ce->{pg}->{options}->{catchWarnings}; 67 68 # create a Translator 69 #warn "PG: creating a Translator\n"; 70 my $translator = WeBWorK::PG::Translator->new; 71 72 # set the directory hash 73 #warn "PG: setting the directory hash\n"; 74 $translator->rh_directories({ 75 courseScriptsDirectory => $ce->{pg}->{directories}->{macros}, 76 macroDirectory => $ce->{courseDirs}->{macros}, 77 templateDirectory => $ce->{courseDirs}->{templates}, 78 tempDirectory => $ce->{courseDirs}->{html_temp}, 79 }); 80 81 # evaluate modules and "extra packages" 82 #warn "PG: evaluating modules and \"extra packages\"\n"; 83 my @modules = @{ $ce->{pg}->{modules} }; 84 foreach my $module_packages_ref (@modules) { 85 my ($module, @extra_packages) = @$module_packages_ref; 86 # the first item is the main package 87 $translator->evaluate_modules($module); 88 # the remaining items are "extra" packages 89 $translator->load_extra_packages(@extra_packages); 90 } 91 92 # set the environment (from defineProblemEnvir) 93 #warn "PG: setting the environment (from defineProblemEnvir)\n"; 94 my $envir = defineProblemEnvir( 95 $ce, 96 $user, 97 $key, 98 $set, 99 $problem, 100 $psvn, 101 $formFields, 102 $translationOptions, 103 ); 104 $translator->environment($envir); 105 106 # initialize the Translator 107 #warn "PG: initializing the Translator\n"; 108 $translator->initialize(); 109 # $translator->dumpSafe; # debugging code 110 ############################################################################### 111 # Preload the macros files which are used routinely: PG.pl, dangerousMacros.pl, IO.pl 112 # PGbasicmacros.pl and PGanswermacros.pl 113 # Preloading the last two files safes a significant amount of time. 114 ############################################################################### 115 116 # IO.pl, PG.pl, and dangerousMacros.pl are loaded using unrestricted_load 117 # This is hard wired into the Translator::pre_load_macro_files subroutine 118 # I'd like to change this at some point to have the same sort of interface to global.conf 119 # that the module loading does -- have a list of macros to load unrestrictedly. 120 121 # This has been replaced by the pre_load_macro_files subroutine. It loads AND caches the files. 122 # While PG.pl and dangerousMacros are not large, they are referred to by PGbasicmacros and PGanswermacros. 123 # Because these are loaded into the cached name space (e.g. Safe::Root1::) all calls to, say NEW_ANSWER_NAME 124 # are actually calls to Safe::Root1::NEW_ANSWER_NAME. It is useful to have these names inside the Safe::Root1: 125 # cached safe compartment. (NEW_ANSWER_NAME and all other subroutine names are also automatically exported into 126 # the current safe compartment Safe::Rootx:: 127 128 # The headers of both PGbasicmacros and PGanswermacros has code that insures that the constants used are imported into 129 # the current safe compartment. This involves evaluating references to, say $main::displayMode, at runtime to insure that main 130 # refers to Safe::Rootx:: and NOT to Safe::Root1::, which is the value of main:: at compile time. 131 132 133 ############################################################################### 134 # TO ENABLE CACHEING UNCOMMENT THE CACHEING CODE 135 # On webwork3 cached code is .2 seconds faster than non-cached code for an existing child. 136 137 # CACHING CODE: 138 eval{ 139 $translator->pre_load_macro_files($WeBWorK::PG::Local::safeCache, $ce->{pg}->{directories}->{macros}, 140 'PG.pl', 'dangerousMacros.pl','IO.pl','PGbasicmacros.pl','PGanswermacros.pl');}; 141 warn "Error while preloading macro files: $@" if $@; 142 143 # STANDARD LOADING CODE: for cached script files this merely initializes the constants. 144 foreach (qw( PG.pl dangerousMacros.pl IO.pl)) { 145 my $macroPath = $ce->{pg}->{directories}->{macros} . "/$_"; 146 my $err = $translator->unrestricted_load($macroPath); 147 warn "Error while loading $macroPath: |$err|" if $err; 148 } 149 ############################################################################### 150 151 # set the opcode mask (using default values) 152 #warn "PG: setting the opcode mask (using default values)\n"; 153 $translator->set_mask(); 154 155 # store the problem source 156 #warn "PG: storing the problem source\n"; 157 my $sourceFile = $problem->source_file; 158 $sourceFile = $ce->{courseDirs}->{templates}."/".$sourceFile 159 unless ($sourceFile =~ /^\//); 160 eval { $translator->source_string(readFile($sourceFile)) }; 161 if ($@) { 162 # well, we couldn't get the problem source, for some reason. 163 return bless { 164 translator => $translator, 165 head_text => "", 166 body_text => <<EOF, 167 WeBWorK::Utils::readFile($sourceFile) says: 168 $@ 169 EOF 170 answers => {}, 171 result => {}, 172 state => {}, 173 errors => "Failed to read the problem source file.", 174 warnings => $warnings, 175 flags => {error_flag => 1}, 176 }, $class; 177 } 178 179 # install a safety filter (&safetyFilter) 180 #warn "PG: installing a safety filter\n"; 181 $translator->rf_safety_filter(\&safetyFilter); 182 183 # write timing log entry -- the translator is now all set up 184 writeTimingLogEntry($ce, "WeBWorK::PG::new", 185 "initialized", 186 "intermediate"); 187 188 # translate the PG source into text 189 #warn "PG: translating the PG source into text\n"; 190 $translator->translate(); 191 192 # after we're done translating, we may have to clean up after the 193 # translator: 194 195 # for example, HTML_img mode uses a tempdir for dvipng's temp files.\ 196 # We have to remove it. 197 if ($envir->{dvipngTempDir}) { 198 rmtree($envir->{dvipngTempDir}, 0, 0); 199 } 200 201 # HTML_dpng, on the other hand, uses an ImageGenerator. We have to 202 # render the queued equations. 203 if ($envir->{imagegen}) { 204 my $sourceFile = $ce->{courseDirs}->{templates} . "/" . $problem->source_file; 205 my %mtimeOption = -e $sourceFile 206 ? (mtime => (stat $sourceFile)[9]) 207 : (); 208 209 $envir->{imagegen}->render( 210 refresh => $translationOptions->{refreshMath2img}, 211 %mtimeOption, 212 ); 213 } 214 215 my ($result, $state); # we'll need these on the other side of the if block! 216 if ($translationOptions->{processAnswers}) { 217 218 # process student answers 219 #warn "PG: processing student answers\n"; 220 $translator->process_answers($formFields); 221 222 # retrieve the problem state and give it to the translator 223 #warn "PG: retrieving the problem state and giving it to the translator\n"; 224 $translator->rh_problem_state({ 225 recorded_score => $problem->status, 226 num_of_correct_ans => $problem->num_correct, 227 num_of_incorrect_ans => $problem->num_incorrect, 228 }); 229 230 # determine an entry order -- the ANSWER_ENTRY_ORDER flag is built by 231 # the PG macro package (PG.pl) 232 #warn "PG: determining an entry order\n"; 233 my @answerOrder = 234 $translator->rh_flags->{ANSWER_ENTRY_ORDER} 235 ? @{ $translator->rh_flags->{ANSWER_ENTRY_ORDER} } 236 : keys %{ $translator->rh_evaluated_answers }; 237 238 # install a grader -- use the one specified in the problem, 239 # or fall back on the default from the course environment. 240 # (two magic strings are accepted, to avoid having to 241 # reference code when it would be difficult.) 242 #warn "PG: installing a grader\n"; 243 my $grader = $translator->rh_flags->{PROBLEM_GRADER_TO_USE} 244 || $ce->{pg}->{options}->{grader}; 245 $grader = $translator->rf_std_problem_grader 246 if $grader eq "std_problem_grader"; 247 $grader = $translator->rf_avg_problem_grader 248 if $grader eq "avg_problem_grader"; 249 die "Problem grader $grader is not a CODE reference." 250 unless ref $grader eq "CODE"; 251 $translator->rf_problem_grader($grader); 252 253 # grade the problem 254 #warn "PG: grading the problem\n"; 255 ($result, $state) = $translator->grade_problem( 256 answers_submitted => $translationOptions->{processAnswers}, 257 ANSWER_ENTRY_ORDER => \@answerOrder, 258 ); 259 260 } 261 262 # write timing log entry 263 writeTimingLogEntry($ce, "WeBWorK::PG::new", "", "end"); 264 265 # return an object which contains the translator and the results of 266 # the translation process. this is DIFFERENT from the "format expected 267 # by Webwork.pm (and I believe processProblem8, but check.)" 268 return bless { 269 translator => $translator, 270 head_text => ${ $translator->r_header }, 271 body_text => ${ $translator->r_text }, 272 answers => $translator->rh_evaluated_answers, 273 result => $result, 274 state => $state, 275 errors => $translator->errors, 276 warnings => $warnings, 277 flags => $translator->rh_flags, 278 }, $class; 279 } 280 281 # ----- 282 283 sub defineProblemEnvir { 284 my ( 285 $ce, 286 $user, 287 $key, 288 $set, 289 $problem, 290 $psvn, 291 $formFields, 292 $options, 293 ) = @_; 294 295 my %envir; 296 297 # ---------------------------------------------------------------------- 298 299 # PG environment variables 300 # from docs/pglanguage/pgreference/environmentvariables as of 06/25/2002 301 # any changes are noted by "ADDED:" or "REMOVED:" 302 303 # Vital state information 304 # ADDED: displayHintsQ, displaySolutionsQ, refreshMath2img, 305 # texDisposition 306 307 $envir{psvn} = $set->psvn; 308 $envir{psvnNumber} = $envir{psvn}; 309 $envir{probNum} = $problem->problem_id; 310 $envir{questionNumber} = $envir{probNum}; 311 $envir{fileName} = $problem->source_file; 312 $envir{probFileName} = $envir{fileName}; 313 $envir{problemSeed} = $problem->problem_seed; 314 $envir{displayMode} = translateDisplayModeNames($options->{displayMode}); 315 $envir{languageMode} = $envir{displayMode}; 316 $envir{outputMode} = $envir{displayMode}; 317 $envir{displayHintsQ} = $options->{showHints}; 318 $envir{displaySolutionsQ} = $options->{showSolutions}; 319 # FIXME: this is HTML_img specific 320 #$envir{refreshMath2img} = $options->{refreshMath2img}; 321 $envir{texDisposition} = "pdf"; # in webwork-modperl, we use pdflatex 322 323 # Problem Information 324 # ADDED: courseName, formatedDueDate 325 326 $envir{openDate} = $set->open_date; 327 $envir{formattedOpenDate} = formatDateTime($envir{openDate}); 328 $envir{dueDate} = $set->due_date; 329 $envir{formattedDueDate} = formatDateTime($envir{dueDate}); 330 $envir{formatedDueDate} = $envir{formattedDueDate}; # typo in many header files 331 $envir{answerDate} = $set->answer_date; 332 $envir{formattedAnswerDate} = formatDateTime($envir{answerDate}); 333 $envir{numOfAttempts} = ($problem->num_correct || 0) + ($problem->num_incorrect || 0); 334 $envir{problemValue} = $problem->value; 335 $envir{sessionKey} = $key; 336 $envir{courseName} = $ce->{courseName}; 337 338 # Student Information 339 # ADDED: studentID 340 341 $envir{sectionName} = $user->section; 342 $envir{sectionNumber} = $envir{sectionName}; 343 $envir{recitationName} = $user->recitation; 344 $envir{recitationNumber} = $envir{recitationName}; 345 $envir{setNumber} = $set->set_id; 346 $envir{studentLogin} = $user->user_id; 347 $envir{studentName} = $user->first_name . " " . $user->last_name; 348 $envir{studentID} = $user->student_id; 349 350 # Answer Information 351 # REMOVED: refSubmittedAnswers 352 353 $envir{inputs_ref} = $formFields; 354 355 # External Programs 356 # ADDED: externalLaTeXPath, externalDvipngPath, 357 # externalGif2EpsPath, externalPng2EpsPath 358 359 $envir{externalTTHPath} = $ce->{externalPrograms}->{tth}; 360 $envir{externalLaTeXPath} = $ce->{externalPrograms}->{latex}; 361 $envir{externalDvipngPath} = $ce->{externalPrograms}->{dvipng}; 362 $envir{externalGif2EpsPath} = $ce->{externalPrograms}->{gif2eps}; 363 $envir{externalPng2EpsPath} = $ce->{externalPrograms}->{png2eps}; 364 $envir{externalGif2PngPath} = $ce->{externalPrograms}->{gif2png}; 365 366 # Directories and URLs 367 # REMOVED: courseName 368 # ADDED: dvipngTempDir 369 370 $envir{cgiDirectory} = undef; 371 $envir{cgiURL} = undef; 372 $envir{classDirectory} = undef; 373 $envir{courseScriptsDirectory} = $ce->{pg}->{directories}->{macros}."/"; 374 $envir{htmlDirectory} = $ce->{courseDirs}->{html}."/"; 375 $envir{htmlURL} = $ce->{courseURLs}->{html}."/"; 376 $envir{macroDirectory} = $ce->{courseDirs}->{macros}."/"; 377 $envir{templateDirectory} = $ce->{courseDirs}->{templates}."/"; 378 $envir{tempDirectory} = $ce->{courseDirs}->{html_temp}."/"; 379 $envir{tempURL} = $ce->{courseURLs}->{html_temp}."/"; 380 $envir{scriptDirectory} = undef; 381 $envir{webworkDocsURL} = $ce->{webworkURLs}->{docs}."/"; 382 # FIXME: this is HTML_img mode-specific 383 #$envir{dvipngTempDir} = $options->{displayMode} eq 'images' 384 # ? makeTempDirectory($envir{tempDirectory}, "webwork-dvipng") 385 # : undef; 386 387 # Information for sending mail 388 389 $envir{mailSmtpServer} = $ce->{mail}->{smtpServer}; 390 $envir{mailSmtpSender} = $ce->{mail}->{smtpSender}; 391 $envir{ALLOW_MAIL_TO} = $ce->{mail}->{allowedRecipients}; 392 393 # Default values for evaluating answers 394 395 my $ansEvalDefaults = $ce->{pg}->{ansEvalDefaults}; 396 $envir{$_} = $ansEvalDefaults->{$_} foreach (keys %$ansEvalDefaults); 397 398 # ---------------------------------------------------------------------- 399 400 my $basename = "equation-$envir{psvn}.$envir{probNum}"; 401 $basename .= ".$envir{problemSeed}" if $envir{problemSeed}; 402 403 # Object for generating equation images 404 $envir{imagegen} = WeBWorK::PG::ImageGenerator->new( 405 tempDir => $ce->{webworkDirs}->{tmp}, # global temp dir 406 dir => $envir{tempDirectory}, 407 url => $envir{tempURL}, 408 basename => $basename, 409 latex => $envir{externalLaTeXPath}, 410 dvipng => $envir{externalDvipngPath}, 411 useCache => 1, 412 cacheDir => $ce->{webworkDirs}->{equationCache}, 413 cacheURL => $ce->{webworkURLs}->{equationCache}, 414 cacheDB => $ce->{webworkFiles}->{equationCacheDB}, 415 ); 416 417 # Other things... 418 $envir{QUIZ_PREFIX} = $options->{QUIZ_PREFIX}; # used by quizzes 419 $envir{PROBLEM_GRADER_TO_USE} = $ce->{pg}->{options}->{grader}; 420 $envir{PRINT_FILE_NAMES_FOR} = $ce->{pg}->{specialPGEnvironmentVars}->{PRINT_FILE_NAMES_FOR}; 421 422 # variables for interpreting capa problems. 423 $envir{CAPA_Tools} = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_Tools}; 424 $envir{CAPA_MCTools} = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_MCTools}; 425 $envir{CAPA_Graphics_URL} = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_Graphics_URL}; 426 $envir{CAPA_GraphicsDirectory} = $ce->{pg}->{specialPGEnvironmentVars}->{CAPA_GraphicsDirectory}; 427 428 return \%envir; 429 } 430 431 sub translateDisplayModeNames($) { 432 my $name = shift; 433 return { 434 tex => "TeX", 435 plainText => "HTML", 436 formattedText => "HTML_tth", 437 images => "HTML_dpng", # "HTML_img", 438 }->{$name}; 439 } 440 441 sub safetyFilter { 442 my $answer = shift; # accepts one answer and checks it 443 my $submittedAnswer = $answer; 444 $answer = '' unless defined $answer; 445 my ($errorno); 446 $answer =~ tr/\000-\037/ /; 447 # Return if answer field is empty 448 unless ($answer =~ /\S/) { 449 #$errorno = "<BR>No answer was submitted."; 450 $errorno = 0; ## don't report blank answer as error 451 return ($answer,$errorno); 452 } 453 # replace ^ with ** (for exponentiation) 454 # $answer =~ s/\^/**/g; 455 # Return if forbidden characters are found 456 unless ($answer =~ /^[a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\[\]\(\)\,\|]+$/ ) { 457 $answer =~ tr/a-zA-Z0-9_\-\+ \t\/@%\*\.\n^\(\)/#/c; 458 $errorno = "<BR>There are forbidden characters in your answer: $submittedAnswer<BR>"; 459 return ($answer,$errorno); 460 } 461 $errorno = 0; 462 return($answer, $errorno); 463 } 464 465 1; 466 467 __END__ 468 469 =head1 OPERATION 470 471 WeBWorK::PG goes through the following operations when constructed: 472 473 =over 474 475 =item Get database information 476 477 Retrieve information about the current user, set, and problem from the 478 database. 479 480 =item Create a translator 481 482 Instantiate a WeBWorK::PG::Translator object. 483 484 =item Set the directory hash 485 486 Set the translator's directory hash (courseScripts, macros, templates, and temp 487 directories) from the course environment. 488 489 =item Evaluate PG modules 490 491 Using the module list from the course environment (pg->modules), perform a 492 "use"-like operation to evaluate modules at runtime. 493 494 =item Set the problem environment 495 496 Use data from the user, set, and problem, as well as the course environemnt and 497 translation options, to set the problem environment. 498 499 =item Initialize the translator 500 501 Call &WeBWorK::PG::Translator::initialize. What more do you want? 502 503 =item Load PG.pl and dangerousMacros.pl 504 505 These macros must be loaded without opcode masking, so they are loaded here. 506 507 =item Set the opcode mask 508 509 Set the opcode mask to the default specified by WeBWorK::PG::Translator. 510 511 =item Load the problem source 512 513 Give the problem source to the translator. 514 515 =item Install a safety filter 516 517 The safety filter is used to preprocess student input before evaluation. The 518 default safety filter, &WeBWorK::PG::safetyFilter, is used. 519 520 =item Translate the problem source 521 522 Call &WeBWorK::PG::Translator::translate to render the problem source into the 523 format given by the display mode. 524 525 =item Process student answers 526 527 Use form field inputs to evaluate student answers. 528 529 =item Load the problem state 530 531 Use values from the database to initialize the problem state, so that the 532 grader will have a point of reference. 533 534 =item Determine an entry order 535 536 Use the ANSWER_ENTRY_ORDER flag to determine the order of answers in the 537 problem. This is important for problems with dependancies among parts. 538 539 =item Install a grader 540 541 Use the PROBLEM_GRADER_TO_USE flag, or a default from the course environment, 542 to install a grader. 543 544 =item Grade the problem 545 546 Use the selected grader to grade the problem. 547 548 =back 549 550 =head1 AUTHOR 551 552 Written by Sam Hathaway, sh002i (at) math.rochester.edu. 553 554 =cut
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |