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