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