Parent Directory
|
Revision Log
don't load Apache::Log via global.conf anymore -- special-case it in WeBWorK::PG::Local instead. this allows for selective loading of Apache::Log for Apache1 or Apache2::Log and APR::Table for Apache2.
1 ################################################################################ 2 # WeBWorK Online Homework Delivery System 3 # Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/ 4 # $CVSHeader: webwork2/lib/WeBWorK/PG/Local.pm,v 1.20 2006/05/21 00:50:04 gage Exp $ 5 # 6 # This program is free software; you can redistribute it and/or modify it under 7 # the terms of either: (a) the GNU General Public License as published by the 8 # Free Software Foundation; either version 2, or (at your option) any later 9 # version, or (b) the "Artistic License" which comes with this package. 10 # 11 # This program is distributed in the hope that it will be useful, but WITHOUT 12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 13 # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the 14 # Artistic License for more details. 15 ################################################################################ 16 17 package WeBWorK::PG::Local; 18 use base qw(WeBWorK::PG); 19 20 =head1 NAME 21 22 WeBWorK::PG::Local - Use the WeBWorK::PG API to invoke a local 23 WeBWorK::PG::Translator object. 24 25 =head1 DESCRIPTION 26 27 WeBWorK::PG::Local encapsulates the PG translation process, making multiple 28 calls to WeBWorK::PG::Translator. Much of the flexibility of the Translator is 29 hidden, instead making choices that are appropriate for the webwork2 30 system 31 32 It implements the WeBWorK::PG interface and uses a local 33 WeBWorK::PG::Translator to perform problem rendering. See the documentation for 34 the WeBWorK::PG module for information about the API. 35 36 =cut 37 38 use strict; 39 use warnings; 40 use WeBWorK::Constants; 41 use File::Path qw(rmtree); 42 use WeBWorK::PG::Translator; 43 use WeBWorK::Utils qw(readFile writeTimingLogEntry); 44 45 use mod_perl; 46 use constant MP2 => ( exists $ENV{MOD_PERL_API_VERSION} and $ENV{MOD_PERL_API_VERSION} >= 2 ); 47 48 # Problem processing will time out after this number of seconds. 49 use constant TIMEOUT => $WeBWorK::PG::Local::TIMEOUT || 10; 50 51 BEGIN { 52 # This safe compartment is used to read the large macro files such as 53 # PG.pl, PGbasicmacros.pl and PGanswermacros and cache the results so that 54 # future calls have preloaded versions of these large files. This saves a 55 # significant amount of time. 56 $WeBWorK::PG::Local::safeCache = new Safe; 57 } 58 59 sub new { 60 my $invocant = shift; 61 local $SIG{ALRM} = \&alarm_handler; 62 alarm TIMEOUT; 63 my $result = eval { $invocant->new_helper(@_) }; 64 alarm 0; 65 die $@ if $@; 66 return $result; 67 } 68 69 sub alarm_handler { 70 my $msg = "Timeout after processing this problem for ". TIMEOUT. " seconds. Check for infinite loops in problem source.\n"; 71 warn $msg; 72 die $msg; 73 74 } 75 sub new_helper { 76 my $invocant = shift; 77 my $class = ref($invocant) || $invocant; 78 my ( 79 $ce, 80 $user, 81 $key, 82 $set, 83 $problem, 84 $psvn, 85 $formFields, # in CGI::Vars format 86 $translationOptions, # hashref containing options for the 87 # translator, such as whether to show 88 # hints and the display mode to use 89 ) = @_; 90 91 # write timing log entry 92 # writeTimingLogEntry($ce, "WeBWorK::PG::new", 93 # "user=".$user->user_id.",problem=".$ce->{courseName}."/".$set->set_id."/".$problem->problem_id.",mode=".$translationOptions->{displayMode}, 94 # "begin"); 95 96 # install a local warn handler to collect warnings 97 my $warnings = ""; 98 local $SIG{__WARN__} = sub { $warnings .= shift } 99 if $ce->{pg}->{options}->{catchWarnings}; 100 101 # create a Translator 102 #warn "PG: creating a Translator\n"; 103 my $translator = WeBWorK::PG::Translator->new; 104 105 # set the directory hash 106 #warn "PG: setting the directory hash\n"; 107 $translator->rh_directories({ 108 macrosPath => $ce->{courseDirs}->{macrosPath}, 109 templateDirectory => $ce->{courseDirs}->{templates}, 110 tempDirectory => $ce->{courseDirs}->{html_temp}, 111 }); 112 113 # evaluate modules and "extra packages" 114 #warn "PG: evaluating modules and \"extra packages\"\n"; 115 my @modules = @{ $ce->{pg}->{modules} }; 116 # HACK for apache2 117 if (MP2) { 118 push @modules, ["Apache2::Log"], ["APR::Table"]; 119 } else { 120 push @modules, ["Apache::Log"]; 121 } 122 foreach my $module_packages_ref (@modules) { 123 my ($module, @extra_packages) = @$module_packages_ref; 124 # the first item is the main package 125 $translator->evaluate_modules($module); 126 # the remaining items are "extra" packages 127 $translator->load_extra_packages(@extra_packages); 128 } 129 130 # set the environment (from defineProblemEnvir) 131 #warn "PG: setting the environment (from defineProblemEnvir)\n"; 132 my $envir = $class->defineProblemEnvir( 133 $ce, 134 $user, 135 $key, 136 $set, 137 $problem, 138 $psvn, 139 $formFields, 140 $translationOptions, 141 ); 142 $translator->environment($envir); 143 144 # initialize the Translator 145 #warn "PG: initializing the Translator\n"; 146 $translator->initialize(); 147 148 # Preload the macros files which are used routinely: PG.pl, 149 # dangerousMacros.pl, IO.pl, PGbasicmacros.pl, and PGanswermacros.pl 150 # (Preloading the last two files safes a significant amount of time.) 151 # 152 # IO.pl, PG.pl, and dangerousMacros.pl are loaded using 153 # unrestricted_load This is hard wired into the 154 # Translator::pre_load_macro_files subroutine. I'd like to change this 155 # at some point to have the same sort of interface to global.conf that 156 # the module loading does -- have a list of macros to load 157 # unrestrictedly. 158 # 159 # This has been replaced by the pre_load_macro_files subroutine. It 160 # loads AND caches the files. While PG.pl and dangerousMacros are not 161 # large, they are referred to by PGbasicmacros and PGanswermacros. 162 # Because these are loaded into the cached name space (e.g. 163 # Safe::Root1::) all calls to, say NEW_ANSWER_NAME are actually calls 164 # to Safe::Root1::NEW_ANSWER_NAME. It is useful to have these names 165 # inside the Safe::Root1: cached safe compartment. (NEW_ANSWER_NAME 166 # and all other subroutine names are also automatically exported into 167 # the current safe compartment Safe::Rootx:: 168 # 169 # The headers of both PGbasicmacros and PGanswermacros has code that 170 # insures that the constants used are imported into the current safe 171 # compartment. This involves evaluating references to, say 172 # $main::displayMode, at runtime to insure that main refers to 173 # Safe::Rootx:: and NOT to Safe::Root1::, which is the value of main:: 174 # at compile time. 175 # 176 # TO ENABLE CACHEING UNCOMMENT THE FOLLOWING: 177 eval{$translator->pre_load_macro_files( 178 $WeBWorK::PG::Local::safeCache, 179 $ce->{pg}->{directories}->{macros}, 180 'PG.pl', 'dangerousMacros.pl','IO.pl','PGbasicmacros.pl','PGanswermacros.pl' 181 )}; 182 warn "Error while preloading macro files: $@" if $@; 183 184 # STANDARD LOADING CODE: for cached script files, this merely 185 # initializes the constants. 186 foreach (qw(PG.pl dangerousMacros.pl IO.pl)) { 187 my $macroPath = $ce->{pg}->{directories}->{macros} . "/$_"; 188 my $err = $translator->unrestricted_load($macroPath); 189 warn "Error while loading $macroPath: $err" if $err; 190 } 191 192 # set the opcode mask (using default values) 193 #warn "PG: setting the opcode mask (using default values)\n"; 194 $translator->set_mask(); 195 196 # store the problem source 197 #warn "PG: storing the problem source\n"; 198 my $source =''; 199 my $sourceFilePath = ''; 200 my $readErrors = undef; 201 if (ref($translationOptions->{r_source}) ) { 202 # the source for the problem is already given to us as a reference to a string 203 $source = ${$translationOptions->{r_source}}; 204 } else { 205 # the source isn't given to us so we need to read it 206 # from a file defined by the problem 207 208 # we grab the sourceFilePath from the problem 209 $sourceFilePath = $problem->source_file; 210 211 # the path to the source file is usually given relative to the 212 # the templates directory. Unless the path starts with / assume 213 # that it is relative to the templates directory 214 215 $sourceFilePath = $ce->{courseDirs}->{templates}."/" 216 .$sourceFilePath unless ($sourceFilePath =~ /^\//); 217 #now grab the source 218 eval {$source = readFile($sourceFilePath) }; 219 $readErrors = $@ if $@; 220 } 221 # put the source into the translator object 222 eval { $translator->source_string( $source ) } unless $readErrors; 223 $readErrors .="\n $@ " if $@; 224 if ($readErrors) { 225 # well, we couldn't get the problem source, for some reason. 226 return bless { 227 translator => $translator, 228 head_text => "", 229 body_text => <<EOF, 230 WeBWorK::Utils::readFile($sourceFilePath) says: 231 $@ 232 EOF 233 answers => {}, 234 result => {}, 235 state => {}, 236 errors => "Failed to read the problem source file.", 237 warnings => $warnings, 238 flags => {error_flag => 1}, 239 }, $class; 240 } 241 242 # install a safety filter 243 #warn "PG: installing a safety filter\n"; 244 #$translator->rf_safety_filter(\&oldSafetyFilter); 245 $translator->rf_safety_filter(\&WeBWorK::PG::nullSafetyFilter); 246 247 # write timing log entry -- the translator is now all set up 248 # writeTimingLogEntry($ce, "WeBWorK::PG::new", 249 # "initialized", 250 # "intermediate"); 251 252 # translate the PG source into text 253 #warn "PG: translating the PG source into text\n"; 254 $translator->translate(); 255 256 # after we're done translating, we may have to clean up after the 257 # translator: 258 259 # for example, HTML_img mode uses a tempdir for dvipng's temp files.\ 260 # We have to remove it. 261 if ($envir->{dvipngTempDir}) { 262 rmtree($envir->{dvipngTempDir}, 0, 0); 263 } 264 265 # HTML_dpng, on the other hand, uses an ImageGenerator. We have to 266 # render the queued equations. 267 my $body_text_ref = $translator->r_text; 268 if ($envir->{imagegen}) { 269 my $sourceFile = $ce->{courseDirs}->{templates} . "/" . $problem->source_file; 270 my %mtimeOption = -e $sourceFile 271 ? (mtime => (stat $sourceFile)[9]) 272 : (); 273 274 $envir->{imagegen}->render( 275 refresh => $translationOptions->{refreshMath2img}, 276 %mtimeOption, 277 body_text => $body_text_ref, 278 ); 279 } 280 281 my ($result, $state); # we'll need these on the other side of the if block! 282 if ($translationOptions->{processAnswers}) { 283 284 # process student answers 285 #warn "PG: processing student answers\n"; 286 $translator->process_answers($formFields); 287 288 # retrieve the problem state and give it to the translator 289 #warn "PG: retrieving the problem state and giving it to the translator\n"; 290 $translator->rh_problem_state({ 291 recorded_score => $problem->status, 292 num_of_correct_ans => $problem->num_correct, 293 num_of_incorrect_ans => $problem->num_incorrect, 294 }); 295 296 # determine an entry order -- the ANSWER_ENTRY_ORDER flag is built by 297 # the PG macro package (PG.pl) 298 #warn "PG: determining an entry order\n"; 299 my @answerOrder = 300 $translator->rh_flags->{ANSWER_ENTRY_ORDER} 301 ? @{ $translator->rh_flags->{ANSWER_ENTRY_ORDER} } 302 : keys %{ $translator->rh_evaluated_answers }; 303 304 # install a grader -- use the one specified in the problem, 305 # or fall back on the default from the course environment. 306 # (two magic strings are accepted, to avoid having to 307 # reference code when it would be difficult.) 308 #warn "PG: installing a grader\n"; 309 my $grader = $translator->rh_flags->{PROBLEM_GRADER_TO_USE} 310 || $ce->{pg}->{options}->{grader}; 311 $grader = $translator->rf_std_problem_grader 312 if $grader eq "std_problem_grader"; 313 $grader = $translator->rf_avg_problem_grader 314 if $grader eq "avg_problem_grader"; 315 die "Problem grader $grader is not a CODE reference." 316 unless ref $grader eq "CODE"; 317 $translator->rf_problem_grader($grader); 318 319 # grade the problem 320 #warn "PG: grading the problem\n"; 321 ($result, $state) = $translator->grade_problem( 322 answers_submitted => $translationOptions->{processAnswers}, 323 ANSWER_ENTRY_ORDER => \@answerOrder, 324 %{$formFields}, #FIXME? this is used by sequentialGrader is there a better way? 325 ); 326 327 } 328 329 # write timing log entry 330 # writeTimingLogEntry($ce, "WeBWorK::PG::new", "", "end"); 331 332 # return an object which contains the translator and the results of 333 # the translation process. this is DIFFERENT from the "format expected 334 # by Webwork.pm (and I believe processProblem8, but check.)" 335 return bless { 336 translator => $translator, 337 head_text => ${ $translator->r_header }, 338 body_text => ${ $body_text_ref }, 339 answers => $translator->rh_evaluated_answers, 340 result => $result, 341 state => $state, 342 errors => $translator->errors, 343 warnings => $warnings, 344 flags => $translator->rh_flags, 345 }, $class; 346 } 347 348 1; 349 350 __END__ 351 352 =head1 OPERATION 353 354 WeBWorK::PG::Local goes through the following operations when constructed: 355 356 =over 357 358 =item Create a translator 359 360 Instantiate a WeBWorK::PG::Translator object. 361 362 =item Set the directory hash 363 364 Set the translator's directory hash (courseScripts, macros, templates, and temp 365 directories) from the course environment. 366 367 =item Evaluate PG modules 368 369 Using the module list from the course environment (pg->modules), perform a 370 "use"-like operation to evaluate modules at runtime. 371 372 =item Set the problem environment 373 374 Use data from the user, set, and problem, as well as the course 375 environemnt and translation options, to set the problem environment. The 376 default subroutine, &WeBWorK::PG::defineProblemEnvir, is used. 377 378 =item Initialize the translator 379 380 Call &WeBWorK::PG::Translator::initialize. What more do you want? 381 382 =item Load IO.pl, PG.pl and dangerousMacros.pl 383 384 These macros must be loaded without opcode masking, so they are loaded here. 385 386 =item Set the opcode mask 387 388 Set the opcode mask to the default specified by WeBWorK::PG::Translator. 389 390 =item Load the problem source 391 392 Give the problem source to the translator. 393 394 =item Install a safety filter 395 396 The safety filter is used to preprocess student input before evaluation. The 397 default safety filter, &WeBWorK::PG::safetyFilter, is used. 398 399 =item Translate the problem source 400 401 Call &WeBWorK::PG::Translator::translate to render the problem source into the 402 format given by the display mode. 403 404 =item Process student answers 405 406 Use form field inputs to evaluate student answers. 407 408 =item Load the problem state 409 410 Use values from the database to initialize the problem state, so that the 411 grader will have a point of reference. 412 413 =item Determine an entry order 414 415 Use the ANSWER_ENTRY_ORDER flag to determine the order of answers in the 416 problem. This is important for problems with dependancies among parts. 417 418 =item Install a grader 419 420 Use the PROBLEM_GRADER_TO_USE flag, or a default from the course environment, 421 to install a grader. 422 423 =item Grade the problem 424 425 Use the selected grader to grade the problem. 426 427 =back 428 429 =head1 AUTHOR 430 431 Written by Sam Hathaway, sh002i (at) math.rochester.edu. 432 433 =cut
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |