################################################################################ # WeBWorK Online Homework Delivery System # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm,v 1.54 2005/06/29 02:46:08 gage Exp $ # # 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. ################################################################################ package WeBWorK::ContentGenerator::Hardcopy; use base qw(WeBWorK::ContentGenerator); =head1 NAME WeBWorK::ContentGenerator::Hardcopy - generate a PDF version of one or more problem sets. =cut ################################################################################ ## ## WARNING: This file has been hacked so that it will download ## TeX files rather than displaying them in the browser. ## In particular, if a TeX file is requested then ## the value of the variable $pdfFileURL (in spite of its name) ## will be the URL for the texFile, i.e., ## $pdfFileURL = $texFileURL if TeX file is requested ## ## wheeler@indiana.edu, 7/9/04 ## ################################################################################ use strict; use warnings; use CGI qw(); use File::Path qw(rmtree); use WeBWorK::Form; use WeBWorK::PG; use WeBWorK::Utils qw(readFile makeTempDirectory); use Apache::Constants qw(:common REDIRECT); =head1 CONFIGURATION VARIABLES =over =item $PreserveTempFiles If true, don't delete temporary files. =cut our $PreserveTempFiles = 0 unless defined $PreserveTempFiles; =back =cut sub pre_header_initialize { my ($self) = @_; my $r = $self->r; my $ce = $r->ce; my $db = $r->db; my $authz = $r->authz; my $userID = $r->param("user"); my $singleSet = $r->urlpath->arg("setID"); my @sets = $r->param("hcSet"); my @users = $r->param("hcUser"); my $hardcopy_format = $r->param('hardcopy_format') ? $r->param('hardcopy_format') : ''; # add singleSet to the list of sets if (defined $singleSet and $singleSet ne "") { $singleSet =~ s/^set//; unshift @sets, $singleSet unless grep { $_ eq $singleSet } @sets; } #die "single set is $singleSet and sets is ", join("|",@sets); # default user is the effectiveUser unless (@users) { unshift @users, $r->param("effectiveUser"); } # this should never happen, but apparently it did once (see bug #714), so we check for it die "Parameter 'user' not defined. Can't continue." unless defined $userID; $self->{user} = $db->getUser($userID); # checked die "user ", $userID, " (real user) not found." unless $self->{user}; $self->{effectiveUser} = $db->getUser($r->param("effectiveUser")); # checked die "user ", $r->param("effectiveUser"), " (effective user) not found." unless $self->{effectiveUser}; #my $PermissionLevel = $db->getPermissionLevel($r->param("user")); # checked #if ($PermissionLevel) { # $self->{permissionLevel} = $PermissionLevel->permission(); #} else { # die "permission level for user ", $r->param("user"), " (real user) not found."; #} $self->{sets} = \@sets; $self->{users} = \@users; $self->{hardcopy_format} = $hardcopy_format; $self->{errors} = []; $self->{warnings} = []; # is the user allowed to request multiple sets/users at a time? my $multiSet = $authz->hasPermissions($userID, "download_hardcopy_multiset"); my $multiUser = $authz->hasPermissions($userID, "download_hardcopy_multiuser"); if (@sets > 1 and not $multiSet) { $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for multiple sets. Please select a single set and try again."]; } if (@users > 1 and not $multiUser) { $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for multiple users. Please select a single user and try again."]; } if ($users[0] ne $self->{effectiveUser}->user_id and not $multiUser) { $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for other users."]; } unless ($self->{generationError}) { if ($r->param("generateHardcopy")) { #my ($tempDir, $fileName) = eval { $self->generateHardcopy() }; my ($pdfFileURL) = eval { $self->generateHardcopy() }; $self->{generationError} = $@ if $@; #warn "pdfFileURL is $pdfFileURL"; #warn "generation error is ".$self->{generationError}; #warn "hardcopy_format is ".$self->{hardcopy_format}; if ($self->{generationError}) { # In this case no correct pdf file was generated. # throw the error up higher. # The error is reported in body. # the tempDir was removed in generateHardcopy # } elsif ( $self->{hardcopy_format} eq 'tex') { # # Only tex output was asked for, proceed to have the tex output # # handled by the subroutine "body". } else { # information for redirect $self->{pdfFileURL} = $pdfFileURL; } } } } sub header { my ($self) = @_; my $r = $self->r; if (exists $self->{pdfFileURL}) { $r->header_out(Location => $self->{pdfFileURL} ); $self->{noContent} = 1; return REDIRECT; } $r->content_type("text/html"); $r->send_http_header(); } # ----- #sub path { # my ($self, $args) = @_; # # my $ce = $self->{ce}; # my $root = $ce->{webworkURLs}->{root}; # my $courseName = $ce->{courseName}; # return $self->pathMacro($args, # "Home" => "$root", # $courseName => "$root/$courseName", # "Hardcopy Generator" => "", # ); #} # #sub title { # return "Hardcopy Generator"; #} sub body { my ($self) = @_; if ($self->{generationError}) { if (ref $self->{generationError} eq "ARRAY") { my ($disposition, @rest) = @{$self->{generationError}}; if ($disposition eq "PGFAIL") { $self->multiErrorOutput(@{$self->{errors}}); return ""; } elsif ($disposition eq "FAIL") { print $self->errorOutput(@rest); return ""; } elsif ($disposition eq "RETRY") { print $self->errorOutput(@rest); } else { # a "simple" error print CGI::p(CGI::font({-color=>"red"}, @rest)); } } else { # not something we were expecting... die $self->{generationError}; } } if (@{$self->{warnings}}) { # FIXME: this code will only be reached if there was also a # generation error, because otherwise the module will send # the PDF instead. DAMN! $self->multiWarningOutput(@{$self->{warnings}}); } # if ($self->{hardcopy_format} eq 'tex') { # my $r_tex_content = $self->{r_tex_content}; # return $$r_tex_content; # } $self->displayForm(); } sub multiErrorOutput($@) { my ($self, @errors) = @_; print CGI::h2("Compile Errors"); print CGI::p(<{user}; my $userName = $user->user_id . ' ('.$user->first_name.' '.$user->last_name. ')'; print CGI::h3("Set: ", $error->{set}, ", Problem: ", $error->{problem}, "for $userName"); print CGI::h4("Error messages"), CGI::blockquote(CGI::pre($error->{message})); print CGI::h4("Error context"), CGI::blockquote(CGI::pre($error->{context})); } } sub multiWarningOutput($@) { my ($self, @warnings) = @_; print CGI::h2("Software Warnings"); print CGI::p(<{set}, ", Problem: ", $warning->{problem}); print CGI::h4("Error messages"), CGI::blockquote(CGI::pre($warning->{message})); } } # ----- sub displayForm($) { my ($self) = @_; my $r = $self->r; my $db = $r->db; my $authz = $r->authz; my $userID = $r->param("user"); my $ss= ''; my $aa= ' a '; if ($authz->hasPermissions($userID, "download_hardcopy_multiuser")) { $ss= 's'; $aa= ' '; } print CGI::start_p(), "Select the homework set$ss for which to generate${aa}hardcopy version$ss."; if ($authz->hasPermissions($userID, "download_hardcopy_multiuser")) { print "You may also select multiple users from the users list. You will receive hardcopy for each (set, user) pair."; } print CGI::end_p(); my $download_texQ = $authz->hasPermissions($userID, "download_hardcopy_format_tex"); # ##########construct action URL ################# my $ce = $r->ce; my $root = $ce->{webworkURLs}->{root}; my $courseName = $ce->{courseName}; my $actionURL = "$root/$courseName/hardcopy/"; # ################################################ my $phrase_for_privileged_users = ''; $phrase_for_privileged_users ='to privileged users or' if $authz->hasPermissions($userID, "download_hardcopy_multiuser"); print CGI::start_form(-method=>"POST", -action=>$actionURL); print $self->hidden_authen_fields(); print CGI::h3("Options"); 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."); print CGI::p( CGI::checkbox( -name => "showCorrectAnswers", -checked => $r->param("showCorrectAnswers") || 0, -label => "Correct answers", ), CGI::br(), CGI::checkbox( -name => "showHints", -checked => $r->param("showHints") || 0, -label => "Hints", ), CGI::br(), CGI::checkbox( -name => "showSolutions", -checked => $r->param("showSolutions") || 0, -label => "Solutions", ), ); print CGI::start_table({-width=>"100%"}), CGI::start_Tr({-valign=>"top"}); my $multiSet = $authz->hasPermissions($userID, "download_hardcopy_multiset"); my $multiUser = $authz->hasPermissions($userID, "download_hardcopy_multiuser"); my $preOpenSets = $authz->hasPermissions($userID, "view_unopened_sets"); my $unpublishedSets = $authz->hasPermissions($userID, "view_unpublished_sets"); my $effectiveUserName = $self->{effectiveUser}->user_id; my @setNames = $db->listUserSets($effectiveUserName); my @sets = $db->getMergedSets( map { [$effectiveUserName, $_] } @setNames ); # checked @sets = grep { defined $_ and ($preOpenSets or $_->open_date < time) and ($unpublishedSets or $_->published) } @sets; @sets = sort { $a->set_id cmp $b->set_id } @sets; @setNames = map( {$_->set_id } @sets ); # get sorted version of setNames my %setLabels = map( {($_->set_id, "set ".$_->set_id )} @sets ); my (@users, @userNames,%userLabels); if ($multiUser) { @userNames = $db->listUsers(); @users = $db->getUsers(@userNames); # checked @users = grep { defined $_ } @users; @users = sort { $a->last_name cmp $b->last_name } @users; @userNames = map( {$_->user_id} @users ); # get sorted version of user names %userLabels = map( {($_->user_id , $_->last_name .", ". $_->first_name ." --- ". $_->user_id ) } @users ); } # set selection menu { print CGI::start_td(); my $number_of_sets = @{$self->{sets}}; print CGI::h3("Sets: $number_of_sets pre-selected"); print CGI::scrolling_list(-name=>'hcSet', -values=>\@setNames, -labels=>\%setLabels, -size => 10, -multiple => $multiSet, -defaults => $self->{sets}, ); print CGI::end_td(); } # user selection menu if ($multiUser) { print CGI::start_td(); my $number_of_users = @{$self->{users}}; print CGI::h3("Users: $number_of_users pre-selected"); print CGI::scrolling_list(-name=>'hcUser', -values=>\@userNames, -labels=>\%userLabels, -size => 10, -multiple => 'true', -defaults => $self->{users}, ); print CGI::end_td(); } print CGI::end_Tr(), CGI::end_table(); if ($download_texQ) { # provide choice of pdf or tex output print CGI::p( {-align => "center"}, CGI::radio_group( -name=>"hardcopy_format", -values=>['pdf', 'tex'], -default=>'pdf', -labels=>{'tex'=>'TeX','pdf'=>'PDF'} ), ); } else { # only pdf output available print CGI::hidden(-name=>'hardcopy_format',-value=>'pdf'); } print CGI::p({-align=>"center"}, CGI::submit(-name=>"generateHardcopy", -label=>"Generate Hardcopy")); print CGI::end_form(); return ""; } sub generateHardcopy($) { my ($self) = @_; my $r = $self->r; my $ce = $r->ce; my $authz = $r->authz; my $userID = $r->param("user"); my @sets = @{$self->{sets}}; my @users = @{$self->{users}}; my $multiSet = $authz->hasPermissions($userID, "download_hardcopy_multiset"); my $multiUser = $authz->hasPermissions($userID, "download_hardcopy_multiuser"); # sanity checks unless (@sets) { die ["RETRY", "No sets were specified."]; } unless (@users) { die ["RETRY", "No users were specified."]; } # determine where hardcopy is going to go my $tempDir = makeTempDirectory($ce->{webworkDirs}->{tmp}, "webwork-hardcopy"); # determine name of PDF file #FIXME it might be best to have the effective user in here somewhere my $courseName = $ce->{courseName}; my $fileNameSet = (@sets > 1 ? "multiset" : $sets[0]); my $fileNameUser = (@users > 1 ? "multiuser" : $users[0]); my $fileName = "$courseName.$fileNameUser.$fileNameSet.pdf"; # for each user ... generate TeX for each set my $tex; # # the document tex preamble $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{preamble}); # separate users by page break, or something foreach my $user (@users) { $tex .= $self->getMultiSetTeX($user, @sets); if (@users) { # separate users, but not after the last set $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{userDivider}); } } # the document postamble $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{postamble}); # deal with PG errors if (@{$self->{errors}}) { die ["PGFAIL"]; } # FIXME: add something like: #if (@{$self->{warnings}}) { # $self->{generationWarnings} = 1; #} # ??????? # "try" to generate pdf or return TeX file my $pdfFileURL = undef; if ($self->{hardcopy_format} eq 'pdf' ) { my $errors = ''; $pdfFileURL = eval { $self->latex2pdf($tex, $tempDir, $fileName) }; if ($@) { $errors = $@; #$errors =~ s/\n/
/g; # make this readable on HTML FIXME make this a Utils. filter (Error2HTML) # clean up temp directory # FIXME this clean up done in latex2pdf? rmtree($tempDir); die ["FAIL", "Failed to generate PDF from tex", $errors]; #throw error to subroutine body } else { # pass the relative temp file path back up to go subroutine # to have an appropriate redirect generated. } } elsif ($self->{hardcopy_format} eq 'tex') { my $TeXdownloadFileName = "$courseName.$fileNameUser.$fileNameSet.tex"; # Location for hardcopy file to be downloaded # FIXME this should use surePathToTmpFile my $hardcopyTempDirectory = $ce->{courseDirs}->{html_temp}."/hardcopy"; mkdir ($hardcopyTempDirectory) or die "Unable to make $hardcopyTempDirectory" unless -e $hardcopyTempDirectory; my $hardcopyFilePath = "$hardcopyTempDirectory/$TeXdownloadFileName"; my $hardcopyFileURL = $ce->{courseURLs}->{html_temp}."/hardcopy/$TeXdownloadFileName"; $self->{hardcopyFilePath} = $hardcopyFilePath; $self->{hardcopyFileURL} = $hardcopyFileURL; # write the tex file local *TEX; open TEX, ">", $hardcopyFilePath or die "Failed to open $hardcopyFilePath: $!\n".CGI::br(); print TEX $tex; close TEX; $pdfFileURL = $hardcopyFileURL; if ($PreserveTempFiles) { warn "Temporary directory preserved at '$tempDir'.\n"; } else { rmtree($tempDir); } # $tex = protect_HTML($tex); # #$tex =~ s/\n/\\n/g; # $tex = join('', ("
\n",$tex,"\n
\n")); # $self->{r_tex_content} = \$tex; } else { die["FAIL", "Hard copy format |".$self->{hardcopy_format}. "| not recognized."]; } #return $tempDir, $fileName; # return $pdfFilePath; return $pdfFileURL; } # ----- sub latex2pdf { # this is a little ad-hoc function which I will replace with a LaTeX # module at some point (or put it in Utils). my ($self, $tex, $tempDir, $fileName) = @_; my $r = $self->r; my $ce = $r->ce; my $finalFile = "$tempDir/$fileName"; # Location for hardcopy file to be downloaded # FIXME this should use surePathToTmpFile my $hardcopyTempDirectory = $ce->{courseDirs}->{html_temp}."/hardcopy"; mkdir ($hardcopyTempDirectory) or die "Unable to make $hardcopyTempDirectory" unless -e $hardcopyTempDirectory; my $hardcopyFilePath = "$hardcopyTempDirectory/$fileName"; my $hardcopyFileURL = $ce->{courseURLs}->{html_temp}."/hardcopy/$fileName"; $self->{hardcopyFilePath} = $hardcopyFilePath; $self->{hardcopyFileURL} = $hardcopyFileURL; ## create a temporary directory for tex to shit in # - we're using the existing temp dir. now my $wd = $tempDir; my $texFile = "$wd/hardcopy.tex"; my $pdfFile = "$wd/hardcopy.pdf"; my $logFile = "$wd/hardcopy.log"; # write the tex file local *TEX; open TEX, ">", $texFile or die "Failed to open $texFile: $!\n".CGI::br(); print TEX $tex; close TEX; # call pdflatex - we don't want to chdir in the mod_perl process, as # that might step on the feet of other things (esp. in Apache 2.0) my $pdflatex = $ce->{externalPrograms}->{pdflatex}; my $pdflatexResult = system "cd $wd && $pdflatex $texFile"; # Even with errors there may be a valid pdfFile. Move it to where we can get it. if (-e $pdfFile) { # moving to course tmp/hardcopy directory system "/bin/mv", $pdfFile, $hardcopyFilePath and die "Failed to mv: $pdfFile to $hardcopyFilePath
Quite likely this means that there ". "is not sufficient write permission for some directory.
$!\n".CGI::br(); } # Alert the world that the tex file did not process perfectly. if ($pdflatexResult) { # something bad happened my $textErrorMessage = "Call to $pdflatex failed: $!\n".CGI::br(); if (-e $hardcopyFilePath ) { # FIXME Misuse of html tags!!! $textErrorMessage.= "

Some pdf output was produced and is available ". CGI::a({-href=>$hardcopyFileURL},"here.

").CGI::hr(); } # report logfile if (-e $logFile) { $textErrorMessage .= "pdflatex ran, but did not succeed. This suggests an error in the TeX\n".CGI::br(); $textErrorMessage .= "version of one of the problems, or a problem with the pdflatex system.\n".CGI::br(); my $logFileContents = eval { readTexErrorLog($logFile) }; $logFileContents .= CGI::hr().CGI::hr(); $logFileContents .= eval { formatTexFile($texFile) }; if ($@) { $textErrorMessage .= "Additionally, the pdflatex log file could not be read, though it exists.\n".CGI::br(); } else { $textErrorMessage .= "The essential contents of the TeX log are as follows:\n".CGI::hr().CGI::br(); $textErrorMessage .= "$logFileContents\n".CGI::br().CGI::br(); } } else { $textErrorMessage .= "No log file was created, suggesting that pdflatex never ran. Check the WeBWorK\n".CGI::br(); $textErrorMessage .= "configuration to ensure that the path to pdflatex is correct.\n".CGI::br(); } die $textErrorMessage; } ## remove temporary directory if ($PreserveTempFiles) { warn "Working directory preserved at '$wd'.\n"; } else { rmtree($wd, 0, 0); } -e $hardcopyFilePath or die "Failed to create $finalFile for no apparent reason.\n"; # return hardcopyFilePath; return $hardcopyFileURL; } # ----- # FIXME move to Utils? probably not sub readTexErrorLog { my $filePath = shift; my $print_error_switch = 0; my $line=''; my @message=(); #local($/ ) = "\n"; open(LOGFILE,"<$filePath") or die "Can't read $filePath"; while () { $line = $_; $print_error_switch = 1 if $line =~ /^!/; # after a fatal error start printing messages push(@message, protect_HTML($line)) if $print_error_switch; } close(LOGFILE); join("
\n",@message); } sub formatTexFile { my $texFilePath = shift; open (TEXFILE, "$texFilePath") or die "Can't open tex source file: path= $texFilePath: $!"; my @message = (); push @message, '
\n

TeX Source File:


\n', ; my $lineNumber = 1; while () { push @message, protect_HTML("$lineNumber $_")."\n"; $lineNumber++; } close(TEXFILE); #push @message, ''; join("
\n",@message); } sub protect_HTML { my $line = shift; chomp($line); $line =~s/\&/&/g; $line =~s//>/g; $line; } sub texBlockComment(@) { return "\n".("%"x80)."\n%% ".join("", @_)."\n".("%"x80)."\n\n"; } sub getMultiSetTeX { my ($self, $effectiveUserName,@sets) = @_; my $ce = $self->r->ce; my $tex = ""; while (defined (my $setName = shift @sets)) { $tex .= $self->getSetTeX($effectiveUserName, $setName); if (@sets) { # divide sets, but not after the last set $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{setDivider}); } } return $tex; } sub getSetTeX { my ($self, $effectiveUserName, $setName) = @_; my $r = $self->r; my $ce = $r->ce; my $db = $r->db; # FIXME (debug code line next) # print STDERR "Creating set $setName for $effectiveUserName \n"; # FIXME We could define a default for the effective user if no correct name is passed in. # I'm not sure that it is wise. my $effectiveUser = $db->getUser($effectiveUserName); # checked die "effective user ($effectiveUserName) does not exist." unless defined $effectiveUser; my @problemNumbers = sort { $a <=> $b } $db->listUserProblems($effectiveUserName, $setName); # get header and footer my $set = $db->getMergedSet($effectiveUserName, $setName); # checked my $setHeader = (ref($set) && $set->hardcopy_header) ? $set->hardcopy_header: $ce->{webworkFiles}->{hardcopySnippets}->{setHeader}; # database doesn't support the following yet :( #my $setFooter = $wwdb->getMergedSet($effectiveUserName, $setName)->set_footer # || $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; # so we don't allow per-set customization, which is probably okay :) my $setFooter = $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; my $tex = ""; # render header $tex .= texBlockComment("BEGIN $setName : $setHeader"); $tex .= $self->getProblemTeX($effectiveUser,$setName, 0, $setHeader); # render each problem while (my $problemNumber = shift @problemNumbers) { # # DPVC -- do problem divider ABOVE the problem, rather than below it # $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{problemDivider}); # # /DPVC # $tex .= texBlockComment("BEGIN $setName : $problemNumber"); $tex .= $self->getProblemTeX($effectiveUser,$setName, $problemNumber); # # DPVC -- no need for it here since we do it above # #if (@problemNumbers) { # # divide problems, but not after the last problem # $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{problemDivider}); #} # # /DPVC # } # render footer $tex .= texBlockComment("BEGIN $setName : $setFooter"); $tex .= $self->getProblemTeX($effectiveUser,$setName, 0, $setFooter); return $tex; } sub getProblemTeX { $WeBWorK::timer1 ->continue("hardcopy: begin processing problem") if defined($WeBWorK::timer1); my ($self, $effectiveUser, $setName, $problemNumber, $pgFile) = @_; my $r = $self->r; my $ce = $r->ce; my $db = $r->db; my $authz = $r->authz; my $userID = $r->param("user"); # Should we provide a default user ? I think not FIXME # $effectiveUser = $self->{effectiveUser} unless defined($effectiveUser); my $permissionLevel = $self->{permissionLevel}; my $set = $db->getMergedSet($effectiveUser->user_id, $setName); # checked unless (ref($set) ) { # return error if no set is defined push(@{$self->{warnings}}, setName => $setName, problem => 0, message => "No set $setName exists for ".$effectiveUser->first_name.' '. $effectiveUser->last_name.' ('.$effectiveUser->user_id.' )' ); return "No set $setName for ".$effectiveUser->user_id; } my $preOpenSets = $authz->hasPermissions($userID, "view_unopened_sets"); my $unpublishedSets = $authz->hasPermissions($userID, "view_unpublished_sets"); unless ( ($preOpenSets or $set->open_date < time) and ($unpublishedSets or $set->published) ) { # return error if set is invisible push(@{$self->{warnings}}, setName => $setName, problem => 0, message => "The set $setName is hidden for ".$effectiveUser->first_name.' '. $effectiveUser->last_name.' ('.$effectiveUser->user_id.' )' ); return "The set $setName is not yet ready for ".$effectiveUser->user_id; } my $psvn = $set->psvn(); # decide what to do about problem number my $problem; if ($problemNumber) { # problem number defined and not zero $problem = $db->getMergedProblem($effectiveUser->user_id, $setName, $problemNumber); # checked } elsif ($pgFile) { $problem = WeBWorK::DB::Record::UserProblem->new( set_id => $set->set_id, problem_id => 0, login_id => $effectiveUser->user_id, source_file => $pgFile, # the rest of Problem's fields are not needed, i think ); } unless (ref($problem) ) { # return error if no problem is defined $problemNumber = 'undefined problem number' unless defined($problemNumber); $setName = 'undefined set Name' unless defined($setName); my $msg = "Problem $setName/problem $problemNumber not assigned to ". $effectiveUser->first_name.' '. $effectiveUser->last_name.' ('.$effectiveUser->user_id.' )'; push(@{$self->{warnings}}, setName => $setName, problem => $problemNumber, message => $msg, ); $msg =~ s/_/\\_/; # escape underbars to protect them from TeX FIXME--this could be more general?? return $msg; } # figure out if we're allowed to get solutions and call PG->new accordingly. my $showCorrectAnswers = $r->param("showCorrectAnswers") || 0; my $showHints = $r->param("showHints") || 0; my $showSolutions = $r->param("showSolutions") || 0; unless ($authz->hasPermissions($userID, "view_answers") or time > $set->answer_date) { $showCorrectAnswers = 0; $showSolutions = 0; } ##FIXME -- there can be a problem if the $siteDefaults{timezone} is not defined? Why is this? # why does it only occur with hardcopy? my $pg = WeBWorK::PG->new( $ce, $effectiveUser, $r->param('key'), $set, $problem, $psvn, {}, # no form fields! { # translation options displayMode => "tex", showHints => ($showHints)? 1:0, # insure that this value is numeric showSolutions => ($showSolutions)? 1:0, processAnswers => ($showCorrectAnswers)? 1:0, }, ); if ($pg->{warnings} ne "") { push @{$self->{warnings}}, { set => $setName, problem => $problemNumber, message => $pg->{warnings}, }; } if ($pg->{flags}->{error_flag}) { push @{$self->{errors}}, { set => $setName, problem => $problemNumber, user => $effectiveUser, message => $pg->{errors}, context => $pg->{body_text}, }; # if there was an error, body_text contains # the error context, not TeX code FIXME (should this error context be used?) $pg->{body_text} = ''; # FIXME using undef causes error unless it is caught undef; } else { # append list of correct answers to body text if ($showCorrectAnswers && $problemNumber != 0) { # # DPVC -- Adjusted spacing here, and added \small and italics. # Put the answer in verbatim mode to make it display as typed # by the author, rather than use hacks for ^ and _. What about # vectors (where TeX will complain about < and > outside of # math mode)? Do we need hacks for them, too? # This also fixes a bug when the answer begins with [ # where \item would think this was an optional parameter # (otherwise we need to do "\\item{}$correctanswer\n"). # my $correctTeX = "\\par{\\small{\\it Correct Answers:}\n" . "\\vspace{-\\parskip}\\begin{itemize}\n"; foreach my $ansName (@{$pg->{flags}->{ANSWER_ENTRY_ORDER}}) { my $correctAnswer = $pg->{answers}->{$ansName}->{correct_ans}; #$correctAnswer =~ s/\^/\\\^\{\}/g; #$correctAnswer =~ s/\_/\\\_/g; $correctTeX .= "\\item\\begin{verbatim}$correctAnswer\\end{verbatim}\n"; } $correctTeX .= "\\end{itemize}}\\par\n"; # # /DPVC # $pg->{body_text} .= $correctTeX; } } $WeBWorK::timer1 ->continue("hardcopy: end processing problem") if defined($WeBWorK::timer1); return $pg->{body_text}; } sub texInclude { my ($self, $texFile) = @_; my $tex = ""; $tex .= texBlockComment("BEGIN: $texFile"); eval { $tex .= readFile($texFile) }; if ($@) { $tex .= texBlockComment($@); } return $tex; } 1;