| 1 | ################################################################################ |
1 | ################################################################################ |
| 2 | # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project |
2 | # WeBWorK Online Homework Delivery System |
| 3 | # $Id$ |
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. |
| 4 | ################################################################################ |
15 | ################################################################################ |
| 5 | |
16 | |
| 6 | package WeBWorK::ContentGenerator::Hardcopy; |
17 | package WeBWorK::ContentGenerator::Hardcopy; |
|
|
18 | use base qw(WeBWorK::ContentGenerator); |
|
|
19 | |
| 7 | |
20 | |
| 8 | =head1 NAME |
21 | =head1 NAME |
| 9 | |
22 | |
| 10 | WeBWorK::ContentGenerator::Hardcopy - generate a PDF version of one or more |
23 | WeBWorK::ContentGenerator::Hardcopy - generate a PDF version of one or more |
| 11 | problem sets. |
24 | problem sets. |
| 12 | |
25 | |
| 13 | =cut |
26 | =cut |
| 14 | |
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 | |
| 15 | use strict; |
41 | use strict; |
| 16 | use warnings; |
42 | use warnings; |
| 17 | use base qw(WeBWorK::ContentGenerator); |
|
|
| 18 | use CGI qw(); |
43 | use CGI qw(); |
| 19 | use File::Path qw(rmtree); |
44 | use File::Path qw(rmtree); |
| 20 | use File::Temp qw(tempdir); |
|
|
| 21 | use WeBWorK::DB::Classlist; |
|
|
| 22 | use WeBWorK::DB::WW; |
|
|
| 23 | use WeBWorK::Form; |
45 | use WeBWorK::Form; |
|
|
46 | use WeBWorK::PG; |
| 24 | use WeBWorK::Utils qw(readFile); |
47 | use WeBWorK::Utils qw(readFile makeTempDirectory); |
|
|
48 | use Apache::Constants qw(:common REDIRECT); |
| 25 | |
49 | |
| 26 | sub go { |
50 | =head1 CONFIGURATION VARIABLES |
| 27 | my ($self, $singleSet) = @_; |
51 | |
| 28 | |
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) = @_; |
| 29 | my $r = $self->{r}; |
68 | my $r = $self->r; |
| 30 | my $ce = $self->{courseEnvironment}; |
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"); |
| 31 | my @sets = $r->param("hcSet"); |
75 | my @sets = $r->param("hcSet"); |
| 32 | my @users = $r->param("hcUser"); |
76 | my @users = $r->param("hcUser"); |
| 33 | |
77 | my $hardcopy_format = $r->param('hardcopy_format') ? $r->param('hardcopy_format') : ''; |
|
|
78 | |
| 34 | # add singleSet to the list of sets |
79 | # add singleSet to the list of sets |
| 35 | if (length $singleSet > 0) { |
80 | if (defined $singleSet and $singleSet ne "") { |
| 36 | $singleSet =~ s/^set//; |
81 | $singleSet =~ s/^set//; |
| 37 | unshift @sets, $singleSet unless grep { $_ eq $singleSet } @sets; |
82 | unshift @sets, $singleSet unless grep { $_ eq $singleSet } @sets; |
| 38 | } |
83 | } |
| 39 | |
84 | #die "single set is $singleSet and sets is ", join("|",@sets); |
| 40 | # default user is the effectiveUser |
85 | # default user is the effectiveUser |
| 41 | unless (@users) { |
86 | unless (@users) { |
| 42 | unshift @users, $r->param("effectiveUser"); |
87 | unshift @users, $r->param("effectiveUser"); |
| 43 | } |
88 | } |
| 44 | |
89 | |
| 45 | $self->{cldb} = WeBWorK::DB::Classlist->new($ce); |
90 | # this should never happen, but apparently it did once (see bug #714), so we check for it |
| 46 | $self->{authdb} = WeBWorK::DB::Auth->new($ce); |
91 | die "Parameter 'user' not defined. Can't continue." unless defined $userID; |
| 47 | $self->{wwdb} = WeBWorK::DB::WW->new($ce); |
92 | |
| 48 | $self->{user} = $self->{cldb}->getUser($r->param("user")); |
93 | $self->{user} = $db->getUser($userID); # checked |
| 49 | $self->{permissionLevel} = $self->{authdb}->getPermissions($r->param("user")); |
94 | die "user ", $userID, " (real user) not found." |
|
|
95 | unless $self->{user}; |
|
|
96 | |
| 50 | $self->{effectiveUser} = $self->{cldb}->getUser($r->param("effectiveUser")); |
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 | |
| 51 | $self->{sets} = \@sets; |
108 | $self->{sets} = \@sets; |
| 52 | $self->{users} = \@users; |
109 | $self->{users} = \@users; |
|
|
110 | $self->{hardcopy_format} = $hardcopy_format; |
| 53 | $self->{errors} = []; |
111 | $self->{errors} = []; |
| 54 | $self->{warnings} = []; |
112 | $self->{warnings} = []; |
| 55 | |
113 | |
| 56 | # security checks - these have to be put somewhere |
114 | # is the user allowed to request multiple sets/users at a time? |
| 57 | my $multiSet = $self->{permissionLevel} > 0; |
115 | my $multiSet = $authz->hasPermissions($userID, "download_hardcopy_multiset"); |
| 58 | my $multiUser = $self->{permissionLevel} > 0; |
116 | my $multiUser = $authz->hasPermissions($userID, "download_hardcopy_multiuser"); |
|
|
117 | |
| 59 | if (@sets > 1 and not $multiSet) { |
118 | if (@sets > 1 and not $multiSet) { |
| 60 | $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for multiple sets. Please select a single set and try again."]; |
119 | $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for multiple sets. Please select a single set and try again."]; |
| 61 | } |
120 | } |
| 62 | if (@users > 1 and not $multiUser) { |
121 | if (@users > 1 and not $multiUser) { |
| 63 | $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for multiple users. Please select a single user and try again."]; |
122 | $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for multiple users. Please select a single user and try again."]; |
| 64 | } |
123 | } |
| 65 | if ($users[0] ne $self->{effectiveUser}->id and not $multiUser) { |
124 | if ($users[0] ne $self->{effectiveUser}->user_id and not $multiUser) { |
| 66 | $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for other users."]; |
125 | $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for other users."]; |
| 67 | } |
126 | } |
| 68 | |
127 | |
| 69 | unless ($self->{generationError}) { |
128 | unless ($self->{generationError}) { |
| 70 | if ($r->param("generateHardcopy")) { |
129 | if ($r->param("generateHardcopy")) { |
| 71 | my ($tempDir, $fileName) = eval { $self->generateHardcopy() }; |
130 | #my ($tempDir, $fileName) = eval { $self->generateHardcopy() }; |
| 72 | if ($@) { |
131 | my ($pdfFileURL) = eval { $self->generateHardcopy() }; |
|
|
132 | |
| 73 | $self->{generationError} = $@; |
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". |
| 74 | } else { |
145 | } else { |
| 75 | my $filePath = "$tempDir/$fileName"; |
146 | # information for redirect |
| 76 | |
147 | $self->{pdfFileURL} = $pdfFileURL; |
| 77 | $r->content_type("application/x-pdf"); |
|
|
| 78 | # as per RFC2183: |
|
|
| 79 | $r->header_out("Content-Disposition", "attachment; filename=$fileName"); |
|
|
| 80 | $r->send_http_header(); |
|
|
| 81 | |
|
|
| 82 | local *INPUTFILE; |
|
|
| 83 | open INPUTFILE, "<", $filePath |
|
|
| 84 | or die "Failed to read $filePath: $!"; |
|
|
| 85 | my $buf; |
|
|
| 86 | while (read INPUTFILE, $buf, 16384) { |
|
|
| 87 | print $buf; |
|
|
| 88 | } |
|
|
| 89 | close INPUTFILE; |
|
|
| 90 | |
|
|
| 91 | return; |
|
|
| 92 | } |
148 | } |
| 93 | } |
149 | } |
| 94 | } |
150 | } |
|
|
151 | } |
|
|
152 | |
|
|
153 | sub header { |
|
|
154 | my ($self) = @_; |
|
|
155 | my $r = $self->r; |
| 95 | |
156 | |
|
|
157 | if (exists $self->{pdfFileURL}) { |
|
|
158 | $r->header_out(Location => $self->{pdfFileURL} ); |
|
|
159 | $self->{noContent} = 1; |
|
|
160 | return REDIRECT; |
|
|
161 | } |
| 96 | $r->content_type("text/html"); |
162 | $r->content_type("text/html"); |
| 97 | $r->send_http_header(); |
163 | $r->send_http_header(); |
| 98 | $self->template($ce->{templates}->{system}, $singleSet); |
|
|
| 99 | } |
164 | } |
| 100 | |
165 | |
| 101 | # ----- |
166 | # ----- |
| 102 | |
167 | |
| 103 | sub path { |
168 | #sub path { |
| 104 | my ($self, undef, $args) = @_; |
169 | # my ($self, $args) = @_; |
| 105 | |
170 | # |
| 106 | my $ce = $self->{courseEnvironment}; |
171 | # my $ce = $self->{ce}; |
| 107 | my $root = $ce->{webworkURLs}->{root}; |
172 | # my $root = $ce->{webworkURLs}->{root}; |
| 108 | my $courseName = $ce->{courseName}; |
173 | # my $courseName = $ce->{courseName}; |
| 109 | return $self->pathMacro($args, |
174 | # return $self->pathMacro($args, |
| 110 | "Home" => "$root", |
175 | # "Home" => "$root", |
| 111 | $courseName => "$root/$courseName", |
176 | # $courseName => "$root/$courseName", |
| 112 | "Hardcopy Generator" => "", |
177 | # "Hardcopy Generator" => "", |
| 113 | ); |
178 | # ); |
| 114 | } |
179 | #} |
| 115 | |
180 | # |
| 116 | sub title { |
181 | #sub title { |
| 117 | return "Hardcopy Generator"; |
182 | # return "Hardcopy Generator"; |
| 118 | } |
183 | #} |
| 119 | |
184 | |
| 120 | sub body { |
185 | sub body { |
| 121 | my $self = shift; |
186 | my ($self) = @_; |
| 122 | |
187 | |
| 123 | if ($self->{generationError}) { |
188 | if ($self->{generationError}) { |
| 124 | if (ref $self->{generationError} eq "ARRAY") { |
189 | if (ref $self->{generationError} eq "ARRAY") { |
| 125 | my ($disposition, @rest) = @{$self->{generationError}}; |
190 | my ($disposition, @rest) = @{$self->{generationError}}; |
| 126 | if ($disposition eq "PGFAIL") { |
191 | if ($disposition eq "PGFAIL") { |
| 127 | print $self->multiErrorOutput(@{$self->{errors}}); |
192 | $self->multiErrorOutput(@{$self->{errors}}); |
| 128 | return ""; |
193 | return ""; |
| 129 | } elsif ($disposition eq "FAIL") { |
194 | } elsif ($disposition eq "FAIL") { |
| 130 | print $self->errorOutput(@rest); |
195 | print $self->errorOutput(@rest); |
| 131 | return ""; |
196 | return ""; |
| 132 | } elsif ($disposition eq "RETRY") { |
197 | } elsif ($disposition eq "RETRY") { |
| … | |
… | |
| 137 | } else { |
202 | } else { |
| 138 | # not something we were expecting... |
203 | # not something we were expecting... |
| 139 | die $self->{generationError}; |
204 | die $self->{generationError}; |
| 140 | } |
205 | } |
| 141 | } |
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 | # } |
| 142 | $self->displayForm(); |
217 | $self->displayForm(); |
| 143 | } |
218 | } |
| 144 | |
219 | |
| 145 | sub multiErrorOutput($@) { |
220 | sub multiErrorOutput($@) { |
| 146 | my ($self, @errors) = @_; |
221 | my ($self, @errors) = @_; |
| 147 | |
222 | |
| 148 | print CGI::h2("Software Errors"); |
223 | print CGI::h2("Compile Errors"); |
| 149 | print CGI::p(<<EOF); |
224 | print CGI::p(<<EOF); |
| 150 | WeBWorK has encountered one or more software errors while attempting to process these sets. |
225 | WeBWorK has encountered one or more errors while attempting to process |
| 151 | It is likely that there are error(s) in the problem itself. |
226 | these homework sets. It is likely that there are errors in the problems |
| 152 | If you are a student, contact your professor to have the error(s) corrected. |
227 | themselves. If you are a student, contact your professor to have the errors |
| 153 | If you are a professor, please consut the error output below for more informaiton. |
228 | corrected. If you are a professor, please consult the error output below for |
|
|
229 | more information. |
| 154 | EOF |
230 | EOF |
| 155 | foreach my $error (@errors) { |
231 | foreach my $error (@errors) { |
|
|
232 | my $user = $error->{user}; |
|
|
233 | my $userName = $user->user_id . ' ('.$user->first_name.' '.$user->last_name. ')'; |
| 156 | print CGI::h3("Set: ", $error->{set}, ", Problem: ", $error->{problem}); |
234 | print CGI::h3("Set: ", $error->{set}, ", Problem: ", $error->{problem}, "for $userName"); |
| 157 | print CGI::h4("Error messages"), CGI::blockquote(CGI::pre($error->{message})); |
235 | print CGI::h4("Error messages"), CGI::blockquote(CGI::pre($error->{message})); |
| 158 | print CGI::h4("Error context"), CGI::blockquote(CGI::pre($error->{context})); |
236 | print CGI::h4("Error context"), CGI::blockquote(CGI::pre($error->{context})); |
| 159 | } |
237 | } |
| 160 | } |
238 | } |
| 161 | |
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 | |
| 162 | # ----- |
257 | # ----- |
| 163 | |
258 | |
| 164 | sub displayForm($) { |
259 | sub displayForm($) { |
| 165 | my $self = shift; |
260 | my ($self) = @_; |
| 166 | my $r = $self->{r}; |
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 | } |
| 167 | |
271 | |
| 168 | print CGI::start_p(), "Select the problem sets for which to generate hardcopy versions."; |
272 | print CGI::start_p(), "Select the homework set$ss for which to generate${aa}hardcopy version$ss."; |
| 169 | if ($self->{permissionLevel} > 0) { |
273 | if ($authz->hasPermissions($userID, "download_hardcopy_multiuser")) { |
| 170 | print "You may also select multiple users from the users list. You will receive hardcopy for each (set, user) pair."; |
274 | print "You may also select multiple users from the users list. You will receive hardcopy for each (set, user) pair."; |
| 171 | } |
275 | } |
| 172 | print CGI::end_p(); |
276 | print CGI::end_p(); |
| 173 | |
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 | |
| 174 | print CGI::start_form(-method=>"POST", -action=>$r->uri); |
290 | print CGI::start_form(-method=>"POST", -action=>$actionURL); |
| 175 | print $self->hidden_authen_fields(); |
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 | ); |
| 176 | print CGI::start_table({-width=>"100%"}), CGI::start_Tr({-valign=>"top"}); |
311 | print CGI::start_table({-width=>"100%"}), CGI::start_Tr({-valign=>"top"}); |
| 177 | |
312 | |
| 178 | my $multiSet = $self->{permissionLevel} > 0; |
313 | my $multiSet = $authz->hasPermissions($userID, "download_hardcopy_multiset"); |
| 179 | my $multiUser = $self->{permissionLevel} > 0; |
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); |
| 180 | |
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 | } |
| 181 | # set selection menu |
334 | # set selection menu |
| 182 | { |
335 | { |
| 183 | print CGI::start_td(); |
336 | print CGI::start_td(); |
| 184 | print CGI::h3("Sets"); |
337 | my $number_of_sets = @{$self->{sets}}; |
| 185 | print CGI::start_table(); |
338 | print CGI::h3("Sets: $number_of_sets pre-selected"); |
| 186 | my @sets; |
339 | print CGI::scrolling_list(-name=>'hcSet', |
| 187 | push @sets, $self->{wwdb}->getSet($self->{effectiveUser}->id, $_) |
340 | -values=>\@setNames, |
| 188 | foreach ($self->{wwdb}->getSets($self->{effectiveUser}->id)); |
341 | -labels=>\%setLabels, |
| 189 | @sets = sort { $a->id cmp $b->id } @sets; |
342 | -size => 10, |
| 190 | foreach my $set (@sets) { |
343 | -multiple => $multiSet, |
| 191 | my $checked = grep { $_ eq $set->id } @{$self->{sets}}; |
344 | -defaults => $self->{sets}, |
| 192 | my $control; |
345 | ); |
| 193 | if (time < $set->open_date) { |
|
|
| 194 | $control = ""; |
|
|
| 195 | } else { |
|
|
| 196 | if ($multiSet) { |
|
|
| 197 | $control = CGI::checkbox( |
|
|
| 198 | -name=>"hcSet", |
|
|
| 199 | -value=>$set->id, |
|
|
| 200 | -label=>"", |
|
|
| 201 | -checked=>$checked |
|
|
| 202 | ); |
|
|
| 203 | } else { |
|
|
| 204 | $control = CGI::radio_group( |
|
|
| 205 | -name=>"hcSet", |
|
|
| 206 | -values=>[$set->id], |
|
|
| 207 | -default=>($checked ? $set->id : "-"), |
|
|
| 208 | -labels=>{$set->id => ""} |
|
|
| 209 | ); |
|
|
| 210 | } |
|
|
| 211 | } |
|
|
| 212 | print CGI::Tr(CGI::td([ |
|
|
| 213 | $control, |
|
|
| 214 | $set->id, |
|
|
| 215 | ])); |
|
|
| 216 | } |
|
|
| 217 | print CGI::end_table(); |
|
|
| 218 | print CGI::end_td(); |
346 | print CGI::end_td(); |
| 219 | } |
347 | } |
| 220 | |
348 | |
| 221 | # user selection menu |
349 | # user selection menu |
| 222 | if ($multiUser) { |
350 | if ($multiUser) { |
| 223 | print CGI::start_td(); |
351 | print CGI::start_td(); |
| 224 | print CGI::h3("Users"); |
352 | my $number_of_users = @{$self->{users}}; |
| 225 | print CGI::start_table(); |
353 | print CGI::h3("Users: $number_of_users pre-selected"); |
| 226 | #print CGI::Tr( |
354 | |
| 227 | # CGI::td(CGI::checkbox(-name=>"hcAllUsers", -value=>"1", -label=>"")), |
355 | print CGI::scrolling_list(-name=>'hcUser', |
| 228 | # CGI::td({-colspan=>"2"}, "All Users"), |
356 | -values=>\@userNames, |
|
|
357 | -labels=>\%userLabels, |
|
|
358 | -size => 10, |
|
|
359 | -multiple => 'true', |
|
|
360 | -defaults => $self->{users}, |
| 229 | #); |
361 | ); |
| 230 | #print CGI::Tr(CGI::td({-colspan=>"3"}, " ")); |
|
|
| 231 | my @users; |
|
|
| 232 | push @users, $self->{cldb}->getUser($_) |
|
|
| 233 | foreach ($self->{cldb}->getUsers()); |
|
|
| 234 | @users = sort { $a->last_name cmp $b->last_name } @users; |
|
|
| 235 | foreach my $user (@users) { |
|
|
| 236 | my $checked = grep { $_ eq $user->id } @{$self->{users}}; |
|
|
| 237 | print CGI::Tr(CGI::td([ |
|
|
| 238 | CGI::checkbox(-name=>"hcUser", -value=>$user->id, -label=>"", -checked=>$checked), |
|
|
| 239 | $user->id, |
|
|
| 240 | $user->last_name.", ".$user->first_name, |
|
|
| 241 | ])); |
|
|
| 242 | } |
|
|
| 243 | print CGI::end_table(); |
|
|
| 244 | print CGI::end_td(); |
362 | print CGI::end_td(); |
| 245 | } |
363 | } |
| 246 | |
364 | |
| 247 | print CGI::end_Tr(), CGI::end_table(); |
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 | } |
| 248 | print CGI::p({-align=>"center"}, |
378 | print CGI::p({-align=>"center"}, |
| 249 | CGI::submit(-name=>"generateHardcopy", -label=>"Generate Hardcopy")); |
379 | CGI::submit(-name=>"generateHardcopy", -label=>"Generate Hardcopy")); |
| 250 | print CGI::end_form(); |
380 | print CGI::end_form(); |
| 251 | |
381 | |
| 252 | return ""; |
382 | return ""; |
| 253 | } |
383 | } |
| 254 | |
384 | |
| 255 | sub generateHardcopy($) { |
385 | sub generateHardcopy($) { |
| 256 | my $self = shift; |
386 | my ($self) = @_; |
|
|
387 | my $r = $self->r; |
|
|
388 | my $ce = $r->ce; |
|
|
389 | my $authz = $r->authz; |
|
|
390 | my $userID = $r->param("user"); |
| 257 | my @sets = @{$self->{sets}}; |
391 | my @sets = @{$self->{sets}}; |
| 258 | my @users = @{$self->{users}}; |
392 | my @users = @{$self->{users}}; |
| 259 | my $multiSet = $self->{permissionLevel} > 0; |
393 | my $multiSet = $authz->hasPermissions($userID, "download_hardcopy_multiset"); |
| 260 | my $multiUser = $self->{permissionLevel} > 0; |
394 | my $multiUser = $authz->hasPermissions($userID, "download_hardcopy_multiuser"); |
| 261 | # sanity checks |
395 | # sanity checks |
| 262 | unless (@sets) { |
396 | unless (@sets) { |
| 263 | die ["RETRY", "No sets were specified."]; |
397 | die ["RETRY", "No sets were specified."]; |
| 264 | } |
398 | } |
| 265 | unless (@users) { |
399 | unless (@users) { |
| 266 | die ["RETRY", "No users were specified."]; |
400 | die ["RETRY", "No users were specified."]; |
| 267 | } |
401 | } |
| 268 | |
402 | |
| 269 | # determine where hardcopy is going to go |
403 | # determine where hardcopy is going to go |
| 270 | #my $tempDir = $self->{courseEnvironment}->{courseDirs}->{html_temp} . "/hardcopy"; |
404 | my $tempDir = makeTempDirectory($ce->{webworkDirs}->{tmp}, "webwork-hardcopy"); |
| 271 | my $tempDir = tempdir("webwork-hardcopy-XXXXXXXX", TMPDIR => 1); |
405 | |
| 272 | |
406 | # determine name of PDF file #FIXME it might be best to have the effective user in here somewhere |
| 273 | # make sure tempDir exists |
407 | my $courseName = $ce->{courseName}; |
| 274 | #unless (-e $tempDir) { |
|
|
| 275 | # if (system "mkdir", "-p", $tempDir) { |
|
|
| 276 | # die ["FAIL", "Failed to mkdir $tempDir", $!]; |
|
|
| 277 | # } |
|
|
| 278 | #} |
|
|
| 279 | |
|
|
| 280 | # determine name of PDF file |
|
|
| 281 | my $courseName = $self->{courseEnvironment}->{courseName}; |
|
|
| 282 | my $fileNameSet = (@sets > 1 ? "multiset" : $sets[0]); |
408 | my $fileNameSet = (@sets > 1 ? "multiset" : $sets[0]); |
| 283 | my $fileNameUser = (@users > 1 ? "multiuser" : $users[0]); |
409 | my $fileNameUser = (@users > 1 ? "multiuser" : $users[0]); |
| 284 | my $fileName = "$courseName.$fileNameUser.$fileNameSet.pdf"; |
410 | my $fileName = "$courseName.$fileNameUser.$fileNameSet.pdf"; |
| 285 | |
411 | |
| 286 | # for each user ... generate TeX for each set |
412 | # for each user ... generate TeX for each set |
| 287 | my $tex; |
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 |
| 288 | foreach my $user (@users) { |
418 | foreach my $user (@users) { |
| 289 | $tex .= $self->getMultiSetTeX(@sets); |
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}); |
| 290 | } |
423 | } |
|
|
424 | |
|
|
425 | } |
|
|
426 | # the document postamble |
|
|
427 | $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{postamble}); |
| 291 | |
428 | |
| 292 | # deal with PG errors |
429 | # deal with PG errors |
| 293 | if (@{$self->{errors}}) { |
430 | if (@{$self->{errors}}) { |
| 294 | die ["PGFAIL"]; |
431 | die ["PGFAIL"]; |
| 295 | } |
432 | } |
| 296 | |
433 | |
| 297 | # "try" to generate pdf |
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 = ''; |
| 298 | eval { $self->latex2pdf($tex, $tempDir, $fileName) }; |
444 | $pdfFileURL = eval { $self->latex2pdf($tex, $tempDir, $fileName) }; |
| 299 | if ($@) { |
445 | if ($@) { |
| 300 | die ["FAIL", "Failed to generate PDF from tex", $@]; |
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 | |
| 301 | } |
456 | } |
|
|
457 | } elsif ($self->{hardcopy_format} eq 'tex') { |
|
|
458 | |
|
|
459 | my $TeXdownloadFileName = "$courseName.$fileNameUser.$fileNameSet.tex"; |
| 302 | |
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 | } |
| 303 | return $tempDir, $fileName; |
494 | #return $tempDir, $fileName; |
|
|
495 | # return $pdfFilePath; |
|
|
496 | return $pdfFileURL; |
| 304 | } |
497 | } |
| 305 | |
498 | |
| 306 | # ----- |
499 | # ----- |
| 307 | |
500 | |
| 308 | sub latex2pdf { |
501 | sub latex2pdf { |
| 309 | # this is a little ad-hoc function which I will replace with a LaTeX |
502 | # this is a little ad-hoc function which I will replace with a LaTeX |
| 310 | # module at some point (or put it in Utils). |
503 | # module at some point (or put it in Utils). |
| 311 | my ($self, $tex, $fileBase, $fileName) = @_; |
504 | my ($self, $tex, $tempDir, $fileName) = @_; |
|
|
505 | my $r = $self->r; |
|
|
506 | my $ce = $r->ce; |
|
|
507 | |
| 312 | my $finalFile = "$fileBase/$fileName"; |
508 | my $finalFile = "$tempDir/$fileName"; |
| 313 | my $ce = $self->{courseEnvironment}; |
|
|
| 314 | |
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 | |
| 315 | # create a temporary directory for tex to shit in |
519 | ## create a temporary directory for tex to shit in |
| 316 | my $wd = tempdir("webwork-hardcopy-XXXXXXXX", TMPDIR => 1); |
520 | # - we're using the existing temp dir. now |
|
|
521 | |
|
|
522 | my $wd = $tempDir; |
| 317 | my $texFile = "$wd/hardcopy.tex"; |
523 | my $texFile = "$wd/hardcopy.tex"; |
| 318 | my $pdfFile = "$wd/hardcopy.pdf"; |
524 | my $pdfFile = "$wd/hardcopy.pdf"; |
| 319 | my $logFile = "$wd/hardcopy.log"; |
525 | my $logFile = "$wd/hardcopy.log"; |
| 320 | |
526 | |
| 321 | # write the tex file |
527 | # write the tex file |
| 322 | local *TEX; |
528 | local *TEX; |
| 323 | open TEX, ">", $texFile or die "Failed to open $texFile: $!\n"; |
529 | open TEX, ">", $texFile or die "Failed to open $texFile: $!\n".CGI::br(); |
| 324 | print TEX $tex; |
530 | print TEX $tex; |
| 325 | close TEX; |
531 | close TEX; |
| 326 | |
532 | |
| 327 | # call pdflatex - we don't want to chdir in the mod_perl process, as |
533 | # call pdflatex - we don't want to chdir in the mod_perl process, as |
| 328 | # that might step on the feet of other things (esp. in Apache 2.0) |
534 | # that might step on the feet of other things (esp. in Apache 2.0) |
| 329 | my $pdflatex = $ce->{externalPrograms}->{pdflatex}; |
535 | my $pdflatex = $ce->{externalPrograms}->{pdflatex}; |
| 330 | system "cd $wd && $pdflatex $texFile" and die "Failed to call pdflatex: $!\n"; |
536 | my $pdflatexResult = system "cd $wd && $pdflatex $texFile"; |
| 331 | |
537 | |
|
|
538 | # Even with errors there may be a valid pdfFile. Move it to where we can get it. |
| 332 | if (-e $pdfFile) { |
539 | if (-e $pdfFile) { |
| 333 | # move resulting PDF file to appropriate location |
540 | |
| 334 | system "/bin/mv", $pdfFile, $finalFile and die "Failed to mv: $!\n"; |
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(); |
| 335 | } |
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 | } |
| 336 | |
574 | |
|
|
575 | |
|
|
576 | |
| 337 | # remove temporary directory |
577 | ## remove temporary directory |
|
|
578 | if ($PreserveTempFiles) { |
|
|
579 | warn "Working directory preserved at '$wd'.\n"; |
|
|
580 | } else { |
| 338 | rmtree($wd, 0, 1); |
581 | rmtree($wd, 0, 0); |
|
|
582 | } |
|
|
583 | |
| 339 | |
584 | |
| 340 | -e $finalFile or die "Failed to create $finalFile for no apparent reason.\n"; |
585 | -e $hardcopyFilePath or die "Failed to create $finalFile for no apparent reason.\n"; |
|
|
586 | # return hardcopyFilePath; |
|
|
587 | return $hardcopyFileURL; |
| 341 | } |
588 | } |
| 342 | |
589 | |
| 343 | # ----- |
590 | # ----- |
|
|
591 | # FIXME move to Utils? probably not |
| 344 | |
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 | } |
| 345 | sub texBlockComment(@) { return "\n".("%"x80)."\n%% ".join("", @_)."\n".("%"x80)."\n\n"; } |
634 | sub texBlockComment(@) { return "\n".("%"x80)."\n%% ".join("", @_)."\n".("%"x80)."\n\n"; } |
| 346 | |
635 | |
| 347 | sub getMultiSetTeX { |
636 | sub getMultiSetTeX { |
| 348 | my ($self, @sets) = @_; |
637 | my ($self, $effectiveUserName,@sets) = @_; |
| 349 | my $ce = $self->{courseEnvironment}; |
638 | my $ce = $self->r->ce; |
| 350 | my $tex = ""; |
639 | my $tex = ""; |
| 351 | |
640 | |
| 352 | # the document preamble |
641 | |
| 353 | $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{preamble}); |
|
|
| 354 | |
642 | |
| 355 | while (defined (my $setName = shift @sets)) { |
643 | while (defined (my $setName = shift @sets)) { |
| 356 | $tex .= $self->getSetTeX($setName); |
644 | $tex .= $self->getSetTeX($effectiveUserName, $setName); |
| 357 | if (@sets) { |
645 | if (@sets) { |
| 358 | # divide sets, but not after the last set |
646 | # divide sets, but not after the last set |
| 359 | $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{setDivider}); |
647 | $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{setDivider}); |
| 360 | } |
648 | } |
| 361 | } |
649 | } |
| 362 | |
650 | |
| 363 | # the document postamble |
651 | |
| 364 | $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{postamble}); |
|
|
| 365 | |
652 | |
| 366 | return $tex; |
653 | return $tex; |
| 367 | } |
654 | } |
| 368 | |
655 | |
| 369 | sub getSetTeX { |
656 | sub getSetTeX { |
| 370 | my ($self, $setName) = @_; |
657 | my ($self, $effectiveUserName, $setName) = @_; |
| 371 | my $ce = $self->{courseEnvironment}; |
658 | my $r = $self->r; |
| 372 | my $wwdb = $self->{wwdb}; |
659 | my $ce = $r->ce; |
| 373 | my $effectiveUserName = $self->{effectiveUser}->id; |
660 | my $db = $r->db; |
| 374 | my @problemNumbers = sort { $a <=> $b } $wwdb->getProblems($effectiveUserName, $setName); |
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); |
| 375 | |
673 | |
| 376 | # get header and footer |
674 | # get header and footer |
| 377 | my $setHeader = $wwdb->getSet($effectiveUserName, $setName)->set_header |
675 | my $set = $db->getMergedSet($effectiveUserName, $setName); # checked |
| 378 | || $ce->{webworkFiles}->{hardcopySnippets}->{setHeader}; |
676 | my $setHeader = (ref($set) && $set->hardcopy_header) ? $set->hardcopy_header: $ce->{webworkFiles}->{hardcopySnippets}->{setHeader}; |
| 379 | # database doesn't support the following yet :( |
677 | # database doesn't support the following yet :( |
| 380 | #my $setFooter = $wwdb->getSet($effectiveUserName, $setName)->set_footer |
678 | #my $setFooter = $wwdb->getMergedSet($effectiveUserName, $setName)->set_footer |
| 381 | # || $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; |
679 | # || $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; |
| 382 | # so we don't allow per-set customization, which is probably okay :) |
680 | # so we don't allow per-set customization, which is probably okay :) |
| 383 | my $setFooter = $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; |
681 | my $setFooter = $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; |
| 384 | |
682 | |
| 385 | my $tex = ""; |
683 | my $tex = ""; |
| 386 | |
684 | |
| 387 | # render header |
685 | # render header |
| 388 | $tex .= texBlockComment("BEGIN $setName : $setHeader"); |
686 | $tex .= texBlockComment("BEGIN $setName : $setHeader"); |
| 389 | $tex .= $self->getProblemTeX($setName, 0, $setHeader); |
687 | $tex .= $self->getProblemTeX($effectiveUser,$setName, 0, $setHeader); |
| 390 | |
688 | |
| 391 | # render each problem |
689 | # render each problem |
| 392 | while (my $problemNumber = shift @problemNumbers) { |
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 | # |
| 393 | $tex .= texBlockComment("BEGIN $setName : $problemNumber"); |
698 | $tex .= texBlockComment("BEGIN $setName : $problemNumber"); |
| 394 | $tex .= $self->getProblemTeX($setName, $problemNumber); |
699 | $tex .= $self->getProblemTeX($effectiveUser,$setName, $problemNumber); |
|
|
700 | # |
|
|
701 | # DPVC -- no need for it here since we do it above |
|
|
702 | # |
| 395 | if (@problemNumbers) { |
703 | #if (@problemNumbers) { |
| 396 | # divide problems, but not after the last problem |
704 | # # divide problems, but not after the last problem |
| 397 | $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{problemDivider}); |
705 | # $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{problemDivider}); |
| 398 | } |
706 | #} |
|
|
707 | # |
|
|
708 | # /DPVC |
|
|
709 | # |
| 399 | } |
710 | } |
| 400 | |
711 | |
| 401 | # render footer |
712 | # render footer |
| 402 | $tex .= texBlockComment("BEGIN $setName : $setFooter"); |
713 | $tex .= texBlockComment("BEGIN $setName : $setFooter"); |
| 403 | $tex .= $self->getProblemTeX($setName, 0, $setFooter); |
714 | $tex .= $self->getProblemTeX($effectiveUser,$setName, 0, $setFooter); |
| 404 | |
715 | |
| 405 | return $tex; |
716 | return $tex; |
| 406 | } |
717 | } |
| 407 | |
718 | |
| 408 | sub getProblemTeX { |
719 | sub getProblemTeX { |
|
|
720 | $WeBWorK::timer1 ->continue("hardcopy: begin processing problem") if defined($WeBWorK::timer1); |
| 409 | my ($self, $setName, $problemNumber, $pgFile) = @_; |
721 | my ($self, $effectiveUser, $setName, $problemNumber, $pgFile) = @_; |
| 410 | my $r = $self->{r}; |
722 | my $r = $self->r; |
| 411 | my $ce = $self->{courseEnvironment}; |
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 |
| 412 | |
728 | |
| 413 | my $wwdb = $self->{wwdb}; |
729 | # $effectiveUser = $self->{effectiveUser} unless defined($effectiveUser); |
| 414 | my $cldb = $self->{cldb}; |
730 | my $permissionLevel = $self->{permissionLevel}; |
| 415 | my $effectiveUser = $self->{effectiveUser}; |
|
|
| 416 | my $set = $wwdb->getSet($effectiveUser->id, $setName); |
731 | my $set = $db->getMergedSet($effectiveUser->user_id, $setName); # checked |
| 417 | my $psvn = $wwdb->getPSVN($effectiveUser->id, $setName); |
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(); |
| 418 | |
754 | |
| 419 | # decide what to do about problem number |
755 | # decide what to do about problem number |
| 420 | my $problem; |
756 | my $problem; |
| 421 | if ($problemNumber) { |
757 | if ($problemNumber) { # problem number defined and not zero |
| 422 | $problem = $wwdb->getProblem($effectiveUser->id, $setName, $problemNumber); |
758 | $problem = $db->getMergedProblem($effectiveUser->user_id, $setName, $problemNumber); # checked |
| 423 | } elsif ($pgFile) { |
759 | } elsif ($pgFile) { |
| 424 | $problem = WeBWorK::Problem->new( |
760 | $problem = WeBWorK::DB::Record::UserProblem->new( |
| 425 | id => 0, |
|
|
| 426 | set_id => $set->id, |
761 | set_id => $set->set_id, |
|
|
762 | problem_id => 0, |
| 427 | login_id => $effectiveUser->id, |
763 | login_id => $effectiveUser->user_id, |
| 428 | source_file => $pgFile, |
764 | source_file => $pgFile, |
| 429 | # the rest of Problem's fields are not needed, i think |
765 | # the rest of Problem's fields are not needed, i think |
| 430 | ); |
766 | ); |
| 431 | } |
767 | } |
| 432 | |
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? |
| 433 | my $pg = WeBWorK::PG->new( |
792 | my $pg = WeBWorK::PG->new( |
| 434 | $ce, |
793 | $ce, |
| 435 | $effectiveUser, |
794 | $effectiveUser, |
| 436 | $r->param('key'), |
795 | $r->param('key'), |
| 437 | $set, |
796 | $set, |
| 438 | $problem, |
797 | $problem, |
| 439 | $psvn, |
798 | $psvn, |
| 440 | {}, # no form fields! |
799 | {}, # no form fields! |
| 441 | { # translation options |
800 | { # translation options |
| 442 | displayMode => "tex", |
801 | displayMode => "tex", |
| 443 | showHints => 0, |
802 | showHints => ($showHints)? 1:0, # insure that this value is numeric |
| 444 | showSolutions => 0, |
803 | showSolutions => ($showSolutions)? 1:0, |
| 445 | processAnswers => 0, |
804 | processAnswers => ($showCorrectAnswers)? 1:0, |
| 446 | }, |
805 | }, |
| 447 | ); |
806 | ); |
| 448 | |
807 | |
| 449 | if ($pg->{warnings} ne "") { |
808 | if ($pg->{warnings} ne "") { |
| 450 | push @{$self->{warnings}}, { |
809 | push @{$self->{warnings}}, { |
| … | |
… | |
| 456 | |
815 | |
| 457 | if ($pg->{flags}->{error_flag}) { |
816 | if ($pg->{flags}->{error_flag}) { |
| 458 | push @{$self->{errors}}, { |
817 | push @{$self->{errors}}, { |
| 459 | set => $setName, |
818 | set => $setName, |
| 460 | problem => $problemNumber, |
819 | problem => $problemNumber, |
|
|
820 | user => $effectiveUser, |
| 461 | message => $pg->{errors}, |
821 | message => $pg->{errors}, |
| 462 | context => $pg->{body_text}, |
822 | context => $pg->{body_text}, |
| 463 | }; |
823 | }; |
| 464 | # if there was an error, body_text contains |
824 | # if there was an error, body_text contains |
| 465 | # the error context, not TeX code |
825 | # the error context, not TeX code FIXME (should this error context be used?) |
| 466 | $pg->{body_text} = undef; |
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; |
| 467 | } |
853 | } |
| 468 | |
854 | } |
|
|
855 | $WeBWorK::timer1 ->continue("hardcopy: end processing problem") if defined($WeBWorK::timer1); |
| 469 | return $pg->{body_text}; |
856 | return $pg->{body_text}; |
| 470 | } |
857 | } |
| 471 | |
858 | |
| 472 | sub texInclude { |
859 | sub texInclude { |
| 473 | my ($self, $texFile) = @_; |
860 | my ($self, $texFile) = @_; |
| … | |
… | |
| 483 | |
870 | |
| 484 | return $tex; |
871 | return $tex; |
| 485 | } |
872 | } |
| 486 | |
873 | |
| 487 | 1; |
874 | 1; |
| 488 | |
|
|
| 489 | __END__ |
|
|
| 490 | |
|
|
| 491 | sub body { |
|
|
| 492 | my $self = shift; |
|
|
| 493 | |
|
|
| 494 | STUFF: { |
|
|
| 495 | my $courseName = $self->{courseEnvironment}->{courseName}; |
|
|
| 496 | my $effectiveUserName = $self->{r}->param("effectiveUser"); |
|
|
| 497 | my @sets = @{$self->{sets}}; |
|
|
| 498 | |
|
|
| 499 | unless (@sets) { |
|
|
| 500 | print CGI::p("No problem sets were specified."); |
|
|
| 501 | last STUFF; |
|
|
| 502 | } |
|
|
| 503 | |
|
|
| 504 | # determine where hardcopy is going to go |
|
|
| 505 | my $tempDir = $self->{courseEnvironment}->{courseDirs}->{html_temp} |
|
|
| 506 | . "/hardcopy"; |
|
|
| 507 | my $tempURL = $self->{courseEnvironment}->{courseURLs}->{html_temp} |
|
|
| 508 | . "/hardcopy"; |
|
|
| 509 | |
|
|
| 510 | # make sure tempDir exists |
|
|
| 511 | unless (-e $tempDir) { |
|
|
| 512 | if (system "mkdir", "-p", $tempDir) { |
|
|
| 513 | print CGI::p("An error occured while trying to generate your PDF hardcopy:"); |
|
|
| 514 | print CGI::blockquote(CGI::pre("Failed to mkdir $tempDir: $!\n")); |
|
|
| 515 | } |
|
|
| 516 | } |
|
|
| 517 | |
|
|
| 518 | # determine name of PDF file |
|
|
| 519 | my $fileName; |
|
|
| 520 | if (@sets > 1) { |
|
|
| 521 | # multiset output |
|
|
| 522 | $fileName = "$courseName.$effectiveUserName.multiset.pdf" |
|
|
| 523 | } elsif (@sets == 1) { |
|
|
| 524 | # only one set |
|
|
| 525 | my $setName = $sets[0]; |
|
|
| 526 | $fileName = "$courseName.$effectiveUserName.$setName.pdf"; |
|
|
| 527 | } else { |
|
|
| 528 | $fileName = "$courseName.$effectiveUserName.pdf"; |
|
|
| 529 | } |
|
|
| 530 | |
|
|
| 531 | # determine full URL |
|
|
| 532 | my $fullURL = "$tempURL/$fileName"; |
|
|
| 533 | |
|
|
| 534 | # generate TeX from sets |
|
|
| 535 | my $tex = $self->getMultiSetTeX(@sets); |
|
|
| 536 | #print CGI::pre($tex); |
|
|
| 537 | |
|
|
| 538 | # check for PG errors (fatal) |
|
|
| 539 | if (@{$self->{errors}}) { |
|
|
| 540 | my @errors = @{$self->{errors}}; |
|
|
| 541 | print CGI::h2("Software Errors"); |
|
|
| 542 | print CGI::p(<<EOF); |
|
|
| 543 | WeBWorK has encountered one or more software errors while attempting to process these sets. |
|
|
| 544 | It is likely that there are error(s) in the problem itself. |
|
|
| 545 | If you are a student, contact your professor to have the error(s) corrected. |
|
|
| 546 | If you are a professor, please consut the error output below for more informaiton. |
|
|
| 547 | EOF |
|
|
| 548 | foreach my $error (@errors) { |
|
|
| 549 | print CGI::h3("Set: ", $error->{set}, ", Problem: ", $error->{problem}); |
|
|
| 550 | print CGI::h4("Error messages"), CGI::blockquote(CGI::pre($error->{message})); |
|
|
| 551 | print CGI::h4("Error context"), CGI::blockquote(CGI::pre($error->{context})); |
|
|
| 552 | } |
|
|
| 553 | |
|
|
| 554 | last STUFF; |
|
|
| 555 | } |
|
|
| 556 | |
|
|
| 557 | # "try" to generate hardcopy |
|
|
| 558 | eval { $self->latex2pdf($tex, $tempDir, $fileName) }; |
|
|
| 559 | if ($@) { |
|
|
| 560 | print CGI::p("An error occured while trying to generate your PDF hardcopy:"); |
|
|
| 561 | print CGI::blockquote(CGI::pre($@)); |
|
|
| 562 | last STUFF; |
|
|
| 563 | } else { |
|
|
| 564 | print CGI::p({-align=>"center"}, |
|
|
| 565 | CGI::big(CGI::a({-href=>$fullURL}, "Download PDF Hardcopy")) |
|
|
| 566 | ); |
|
|
| 567 | } |
|
|
| 568 | |
|
|
| 569 | # check for PG warnings (non-fatal) |
|
|
| 570 | if (@{$self->{warnings}}) { |
|
|
| 571 | my @warnings = @{$self->{warnings}}; |
|
|
| 572 | print CGI::h2("Software Warnings"); |
|
|
| 573 | print CGI::p(<<EOF); |
|
|
| 574 | WeBWorK has encountered warnings while attempting to process these sets. |
|
|
| 575 | It is likely that this indicates an error or ambiguity in the problem(s) themselves. |
|
|
| 576 | If you are a student, contact your professor to have the problem(s) corrected. |
|
|
| 577 | If you are a professor, please consut the error output below for more informaiton. |
|
|
| 578 | EOF |
|
|
| 579 | foreach my $warning (@warnings) { |
|
|
| 580 | print CGI::h3("Set: ", $warning->{set}, ", Problem: ", $warning->{problem}); |
|
|
| 581 | print CGI::h4("Warning messages"), CGI::blockquote(CGI::pre($warning->{message})); |
|
|
| 582 | } |
|
|
| 583 | } |
|
|
| 584 | } |
|
|
| 585 | |
|
|
| 586 | # feedback form |
|
|
| 587 | my $ce = $self->{courseEnvironment}; |
|
|
| 588 | my $root = $ce->{webworkURLs}->{root}; |
|
|
| 589 | my $courseName = $ce->{courseName}; |
|
|
| 590 | my $feedbackURL = "$root/$courseName/feedback/"; |
|
|
| 591 | print |
|
|
| 592 | CGI::startform("POST", $feedbackURL), |
|
|
| 593 | $self->hidden_authen_fields, |
|
|
| 594 | CGI::hidden("module", __PACKAGE__), |
|
|
| 595 | CGI::p({-align=>"right"}, |
|
|
| 596 | CGI::submit(-name=>"feedbackForm", -label=>"Send Feedback") |
|
|
| 597 | ), |
|
|
| 598 | CGI::endform(); |
|
|
| 599 | |
|
|
| 600 | return ""; |
|
|
| 601 | } |
|
|