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