| … | |
… | |
| 5 | |
5 | |
| 6 | package WeBWorK::ContentGenerator::Hardcopy; |
6 | package WeBWorK::ContentGenerator::Hardcopy; |
| 7 | |
7 | |
| 8 | =head1 NAME |
8 | =head1 NAME |
| 9 | |
9 | |
| 10 | WeBWorK::ContentGenerator::Test - generate a PDF version of one or more |
10 | WeBWorK::ContentGenerator::Hardcopy - generate a PDF version of one or more |
| 11 | problem sets. |
11 | problem sets. |
| 12 | |
12 | |
| 13 | =cut |
13 | =cut |
| 14 | |
14 | |
| 15 | use strict; |
15 | use strict; |
| 16 | use warnings; |
16 | use warnings; |
| 17 | use base qw(WeBWorK::ContentGenerator); |
17 | use base qw(WeBWorK::ContentGenerator); |
| 18 | use Apache::Constants qw(:common); |
18 | #use Apache::Constants qw(:common); |
| 19 | use CGI qw(); |
19 | use CGI qw(); |
| 20 | use File::Path qw(rmtree); |
20 | use File::Path qw(rmtree); |
| 21 | use File::Temp qw(tempdir); |
21 | use File::Temp qw(tempdir); |
| 22 | use WeBWorK::DB::Classlist; |
22 | use WeBWorK::DB::Classlist; |
| 23 | use WeBWorK::DB::WW; |
23 | use WeBWorK::DB::WW; |
| 24 | use WeBWorK::Form; |
24 | use WeBWorK::Form; |
| 25 | use WeBWorK::Utils qw(readFile); |
25 | use WeBWorK::Utils qw(readFile); |
| 26 | |
26 | |
| 27 | sub texBlockComment { return "\n".("%"x80)."\n%% ".join("", @_)."\n".("%"x80)."\n\n"; } |
27 | sub texBlockComment(@) { return "\n".("%"x80)."\n%% ".join("", @_)."\n".("%"x80)."\n\n"; } |
| 28 | |
28 | |
| 29 | sub initialize { |
29 | sub initialize { |
| 30 | my $self = shift; |
30 | my ($self, $singleSet, undef) = @_; |
|
|
31 | |
|
|
32 | my $r = $self->{r}; |
| 31 | my $ce = $self->{courseEnvironment}; |
33 | my $ce = $self->{courseEnvironment}; |
|
|
34 | my @sets = $r->param("set"); |
|
|
35 | |
|
|
36 | if (length $singleSet > 0) { |
|
|
37 | $singleSet =~ s/^set//; |
|
|
38 | unshift @sets, $singleSet; |
|
|
39 | } |
|
|
40 | |
| 32 | $self->{cldb} = WeBWorK::DB::Classlist->new($ce); |
41 | $self->{cldb} = WeBWorK::DB::Classlist->new($ce); |
| 33 | $self->{wwdb} = WeBWorK::DB::WW->new($ce); |
42 | $self->{wwdb} = WeBWorK::DB::WW->new($ce); |
|
|
43 | $self->{sets} = \@sets; |
|
|
44 | $self->{errors} = []; |
|
|
45 | $self->{warnings} = []; |
| 34 | } |
46 | } |
| 35 | |
47 | |
| 36 | sub path { |
48 | sub path { |
| 37 | my ($self, undef, $args) = @_; |
49 | my ($self, undef, $args) = @_; |
| 38 | |
50 | |
| … | |
… | |
| 49 | sub title { |
61 | sub title { |
| 50 | return "Hardcopy Generator"; |
62 | return "Hardcopy Generator"; |
| 51 | } |
63 | } |
| 52 | |
64 | |
| 53 | sub body { |
65 | sub body { |
| 54 | my ($self, $singleSet) = @_; |
66 | my $self = shift; |
| 55 | $singleSet =~ s/^set//; |
67 | |
| 56 | my $r = $self->{r}; |
|
|
| 57 | my $ce = $self->{courseEnvironment}; |
68 | my $courseName = $self->{courseEnvironment}->{courseName}; |
| 58 | $self->{wwdb} = WeBWorK::DB::WW->new($ce); |
69 | my $userName = $self->{r}->param("user"); |
|
|
70 | my @sets = @{$self->{sets}}; |
| 59 | |
71 | |
| 60 | my @sets = $r->param("set"); |
|
|
| 61 | unshift @sets, $singleSet; |
|
|
| 62 | unless (@sets) { |
72 | unless (@sets) { |
| 63 | print CGI::p("No problem sets were specified."); |
73 | print CGI::p("No problem sets were specified."); |
| 64 | return OK; |
|
|
| 65 | } |
|
|
| 66 | |
|
|
| 67 | #print CGI::pre($self->getMultiSetTeX(@sets)); |
|
|
| 68 | #return ""; |
|
|
| 69 | |
|
|
| 70 | print CGI::p("Generating your hardcopy..."); |
|
|
| 71 | my $url = $self->makeHardcopy(@sets); |
|
|
| 72 | if ($url) { |
|
|
| 73 | print CGI::p("Ok, your hardcopy is ready. Click the following link to download it."); |
|
|
| 74 | print CGI::p({-align=>"center"}, |
|
|
| 75 | CGI::big(CGI::a({-href=>$url}, "Download PDF Hardcopy")) |
|
|
| 76 | ); |
|
|
| 77 | } else { |
|
|
| 78 | print CGI::p("Hmm, looks like I was unable to generate the hardcopy you requested. I'm really sorry... :("); |
|
|
| 79 | } |
|
|
| 80 | |
|
|
| 81 | return ""; |
74 | return ""; |
| 82 | } |
75 | } |
| 83 | |
76 | |
| 84 | # ----- |
77 | # determine where hardcopy is going to go |
| 85 | |
|
|
| 86 | sub makeHardcopy { |
|
|
| 87 | my ($self, @sets) = @_; |
|
|
| 88 | my $courseName = $self->{courseEnvironment}->{courseName}; |
|
|
| 89 | my $userName = $self->{r}->param("user"); |
|
|
| 90 | my $tempDir = $self->{courseEnvironment}->{courseDirs}->{html_temp} |
78 | my $tempDir = $self->{courseEnvironment}->{courseDirs}->{html_temp} |
| 91 | . "/hardcopy"; |
79 | . "/hardcopy"; |
| 92 | my $tempURL = $self->{courseEnvironment}->{courseURLs}->{html_temp} |
80 | my $tempURL = $self->{courseEnvironment}->{courseURLs}->{html_temp} |
| 93 | . "/hardcopy"; |
81 | . "/hardcopy"; |
| 94 | |
82 | |
| … | |
… | |
| 102 | my $setName = $sets[0]; |
90 | my $setName = $sets[0]; |
| 103 | $fileName = "$courseName.$userName.$setName.pdf"; |
91 | $fileName = "$courseName.$userName.$setName.pdf"; |
| 104 | } else { |
92 | } else { |
| 105 | $fileName = "$courseName.$userName.pdf"; |
93 | $fileName = "$courseName.$userName.pdf"; |
| 106 | } |
94 | } |
|
|
95 | |
|
|
96 | # determine full URL |
|
|
97 | my $fullURL = "$tempURL/$fileName"; |
|
|
98 | |
|
|
99 | # generate TeX from sets |
| 107 | my $tex = $self->getMultiSetTeX(@sets); |
100 | my $tex = $self->getMultiSetTeX(@sets); |
|
|
101 | #print CGI::pre($tex); |
|
|
102 | |
|
|
103 | # check for PG errors (fatal) |
|
|
104 | if (@{$self->{errors}}) { |
|
|
105 | my @errors = @{$self->{errors}}; |
|
|
106 | print CGI::h2("Software Errors"); |
|
|
107 | print CGI::p(<<EOF); |
|
|
108 | WeBWorK has encountered one or more software errors while attempting to process these sets. |
|
|
109 | It is likely that there are error(s) in the problem itself. |
|
|
110 | If you are a student, contact your professor to have the error(s) corrected. |
|
|
111 | If you are a professor, please consut the error output below for more informaiton. |
|
|
112 | EOF |
|
|
113 | foreach my $error (@errors) { |
|
|
114 | print CGI::h3("Set: ", $error->{set}, ", Problem: ", $error->{problem}); |
|
|
115 | print CGI::h4("Error messages"), CGI::blockquote(CGI::pre($error->{message})); |
|
|
116 | print CGI::h4("Error context"), CGI::blockquote(CGI::pre($error->{context})); |
|
|
117 | } |
|
|
118 | |
|
|
119 | return ""; |
|
|
120 | } |
|
|
121 | |
|
|
122 | # "try" to generate hardcopy |
| 108 | $self->latex2pdf($tex, $tempDir, $fileName) or return; |
123 | eval { $self->latex2pdf($tex, $tempDir, $fileName) }; |
|
|
124 | if ($@) { |
|
|
125 | print CGI::p("An error occured while trying to generate your PDF hardcopy:"); |
|
|
126 | print CGI::blockquote(CGI::pre($@)); |
|
|
127 | return ""; |
|
|
128 | } else { |
|
|
129 | print CGI::p({-align=>"center"}, |
|
|
130 | CGI::big(CGI::a({-href=>$fullURL}, "Download PDF Hardcopy")) |
|
|
131 | ); |
|
|
132 | } |
| 109 | |
133 | |
| 110 | return "$tempURL/$fileName"; |
134 | # check for PG warnings (non-fatal) |
|
|
135 | if (@{$self->{warnings}}) { |
|
|
136 | my @warnings = @{$self->{warnings}}; |
|
|
137 | print CGI::h2("Software Warnings"); |
|
|
138 | print CGI::p(<<EOF); |
|
|
139 | WeBWorK has encountered warnings while attempting to process these sets. |
|
|
140 | It is likely that this indicates an error or ambiguity in the problem(s) themselves. |
|
|
141 | If you are a student, contact your professor to have the problem(s) corrected. |
|
|
142 | If you are a professor, please consut the error output below for more informaiton. |
|
|
143 | EOF |
|
|
144 | foreach my $warning (@warnings) { |
|
|
145 | print CGI::h3("Set: ", $warning->{set}, ", Problem: ", $warning->{problem}); |
|
|
146 | print CGI::h4("Warning messages"), CGI::blockquote(CGI::pre($warning->{message})); |
|
|
147 | } |
|
|
148 | } |
|
|
149 | |
|
|
150 | return ""; |
| 111 | } |
151 | } |
|
|
152 | |
|
|
153 | # ----- |
| 112 | |
154 | |
| 113 | sub latex2pdf { |
155 | sub latex2pdf { |
| 114 | # this is a little ad-hoc function which I will replace with a LaTeX |
156 | # this is a little ad-hoc function which I will replace with a LaTeX |
| 115 | # module at some point (or put it in Utils). |
157 | # module at some point (or put it in Utils). |
| 116 | my ($self, $tex, $fileBase, $fileName) = @_; |
158 | my ($self, $tex, $fileBase, $fileName) = @_; |
| … | |
… | |
| 123 | my $pdfFile = "$wd/hardcopy.pdf"; |
165 | my $pdfFile = "$wd/hardcopy.pdf"; |
| 124 | my $logFile = "$wd/hardcopy.log"; |
166 | my $logFile = "$wd/hardcopy.log"; |
| 125 | |
167 | |
| 126 | # write the tex file |
168 | # write the tex file |
| 127 | local *TEX; |
169 | local *TEX; |
| 128 | open TEX, ">", $texFile; |
170 | open TEX, ">", $texFile or die "Failed to open $texFile: $!\n"; |
| 129 | print TEX $tex; |
171 | print TEX $tex; |
| 130 | close TEX; |
172 | close TEX; |
| 131 | |
173 | |
| 132 | # call pdflatex - we don't want to chdir in the mod_perl process, as |
174 | # call pdflatex - we don't want to chdir in the mod_perl process, as |
| 133 | # that might step on the feet of other things (esp. in Apache 2.0) |
175 | # that might step on the feet of other things (esp. in Apache 2.0) |
| 134 | my $pdflatex = $ce->{externalPrograms}->{pdflatex}; |
176 | my $pdflatex = $ce->{externalPrograms}->{pdflatex}; |
| 135 | system "cd $wd && $pdflatex $texFile"; |
177 | system "cd $wd && $pdflatex $texFile" and die "Failed to call pdflatex: $!\n"; |
| 136 | |
178 | |
| 137 | if (-e $pdfFile) { |
179 | if (-e $pdfFile) { |
| 138 | # move resulting PDF file to appropriate location |
180 | # move resulting PDF file to appropriate location |
| 139 | my $mv = $ce->{externalPrograms}->{mv}; |
|
|
| 140 | system $mv, $pdfFile, $finalFile and die "Failed to mv: $!\n"; |
181 | system "/bin/mv", $pdfFile, $finalFile and die "Failed to mv: $!\n"; |
| 141 | } |
182 | } |
| 142 | |
183 | |
| 143 | # remove temporary directory |
184 | # remove temporary directory |
| 144 | rmtree($wd, 0, 1); |
185 | rmtree($wd, 0, 1); |
| 145 | |
186 | |
| 146 | return -e $finalFile; |
187 | -e $finalFile or die "Failed to create $finalFile for no apparent reason.\n"; |
| 147 | } |
188 | } |
| 148 | |
189 | |
| 149 | # ----- |
190 | # ----- |
| 150 | |
191 | |
| 151 | sub getMultiSetTeX { |
192 | sub getMultiSetTeX { |
| … | |
… | |
| 154 | my $tex = ""; |
195 | my $tex = ""; |
| 155 | |
196 | |
| 156 | # the document preamble |
197 | # the document preamble |
| 157 | $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{preamble}); |
198 | $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{preamble}); |
| 158 | |
199 | |
| 159 | while (my $set = shift @sets) { |
200 | while (defined (my $setName = shift @sets)) { |
| 160 | $tex .= $self->getSetTeX($set); |
201 | $tex .= $self->getSetTeX($setName); |
| 161 | if (@sets) { |
202 | if (@sets) { |
| 162 | # divide sets, but not after the last set |
203 | # divide sets, but not after the last set |
| 163 | $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{setDivider}); |
204 | $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{setDivider}); |
| 164 | } |
205 | } |
| 165 | } |
206 | } |
| … | |
… | |
| 214 | my $r = $self->{r}; |
255 | my $r = $self->{r}; |
| 215 | my $ce = $self->{courseEnvironment}; |
256 | my $ce = $self->{courseEnvironment}; |
| 216 | |
257 | |
| 217 | my $wwdb = $self->{wwdb}; |
258 | my $wwdb = $self->{wwdb}; |
| 218 | my $cldb = $self->{cldb}; |
259 | my $cldb = $self->{cldb}; |
| 219 | my $user = $cldb->getUser($r->param("user")); |
260 | my $user = $cldb->getUser($r->param("user")); |
| 220 | my $set = $wwdb->getSet($user->id, $setName); |
261 | my $set = $wwdb->getSet($user->id, $setName); |
| 221 | my $psvn = $wwdb->getPSVN($user->id, $setName); |
262 | my $psvn = $wwdb->getPSVN($user->id, $setName); |
| 222 | |
263 | |
| 223 | # decide what to do about problem number |
264 | # decide what to do about problem number |
| 224 | my $problem; |
265 | my $problem; |
| 225 | if ($problemNumber) { |
266 | if ($problemNumber) { |
| 226 | $problem = $wwdb->getProblem($user->id, $setName, $problemNumber); |
267 | $problem = $wwdb->getProblem($user->id, $setName, $problemNumber); |
| … | |
… | |
| 248 | showSolutions => 0, |
289 | showSolutions => 0, |
| 249 | processAnswers => 0, |
290 | processAnswers => 0, |
| 250 | }, |
291 | }, |
| 251 | ); |
292 | ); |
| 252 | |
293 | |
| 253 | warn "***GET READY FOR PG WARNINGS!!!!!\n***SET=$setName PROBLEM=$problemNumber\n", |
294 | if ($pg->{warnings} ne "") { |
| 254 | $pg->{warnings}, "***OK NO MORE PG WARNINGS!!!!\n" if $pg->{warnings}; |
295 | push @{$self->{warnings}}, { |
|
|
296 | set => $setName, |
|
|
297 | problem => $problemNumber, |
|
|
298 | message => $pg->{warnings}, |
|
|
299 | }; |
|
|
300 | } |
|
|
301 | |
|
|
302 | if ($pg->{flags}->{error_flag}) { |
|
|
303 | push @{$self->{errors}}, { |
|
|
304 | set => $setName, |
|
|
305 | problem => $problemNumber, |
|
|
306 | message => $pg->{errors}, |
|
|
307 | context => $pg->{body_text}, |
|
|
308 | }; |
|
|
309 | # if there was an error, body_text contains |
|
|
310 | # the error context, not TeX code |
|
|
311 | $pg->{body_text} = undef; |
|
|
312 | } |
| 255 | |
313 | |
| 256 | return $pg->{body_text}; |
314 | return $pg->{body_text}; |
| 257 | } |
315 | } |
| 258 | |
316 | |
| 259 | sub texInclude { |
317 | sub texInclude { |