Parent Directory
|
Revision Log
Normalized headers. All files now contain the text below as a header. This is important since all files now (a) use the full name of the package, (b) assign copyright to "The WeBWorK Project", (c) give the full path of the file (relative to CVSROOT) instead of simply the file name, and (d) include license and warranty information. Here is the new header: ################################################################################ # WeBWorK Online Homework Delivery System # Copyright © 2000-2003 The WeBWorK Projcct, http://openwebwork.sf.net/ # $CVSHeader$ # # This program is free software; you can redistribute it and/or modify it under # the terms of either: (a) the GNU General Public License as published by the # Free Software Foundation; either version 2, or (at your option) any later # version, or (b) the "Artistic License" which comes with this package. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the # Artistic License for more details. ################################################################################
1 ################################################################################ 2 # WeBWorK Online Homework Delivery System 3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ 4 # $CVSHeader$ 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::ContentGenerator::Hardcopy; 18 use base qw(WeBWorK::ContentGenerator); 19 20 21 =head1 NAME 22 23 WeBWorK::ContentGenerator::Hardcopy - generate a PDF version of one or more 24 problem sets. 25 26 =cut 27 28 use strict; 29 use warnings; 30 use CGI qw(); 31 use File::Path qw(rmtree); 32 use WeBWorK::Form; 33 use WeBWorK::PG; 34 use WeBWorK::Utils qw(readFile makeTempDirectory); 35 use Apache::Constants qw(:common REDIRECT); 36 37 sub pre_header_initialize { 38 my ($self, $singleSet) = @_; 39 40 my $r = $self->{r}; 41 my $ce = $self->{ce}; 42 my $db = $self->{db}; 43 my @sets = $r->param("hcSet"); 44 my @users = $r->param("hcUser"); 45 my $hardcopy_format = $r->param('hardcopy_format') ? $r->param('hardcopy_format') : ''; 46 47 48 # add singleSet to the list of sets 49 if (length $singleSet > 0) { 50 $singleSet =~ s/^set//; 51 unshift @sets, $singleSet unless grep { $_ eq $singleSet } @sets; 52 } 53 54 # default user is the effectiveUser 55 unless (@users) { 56 unshift @users, $r->param("effectiveUser"); 57 } 58 59 $self->{user} = $db->getUser($r->param("user")); # checked 60 die "user ", $r->param("user"), " (real user) not found." 61 unless $self->{user}; 62 63 $self->{effectiveUser} = $db->getUser($r->param("effectiveUser")); # checked 64 die "user ", $r->param("effectiveUser"), " (effective user) not found." 65 unless $self->{effectiveUser}; 66 67 my $PermissionLevel = $db->getPermissionLevel($r->param("user")); # checked 68 if ($PermissionLevel) { 69 $self->{permissionLevel} = $PermissionLevel->permission(); 70 } else { 71 die "permission level for user ", $r->param("user"), " (real user) not found."; 72 } 73 74 $self->{sets} = \@sets; 75 $self->{users} = \@users; 76 $self->{hardcopy_format} = $hardcopy_format; 77 $self->{errors} = []; 78 $self->{warnings} = []; 79 80 81 # security checks 82 my $multiSet = $self->{permissionLevel} > 0; 83 my $multiUser = $self->{permissionLevel} > 0; 84 85 if (@sets > 1 and not $multiSet) { 86 $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for multiple sets. Please select a single set and try again."]; 87 } 88 if (@users > 1 and not $multiUser) { 89 $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for multiple users. Please select a single user and try again."]; 90 } 91 if ($users[0] ne $self->{effectiveUser}->user_id and not $multiUser) { 92 $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for other users."]; 93 } 94 95 unless ($self->{generationError}) { 96 if ($r->param("generateHardcopy")) { 97 # my ($tempDir, $fileName) = eval { $self->generateHardcopy() }; 98 my ($pdfFileURL) = eval { $self->generateHardcopy() }; 99 100 $self->{generationError} = $@ if $@; 101 # warn "pdfFileURL is $pdfFileURL"; 102 # warn "generation error is ".$self->{generationError}; 103 # warn "hardcopy_format is ".$self->{hardcopy_format}; 104 if ( $self->{generationError} ) { 105 106 # In this case no correct pdf file was generated. 107 # throw the error up higher. 108 # The error is reported in body. 109 # the tempDir was removed in generateHardcopy 110 } elsif ( $self->{hardcopy_format} eq 'tex') { 111 # Only tex output was asked for, proceed to have the tex output handled by the subroutine 112 # "body". 113 114 } else { 115 # information for redirect 116 $self->{pdfFileURL} = $pdfFileURL; 117 } 118 } 119 } 120 } 121 122 sub header { 123 my ($self) = @_; 124 my $r = $self->{r}; 125 126 if (exists $self->{pdfFileURL}) { 127 $r->header_out(Location => $self->{pdfFileURL} ); 128 $self->{noContent} = 1; 129 return REDIRECT; 130 } 131 $r->content_type("text/html"); 132 $r->send_http_header(); 133 } 134 135 # ----- 136 137 sub path { 138 my ($self, undef, $args) = @_; 139 140 my $ce = $self->{ce}; 141 my $root = $ce->{webworkURLs}->{root}; 142 my $courseName = $ce->{courseName}; 143 return $self->pathMacro($args, 144 "Home" => "$root", 145 $courseName => "$root/$courseName", 146 "Hardcopy Generator" => "", 147 ); 148 } 149 150 sub title { 151 return "Hardcopy Generator"; 152 } 153 154 sub body { 155 my $self = shift; 156 157 if ($self->{generationError}) { 158 if (ref $self->{generationError} eq "ARRAY") { 159 my ($disposition, @rest) = @{$self->{generationError}}; 160 if ($disposition eq "PGFAIL") { 161 $self->multiErrorOutput(@{$self->{errors}}); 162 return ""; 163 } elsif ($disposition eq "FAIL") { 164 print $self->errorOutput(@rest); 165 return ""; 166 } elsif ($disposition eq "RETRY") { 167 print $self->errorOutput(@rest); 168 } else { # a "simple" error 169 print CGI::p(CGI::font({-color=>"red"}, @rest)); 170 } 171 } else { 172 # not something we were expecting... 173 die $self->{generationError}; 174 } 175 } 176 if (@{$self->{warnings}}) { 177 # FIXME: this code will only be reached if there was also a 178 # generation error, because otherwise the module will send 179 # the PDF instead. DAMN! 180 $self->multiWarningOutput(@{$self->{warnings}}); 181 } 182 if ($self->{hardcopy_format} eq 'tex') { 183 184 my $r_tex_content = $self->{r_tex_content}; 185 return $$r_tex_content; 186 187 188 } 189 $self->displayForm(); 190 } 191 192 sub multiErrorOutput($@) { 193 my ($self, @errors) = @_; 194 195 print CGI::h2("Compile Errors"); 196 print CGI::p(<<EOF); 197 WeBWorK has encountered one or more errors while attempting to process 198 these problem sets. It is likely that there are errors in the problems 199 themselves. If you are a student, contact your professor to have the errors 200 corrected. If you are a professor, please consult the error output below for 201 more information. 202 EOF 203 foreach my $error (@errors) { 204 my $user = $error->{user}; 205 my $userName = $user->user_id . ' ('.$user->first_name.' '.$user->last_name. ')'; 206 print CGI::h3("Set: ", $error->{set}, ", Problem: ", $error->{problem}, "for $userName"); 207 print CGI::h4("Error messages"), CGI::blockquote(CGI::pre($error->{message})); 208 print CGI::h4("Error context"), CGI::blockquote(CGI::pre($error->{context})); 209 } 210 } 211 212 sub multiWarningOutput($@) { 213 my ($self, @warnings) = @_; 214 215 print CGI::h2("Software Warnings"); 216 print CGI::p(<<EOF); 217 WeBWorK has encountered one or more warnings while attempting to process these 218 problem sets. It is likely that this indicates errors or ambiguitiees in the 219 problems themselves. If you are a student, contact your professor to have the 220 problems corrected. If you are a professor, please consut the warning output 221 below for more informaiton. 222 EOF 223 foreach my $warning (@warnings) { 224 print CGI::h3("Set: ", $warning->{set}, ", Problem: ", $warning->{problem}); 225 print CGI::h4("Error messages"), CGI::blockquote(CGI::pre($warning->{message})); 226 } 227 } 228 229 # ----- 230 231 sub displayForm($) { 232 my $self = shift; 233 my $r = $self->{r}; 234 my $db = $self->{db}; 235 236 print CGI::start_p(), "Select the problem sets for which to generate hardcopy versions."; 237 if ($self->{permissionLevel} > 0) { 238 print "You may also select multiple users from the users list. You will receive hardcopy for each (set, user) pair."; 239 } 240 print CGI::end_p(); 241 242 my $download_texQ = $self->{permissionLevel} > 0; 243 244 # ##########construct action URL ################# 245 my $ce = $self->{ce}; 246 my $root = $ce->{webworkURLs}->{root}; 247 my $courseName = $ce->{courseName}; 248 my $actionURL = "$root/$courseName/hardcopy/"; 249 # ################################################ 250 251 print CGI::start_form(-method=>"POST", -action=>$actionURL); 252 print $self->hidden_authen_fields(); 253 print CGI::h3("Options"); 254 print CGI::p("You may choose to show any of the following data. Correct answers and solutions are only available to privileged users or after the answer date of the problem set."); 255 print CGI::p( 256 CGI::checkbox( 257 -name => "showCorrectAnswers", 258 -checked => $r->param("showCorrectAnswers") || 0, 259 -label => "Correct answers", 260 ), CGI::br(), 261 CGI::checkbox( 262 -name => "showHints", 263 -checked => $r->param("showHints") || 0, 264 -label => "Hints", 265 ), CGI::br(), 266 CGI::checkbox( 267 -name => "showSolutions", 268 -checked => $r->param("showSolutions") || 0, 269 -label => "Solutions", 270 ), 271 ); 272 print CGI::start_table({-width=>"100%"}), CGI::start_Tr({-valign=>"top"}); 273 274 my $multiSet = $self->{permissionLevel} > 0; 275 my $multiUser = $self->{permissionLevel} > 0; 276 my $preOpenSets = $self->{permissionLevel} > 0; 277 my $effectiveUserName = $self->{effectiveUser}->user_id; 278 my @setNames = $db->listUserSets($effectiveUserName); 279 my @sets = $db->getMergedSets( map { [$effectiveUserName, $_] } @setNames ); # checked 280 @sets = grep { defined $_ } @sets; 281 @sets = sort { $a->set_id cmp $b->set_id } @sets; 282 @setNames = map( {$_->set_id } @sets ); # get sorted version of setNames 283 my %setLabels = map( {($_->set_id, "set ".$_->set_id )} @sets ); 284 my (@users, @userNames,%userLabels); 285 286 if ($multiUser) { 287 @userNames = $self->{db}->listUsers(); 288 @users = $self->{db}->getUsers(@userNames); # checked 289 @users = grep { defined $_ } @users; 290 @users = sort { $a->last_name cmp $b->last_name } @users; 291 @userNames = map( {$_->user_id} @users ); # get sorted version of user names 292 %userLabels = map( {($_->user_id , $_->last_name .", ". $_->first_name ." --- ". $_->user_id ) } @users ); 293 } 294 # set selection menu 295 { 296 print CGI::start_td(); 297 my $number_of_sets = @{$self->{sets}}; 298 print CGI::h3("Sets: $number_of_sets pre-selected"); 299 print CGI::scrolling_list(-name=>'hcSet', 300 -values=>\@setNames, 301 -labels=>\%setLabels, 302 -size => 10, 303 -multiple => $multiSet, 304 -defaults => $self->{sets}, 305 ); 306 print CGI::end_td(); 307 } 308 309 # user selection menu 310 if ($multiUser) { 311 print CGI::start_td(); 312 my $number_of_users = @{$self->{users}}; 313 print CGI::h3("Users: $number_of_users pre-selected"); 314 315 print CGI::scrolling_list(-name=>'hcUser', 316 -values=>\@userNames, 317 -labels=>\%userLabels, 318 -size => 10, 319 -multiple => 'true', 320 -defaults => $self->{users}, 321 ); 322 print CGI::end_td(); 323 } 324 325 print CGI::end_Tr(), CGI::end_table(); 326 if ($download_texQ) { # provide choice of pdf or tex output 327 print CGI::p( {-align => "center"}, 328 CGI::radio_group( 329 -name=>"hardcopy_format", 330 -values=>['pdf', 'tex'], 331 -default=>'pdf', 332 -labels=>{'tex'=>'TeX','pdf'=>'PDF'} 333 ), 334 ); 335 } else { # only pdf output available 336 print CGI::hidden(-name=>'hardcopy_format',-value=>'pdf'); 337 } 338 print CGI::p({-align=>"center"}, 339 CGI::submit(-name=>"generateHardcopy", -label=>"Generate Hardcopy")); 340 print CGI::end_form(); 341 342 return ""; 343 } 344 345 sub generateHardcopy($) { 346 my $self = shift; 347 my $ce = $self->{ce}; 348 my @sets = @{$self->{sets}}; 349 my @users = @{$self->{users}}; 350 my $multiSet = $self->{permissionLevel} > 0; 351 my $multiUser = $self->{permissionLevel} > 0; 352 # sanity checks 353 unless (@sets) { 354 die ["RETRY", "No sets were specified."]; 355 } 356 unless (@users) { 357 die ["RETRY", "No users were specified."]; 358 } 359 360 # determine where hardcopy is going to go 361 my $tempDir = makeTempDirectory($ce->{webworkDirs}->{tmp}, "webwork-hardcopy"); 362 363 # determine name of PDF file #FIXME it might be best to have the effective user in here somewhere 364 my $courseName = $self->{ce}->{courseName}; 365 my $fileNameSet = (@sets > 1 ? "multiset" : $sets[0]); 366 my $fileNameUser = (@users > 1 ? "multiuser" : $users[0]); 367 my $fileName = "$courseName.$fileNameUser.$fileNameSet.pdf"; 368 369 # for each user ... generate TeX for each set 370 my $tex; 371 # 372 # the document tex preamble 373 $tex .= $self->texInclude($self->{ce}->{webworkFiles}->{hardcopySnippets}->{preamble}); 374 # separate users by page break, or something 375 foreach my $user (@users) { 376 $tex .= $self->getMultiSetTeX($user, @sets); 377 if (@users) { 378 # separate users, but not after the last set 379 $tex .= $self->texInclude($self->{ce}->{webworkFiles}->{hardcopySnippets}->{userDivider}); 380 } 381 382 } 383 # the document postamble 384 $tex .= $self->texInclude($self->{ce}->{webworkFiles}->{hardcopySnippets}->{postamble}); 385 386 # deal with PG errors 387 if (@{$self->{errors}}) { 388 die ["PGFAIL"]; 389 } 390 391 # FIXME: add something like: 392 #if (@{$self->{warnings}}) { 393 # $self->{generationWarnings} = 1; 394 #} 395 # ??????? 396 397 # "try" to generate pdf or return TeX file 398 my $pdfFileURL = undef; 399 if ($self->{hardcopy_format} eq 'pdf' ) { 400 my $errors = ''; 401 $pdfFileURL = eval { $self->latex2pdf($tex, $tempDir, $fileName) }; 402 if ($@) { 403 $errors = $@; 404 #$errors =~ s/\n/<br>/g; # make this readable on HTML FIXME make this a Utils. filter (Error2HTML) 405 # clean up temp directory 406 # FIXME this clean up done in latex2pdf? rmtree($tempDir); 407 die ["FAIL", "Failed to generate PDF from tex", $errors]; #throw error to subroutine body 408 } else { 409 # pass the relative temp file path back up to go subroutine 410 # to have an appropriate redirect generated. 411 412 413 } 414 } elsif ($self->{hardcopy_format} eq 'tex') { 415 $tex = protect_HTML($tex); 416 #$tex =~ s/\n/\<br\>\n/g; 417 $tex = join('', ("<pre>\n",$tex,"\n</pre>\n")); 418 $self->{r_tex_content} = \$tex; 419 420 421 } else { 422 423 424 die["FAIL", "Hard copy format |".$self->{hardcopy_format}. "| not recognized."]; 425 426 } 427 #return $tempDir, $fileName; 428 # return $pdfFilePath; 429 return $pdfFileURL; 430 } 431 432 # ----- 433 434 sub latex2pdf { 435 # this is a little ad-hoc function which I will replace with a LaTeX 436 # module at some point (or put it in Utils). 437 my ($self, $tex, $tempDir, $fileName) = @_; 438 my $finalFile = "$tempDir/$fileName"; 439 my $ce = $self->{ce}; 440 441 # Location for hardcopy file to be downloaded 442 # FIXME this should use surePathToTmpFile 443 my $hardcopyTempDirectory = $ce->{courseDirs}->{html_temp}."/hardcopy"; 444 mkdir ($hardcopyTempDirectory) or die "Unable to make $hardcopyTempDirectory" unless -e $hardcopyTempDirectory; 445 my $hardcopyFilePath = "$hardcopyTempDirectory/$fileName"; 446 my $hardcopyFileURL = $ce->{courseURLs}->{html_temp}."/hardcopy/$fileName"; 447 $self->{hardcopyFilePath} = $hardcopyFilePath; 448 $self->{hardcopyFileURL} = $hardcopyFileURL; 449 450 ## create a temporary directory for tex to shit in 451 # - we're using the existing temp dir. now 452 453 my $wd = $tempDir; 454 my $texFile = "$wd/hardcopy.tex"; 455 my $pdfFile = "$wd/hardcopy.pdf"; 456 my $logFile = "$wd/hardcopy.log"; 457 458 # write the tex file 459 local *TEX; 460 open TEX, ">", $texFile or die "Failed to open $texFile: $!\n".CGI::br(); 461 print TEX $tex; 462 close TEX; 463 464 # call pdflatex - we don't want to chdir in the mod_perl process, as 465 # that might step on the feet of other things (esp. in Apache 2.0) 466 my $pdflatex = $ce->{externalPrograms}->{pdflatex}; 467 my $pdflatexResult = system "cd $wd && $pdflatex $texFile"; 468 469 # Even with errors there may be a valid pdfFile. Move it to where we can get it. 470 if (-e $pdfFile) { 471 472 # moving to course tmp/hardcopy directory 473 system "/bin/mv", $pdfFile, $hardcopyFilePath 474 and die "Failed to mv: $pdfFile to $hardcopyFilePath<br> Quite likely this means that there ". 475 "is not sufficient write permission for some directory.<br>$!\n".CGI::br(); 476 } 477 # Alert the world that the tex file did not process perfectly. 478 if ($pdflatexResult) { 479 # something bad happened 480 my $textErrorMessage = "Call to $pdflatex failed: $!\n".CGI::br(); 481 482 if (-e $hardcopyFilePath ) { 483 # FIXME Misuse of html tags!!! 484 $textErrorMessage.= "<h4>Some pdf output was produced and is available ". CGI::a({-href=>$hardcopyFileURL},"here.</h4>").CGI::hr(); 485 } 486 # report logfile 487 if (-e $logFile) { 488 $textErrorMessage .= "pdflatex ran, but did not succeed. This suggests an error in the TeX\n".CGI::br(); 489 $textErrorMessage .= "version of one of the problems, or a problem with the pdflatex system.\n".CGI::br(); 490 my $logFileContents = eval { readTexErrorLog($logFile) }; 491 $logFileContents .= CGI::hr().CGI::hr(); 492 $logFileContents .= eval { formatTexFile($texFile) }; 493 if ($@) { 494 $textErrorMessage .= "Additionally, the pdflatex log file could not be read, though it exists.\n".CGI::br(); 495 } else { 496 $textErrorMessage .= "The essential contents of the TeX log are as follows:\n".CGI::hr().CGI::br(); 497 $textErrorMessage .= "$logFileContents\n".CGI::br().CGI::br(); 498 } 499 } else { 500 $textErrorMessage .= "No log file was created, suggesting that pdflatex never ran. Check the WeBWorK\n".CGI::br(); 501 $textErrorMessage .= "configuration to ensure that the path to pdflatex is correct.\n".CGI::br(); 502 } 503 die $textErrorMessage; 504 } 505 506 507 508 ## remove temporary directory 509 rmtree($wd, 0, 0); 510 511 512 -e $hardcopyFilePath or die "Failed to create $finalFile for no apparent reason.\n"; 513 # return hardcopyFilePath; 514 return $hardcopyFileURL; 515 } 516 517 # ----- 518 # FIXME move to Utils? probably not 519 520 sub readTexErrorLog { 521 my $filePath = shift; 522 my $print_error_switch = 0; 523 my $line=''; 524 my @message=(); 525 #local($/ ) = "\n"; 526 open(LOGFILE,"<$filePath") or die "Can't read $filePath"; 527 while (<LOGFILE>) { 528 $line = $_; 529 $print_error_switch = 1 if $line =~ /^!/; # after a fatal error start printing messages 530 push(@message, protect_HTML($line)) if $print_error_switch; 531 } 532 close(LOGFILE); 533 join("<br>\n",@message); 534 } 535 536 sub formatTexFile { 537 my $texFilePath = shift; 538 open (TEXFILE, "$texFilePath") 539 or die "Can't open tex source file: path= $texFilePath: $!"; 540 541 my @message = (); 542 push @message, '<BR>\n<h3>TeX Source File:</h3><BR>\n', ; 543 544 my $lineNumber = 1; 545 while (<TEXFILE>) { 546 push @message, protect_HTML("$lineNumber $_")."\n"; 547 $lineNumber++; 548 } 549 close(TEXFILE); 550 #push @message, '</pre>'; 551 join("<br>\n",@message); 552 } 553 sub protect_HTML { 554 my $line = shift; 555 chomp($line); 556 $line =~s/\&/&/g; 557 $line =~s/</</g; 558 $line =~s/>/>/g; 559 $line; 560 } 561 sub texBlockComment(@) { return "\n".("%"x80)."\n%% ".join("", @_)."\n".("%"x80)."\n\n"; } 562 563 sub getMultiSetTeX { 564 my ($self, $effectiveUserName,@sets) = @_; 565 my $ce = $self->{ce}; 566 my $tex = ""; 567 568 569 570 while (defined (my $setName = shift @sets)) { 571 $tex .= $self->getSetTeX($effectiveUserName, $setName); 572 if (@sets) { 573 # divide sets, but not after the last set 574 $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{setDivider}); 575 } 576 } 577 578 579 580 return $tex; 581 } 582 583 sub getSetTeX { 584 my ($self, $effectiveUserName, $setName) = @_; 585 my $ce = $self->{ce}; 586 my $db = $self->{db}; 587 588 # FIXME (debug code line next) 589 # print STDERR "Creating set $setName for $effectiveUserName \n"; 590 591 # FIXME We could define a default for the effective user if no correct name is passed in. 592 # I'm not sure that it is wise. 593 my $effectiveUser = $db->getUser($effectiveUserName); # checked 594 die "effective user ($effectiveUserName) does not exist." 595 unless defined $effectiveUser; 596 597 my @problemNumbers = sort { $a <=> $b } 598 $db->listUserProblems($effectiveUserName, $setName); 599 600 # get header and footer 601 my $set = $db->getMergedSet($effectiveUserName, $setName); # checked 602 my $setHeader = (ref($set) && $set->set_header) ? $set->set_header : $ce->{webworkFiles}->{hardcopySnippets}->{setHeader}; 603 # database doesn't support the following yet :( 604 #my $setFooter = $wwdb->getMergedSet($effectiveUserName, $setName)->set_footer 605 # || $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; 606 # so we don't allow per-set customization, which is probably okay :) 607 my $setFooter = $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; 608 609 my $tex = ""; 610 611 # render header 612 $tex .= texBlockComment("BEGIN $setName : $setHeader"); 613 $tex .= $self->getProblemTeX($effectiveUser,$setName, 0, $setHeader); 614 615 # render each problem 616 while (my $problemNumber = shift @problemNumbers) { 617 $tex .= texBlockComment("BEGIN $setName : $problemNumber"); 618 $tex .= $self->getProblemTeX($effectiveUser,$setName, $problemNumber); 619 if (@problemNumbers) { 620 # divide problems, but not after the last problem 621 $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{problemDivider}); 622 } 623 } 624 625 # render footer 626 $tex .= texBlockComment("BEGIN $setName : $setFooter"); 627 $tex .= $self->getProblemTeX($effectiveUser,$setName, 0, $setFooter); 628 629 return $tex; 630 } 631 632 sub getProblemTeX { 633 $WeBWorK::timer1 ->continue("hardcopy: begin processing problem") if defined($WeBWorK::timer1); 634 my ($self, $effectiveUser, $setName, $problemNumber, $pgFile) = @_; 635 my $r = $self->{r}; 636 my $ce = $self->{ce}; 637 my $db = $self->{db}; 638 639 # Should we provide a default user ? I think not FIXME 640 641 # $effectiveUser = $self->{effectiveUser} unless defined($effectiveUser); 642 my $permissionLevel = $self->{permissionLevel}; 643 my $set = $db->getMergedSet($effectiveUser->user_id, $setName); # checked 644 unless (ref($set) ) { # return error if no set is defined 645 push(@{$self->{warnings}}, 646 setName => $setName, 647 problem => 0, 648 message => "No set $setName exists for ".$effectiveUser->first_name.' '. 649 $effectiveUser->last_name.' ('.$effectiveUser->user_id.' )' 650 ); 651 return "No set $setName for ".$effectiveUser->user_id; 652 } 653 654 my $psvn = $set->psvn(); 655 656 # decide what to do about problem number 657 my $problem; 658 if ($problemNumber) { # problem number defined and not zero 659 $problem = $db->getMergedProblem($effectiveUser->user_id, $setName, $problemNumber); # checked 660 } elsif ($pgFile) { 661 $problem = WeBWorK::DB::Record::UserProblem->new( 662 set_id => $set->set_id, 663 problem_id => 0, 664 login_id => $effectiveUser->user_id, 665 source_file => $pgFile, 666 # the rest of Problem's fields are not needed, i think 667 ); 668 } 669 unless (ref($problem) ) { # return error if no problem is defined 670 $problemNumber = 'undefined problem number' unless defined($problemNumber); 671 $setName = 'undefined set Name' unless defined($setName); 672 my $msg = "Problem $setName/problem $problemNumber not assigned to ". 673 $effectiveUser->first_name.' '. 674 $effectiveUser->last_name.' ('.$effectiveUser->user_id.' )'; 675 push(@{$self->{warnings}}, 676 setName => $setName, 677 problem => $problemNumber, 678 message => $msg, 679 ); 680 $msg =~ s/_/\\_/; # escape underbars to protect them from TeX FIXME--this could be more general?? 681 return $msg; 682 } 683 # figure out if we're allowed to get solutions and call PG->new accordingly. 684 my $showCorrectAnswers = $r->param("showCorrectAnswers") || 0; 685 my $showHints = $r->param("showHints") || 0; 686 my $showSolutions = $r->param("showSolutions") || 0; 687 unless ($permissionLevel > 0 or time > $set->answer_date) { 688 $showCorrectAnswers = 0; 689 $showSolutions = 0; 690 } 691 692 my $pg = WeBWorK::PG->new( 693 $ce, 694 $effectiveUser, 695 $r->param('key'), 696 $set, 697 $problem, 698 $psvn, 699 {}, # no form fields! 700 { # translation options 701 displayMode => "tex", 702 showHints => $showHints, 703 showSolutions => $showSolutions, 704 processAnswers => $showCorrectAnswers, 705 }, 706 ); 707 708 if ($pg->{warnings} ne "") { 709 push @{$self->{warnings}}, { 710 set => $setName, 711 problem => $problemNumber, 712 message => $pg->{warnings}, 713 }; 714 } 715 716 if ($pg->{flags}->{error_flag}) { 717 push @{$self->{errors}}, { 718 set => $setName, 719 problem => $problemNumber, 720 user => $effectiveUser, 721 message => $pg->{errors}, 722 context => $pg->{body_text}, 723 }; 724 # if there was an error, body_text contains 725 # the error context, not TeX code FIXME (should this error context be used?) 726 $pg->{body_text} = ''; # FIXME using undef causes error unless it is caught undef; 727 } else { 728 # append list of correct answers to body text 729 if ($showCorrectAnswers && $problemNumber != 0) { 730 my $correctTeX = "Correct Answers:\\par\\begin{itemize}\n"; 731 foreach my $ansName (@{$pg->{flags}->{ANSWER_ENTRY_ORDER}}) { 732 my $correctAnswer = $pg->{answers}->{$ansName}->{correct_ans}; 733 $correctAnswer =~ s/\^/\\\^\{\}/g; 734 $correctAnswer =~ s/\_/\\\_/g; 735 $correctTeX .= "\\item $correctAnswer\n"; 736 } 737 $correctTeX .= "\\end{itemize} \\par\n"; 738 $pg->{body_text} .= $correctTeX; 739 } 740 } 741 $WeBWorK::timer1 ->continue("hardcopy: end processing problem") if defined($WeBWorK::timer1); 742 return $pg->{body_text}; 743 } 744 745 sub texInclude { 746 my ($self, $texFile) = @_; 747 my $tex = ""; 748 749 $tex .= texBlockComment("BEGIN: $texFile"); 750 eval { 751 $tex .= readFile($texFile) 752 }; 753 if ($@) { 754 $tex .= texBlockComment($@); 755 } 756 757 return $tex; 758 } 759 760 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |