| … | |
… | |
| 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); |
|
|
21 | use File::Temp qw(tempdir); |
|
|
22 | use WeBWorK::DB::Classlist; |
|
|
23 | use WeBWorK::DB::WW; |
| 20 | use WeBWorK::Form; |
24 | use WeBWorK::Form; |
| 21 | use WeBWorK::Utils qw(readFile); |
25 | use WeBWorK::Utils qw(readFile); |
| 22 | |
26 | |
| 23 | sub texBlockComment { return "\n".("%"x80)."\n%% ".join("", @_)."\n".("%"x80)."\n\n"; } |
27 | sub texBlockComment { return "\n".("%"x80)."\n%% ".join("", @_)."\n".("%"x80)."\n\n"; } |
| 24 | |
28 | |
|
|
29 | sub initialize { |
|
|
30 | my $self = shift; |
|
|
31 | my $ce = $self->{courseEnvironment}; |
|
|
32 | $self->{cldb} = WeBWorK::DB::Classlist->new($ce); |
|
|
33 | $self->{wwdb} = WeBWorK::DB::WW->new($ce); |
|
|
34 | } |
|
|
35 | |
|
|
36 | sub path { |
|
|
37 | my ($self, undef, $args) = @_; |
|
|
38 | |
|
|
39 | my $ce = $self->{courseEnvironment}; |
|
|
40 | my $root = $ce->{webworkURLs}->{root}; |
|
|
41 | my $courseName = $ce->{courseName}; |
|
|
42 | return $self->pathMacro($args, |
|
|
43 | "Home" => "$root", |
|
|
44 | $courseName => "$root/$courseName", |
|
|
45 | "Hardcopy Generator" => "", |
|
|
46 | ); |
|
|
47 | } |
|
|
48 | |
|
|
49 | sub title { |
|
|
50 | return "Hardcore Generator"; |
|
|
51 | } |
|
|
52 | |
| 25 | sub go { |
53 | sub body { |
| 26 | my ($self, $singleSet) = @_; |
54 | my ($self, $singleSet) = @_; |
| 27 | $singleSet =~ s/^set//; |
55 | $singleSet =~ s/^set//; |
| 28 | my $r = $self->{r}; |
56 | my $r = $self->{r}; |
| 29 | my $ce = $self->{courseEnvironment}; |
57 | my $ce = $self->{courseEnvironment}; |
| 30 | $self->{wwdb} = WeBWorK::DB::WW->new($ce); |
58 | $self->{wwdb} = WeBWorK::DB::WW->new($ce); |
| 31 | |
59 | |
| 32 | my @sets = $r->param("set"); |
60 | my @sets = $r->param("set"); |
| 33 | unshift @sets, $singleSet; |
61 | unshift @sets, $singleSet; |
| 34 | return DECLINED unless @sets; |
62 | unless (@sets) { |
| 35 | |
63 | print CGI::p("No problem sets were specified."); |
| 36 | $r->content_type("text/plain"); |
|
|
| 37 | $r->send_http_header(); |
|
|
| 38 | $self->writePDF($singleSet); |
|
|
| 39 | #$self->{texFH} = \*STDOUT; |
|
|
| 40 | #$self->writeMultiSetTeX($singleSet); |
|
|
| 41 | |
|
|
| 42 | return OK; |
64 | return OK; |
|
|
65 | } |
|
|
66 | |
|
|
67 | print CGI::p("Generating your hardcopy..."); |
|
|
68 | my $url = $self->makeHardcopy(@sets); |
|
|
69 | if ($url) { |
|
|
70 | print CGI::p("Ok, your hardcopy is ready. Click the following link to download it."); |
|
|
71 | print CGI::p({-align=>"center"}, |
|
|
72 | CGI::big(CGI::a({-href=>$url}, "Download PDF Hardcopy")) |
|
|
73 | ); |
|
|
74 | } else { |
|
|
75 | print CGI::p("Hmm, looks like I was unable to generate the hardcopy you requested. I'm really sorry... :("); |
|
|
76 | } |
|
|
77 | |
|
|
78 | return ""; |
| 43 | } |
79 | } |
| 44 | |
80 | |
| 45 | sub writePDF { |
81 | # ----- |
|
|
82 | |
|
|
83 | sub makeHardcopy { |
| 46 | my ($self, @sets) = @_; |
84 | my ($self, @sets) = @_; |
| 47 | my $ce = $self->{courseEnvironment}; |
85 | my $courseName = $self->{courseEnvironment}->{courseName}; |
|
|
86 | my $userName = $self->{r}->param("user"); |
|
|
87 | my $tempDir = $self->{courseEnvironment}->{courseDirs}->{html_temp} |
|
|
88 | . "/hardcopy"; |
|
|
89 | my $tempURL = $self->{courseEnvironment}->{courseURLs}->{html_temp} |
|
|
90 | . "/hardcopy"; |
|
|
91 | |
|
|
92 | # determine name of PDF file |
|
|
93 | my $fileName; |
|
|
94 | if (@sets > 1) { |
|
|
95 | # multiset output |
|
|
96 | $fileName = "$courseName.$userName.multiset.pdf" |
|
|
97 | } elsif (@sets == 1) { |
|
|
98 | # only one set |
|
|
99 | my $setName = $sets[0]; |
|
|
100 | $fileName = "$courseName.$userName.$setName.pdf"; |
|
|
101 | } else { |
|
|
102 | $fileName = "$courseName.$userName.pdf"; |
|
|
103 | } |
|
|
104 | |
|
|
105 | my $tex = $self->getMultiSetTeX(@sets); |
|
|
106 | $self->latex2pdf($tex, $tempDir, $fileName) or return; |
|
|
107 | |
|
|
108 | return "$tempURL/$fileName"; |
|
|
109 | } |
|
|
110 | |
|
|
111 | sub latex2pdf { |
|
|
112 | # this is a little ad-hoc function which I will replace with a LaTeX |
|
|
113 | # module at some point (or put it in Utils). |
|
|
114 | my ($self, $tex, $fileBase, $fileName) = @_; |
|
|
115 | my $finalFile = "$fileBase/$fileName"; |
|
|
116 | my $ce = $self->{courseEnvironment}; |
|
|
117 | |
|
|
118 | # create a temporary directory for tex to shit in |
|
|
119 | my $wd = tempdir("webwork-hardcopy-XXXXXXXX", TMPDIR => 1); |
|
|
120 | my $texFile = "$wd/hardcopy.tex"; |
|
|
121 | my $pdfFile = "$wd/hardcopy.pdf"; |
|
|
122 | |
|
|
123 | # write the tex file |
|
|
124 | local *TEX; |
|
|
125 | open TEX, ">", $texFile; |
|
|
126 | print TEX $tex; |
|
|
127 | close TEX; |
|
|
128 | |
|
|
129 | # call pdflatex - we don't want to chdir in the mod_perl process, as |
|
|
130 | # that might step on the feet of other things (esp. in Apache 2.0) |
| 48 | my $pdflatex = $ce->{externalPrograms}->{pdflatex}; |
131 | my $pdflatex = $ce->{externalPrograms}->{pdflatex}; |
|
|
132 | system "cd $wd && $pdflatex $texFile"; |
| 49 | |
133 | |
| 50 | open $self->{texFH}, "|-", "$pdflatex -v2" or die "Failed to call $pdflatex: $!\n"; |
134 | if (-e $pdfFile) { |
| 51 | $self->writeMultiSetTeX(@sets); |
135 | # move resulting PDF file to appropriate location |
| 52 | close $self->{texFH}; |
136 | my $mv = $ce->{externalPrograms}->{mv}; |
|
|
137 | system $mv, $pdfFile, $finalFile and die "Failed to mv: $!\n"; |
|
|
138 | } |
|
|
139 | |
|
|
140 | # remove temporary directory |
|
|
141 | rmtree($wd, 0, 1); |
|
|
142 | |
|
|
143 | return -e $finalFile; |
| 53 | } |
144 | } |
| 54 | |
145 | |
|
|
146 | # ----- |
|
|
147 | |
| 55 | sub writeMultiSetTeX { |
148 | sub getMultiSetTeX { |
| 56 | my ($self, @sets) = @_; |
149 | my ($self, @sets) = @_; |
| 57 | my $texFH = $self->{texFH}; |
|
|
| 58 | my $ce = $self->{courseEnvironment}; |
150 | my $ce = $self->{courseEnvironment}; |
|
|
151 | my $tex = ""; |
| 59 | |
152 | |
| 60 | # print the document preamble |
153 | # the document preamble |
| 61 | $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{preamble}); |
154 | $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{preamble}); |
| 62 | |
155 | |
| 63 | while (my $set = shift @sets) { |
156 | while (my $set = shift @sets) { |
| 64 | $self->getSetTeX($set); |
157 | $tex .= $self->getSetTeX($set); |
| 65 | if (@sets) { |
158 | if (@sets) { |
| 66 | # divide sets, but not after the last set |
159 | # divide sets, but not after the last set |
| 67 | $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{setDivider}); |
160 | $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{setDivider}); |
| 68 | } |
161 | } |
| 69 | } |
162 | } |
| 70 | |
163 | |
| 71 | # print the document postamble |
164 | # the document postamble |
| 72 | $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{postamble}); |
165 | $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{postamble}); |
|
|
166 | |
|
|
167 | return $tex; |
| 73 | } |
168 | } |
| 74 | |
169 | |
| 75 | sub getSetTeX { |
170 | sub getSetTeX { |
| 76 | my ($self, $setName) = @_; |
171 | my ($self, $setName) = @_; |
| 77 | my $texFH = $self->{texFH}; |
|
|
| 78 | my $ce = $self->{courseEnvironment}; |
172 | my $ce = $self->{courseEnvironment}; |
| 79 | my $wwdb = $self->{wwdb}; |
173 | my $wwdb = $self->{wwdb}; |
| 80 | my $user = $self->{r}->param("user"); |
174 | my $user = $self->{r}->param("user"); |
| 81 | my @problemNumbers = sort { $a <=> $b } $wwdb->getProblems($user, $setName); |
175 | my @problemNumbers = sort { $a <=> $b } $wwdb->getProblems($user, $setName); |
| 82 | |
176 | |
| 83 | # get header and footer |
177 | # get header and footer |
| 84 | my $setHeader = $wwdb->getSet($user, $setName)->set_header |
178 | my $setHeader = $wwdb->getSet($user, $setName)->set_header |
| 85 | || $ce->{webworkFiles}->{hardcopySnippets}->{setHeader}; |
179 | || $ce->{webworkFiles}->{hardcopySnippets}->{setHeader}; |
| 86 | my $setFooter = $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; |
|
|
| 87 | # database doesn't support the following yet :( |
180 | # database doesn't support the following yet :( |
| 88 | #my $setFooter = $wwdb->getSet($user, $setName)->set_footer |
181 | #my $setFooter = $wwdb->getSet($user, $setName)->set_footer |
| 89 | # || $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; |
182 | # || $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; |
|
|
183 | # so we don't allow per-set customization, which is probably okay :) |
|
|
184 | my $setFooter = $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; |
|
|
185 | |
|
|
186 | my $tex = ""; |
| 90 | |
187 | |
| 91 | # render header |
188 | # render header |
| 92 | print $texFH texBlockComment("BEGIN $setName : $setHeader"); |
189 | $tex .= texBlockComment("BEGIN $setName : $setHeader"); |
| 93 | #print $texFH $self->getProblemTeX($setName, $setHeader); |
190 | #$tex .= $self->getProblemTeX($setName, 0, $setHeader); |
| 94 | |
191 | |
| 95 | # render each problem |
192 | # render each problem |
| 96 | while (my $problemNumber = shift @problemNumbers) { |
193 | while (my $problemNumber = shift @problemNumbers) { |
| 97 | print $texFH texBlockComment("BEGIN $setName : $problemNumber"); |
194 | $tex .= texBlockComment("BEGIN $setName : $problemNumber"); |
| 98 | print $texFH $self->getProblemTeX($setName, $problemNumber); |
195 | $tex .= $self->getProblemTeX($setName, $problemNumber); |
| 99 | if (@problemNumbers) { |
196 | if (@problemNumbers) { |
| 100 | # divide problems, but not after the last problem |
197 | # divide problems, but not after the last problem |
| 101 | $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{problemDivider}); |
198 | $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{problemDivider}); |
| 102 | } |
199 | } |
| 103 | } |
200 | } |
| 104 | |
201 | |
| 105 | # render footer |
202 | # render footer |
| 106 | print $texFH texBlockComment("BEGIN $setName : $setFooter"); |
203 | $tex .= texBlockComment("BEGIN $setName : $setFooter"); |
| 107 | print $texFH $self->getProblemTeX($setName, $setFooter); |
204 | $tex .= $self->getProblemTeX($setName, 0, $setFooter); |
|
|
205 | |
|
|
206 | return $tex; |
| 108 | } |
207 | } |
| 109 | |
208 | |
| 110 | sub getProblemTeX { |
209 | sub getProblemTeX { |
| 111 | my ($self, $setName, $problemNumber) = @_; |
210 | my ($self, $setName, $problemNumber, $pgFile) = @_; |
| 112 | my $r = $self->{r}; |
211 | my $r = $self->{r}; |
| 113 | my $ce = $self->{courseEnvironment}; |
212 | my $ce = $self->{courseEnvironment}; |
|
|
213 | |
|
|
214 | my $wwdb = $self->{wwdb}; |
|
|
215 | my $cldb = $self->{cldb}; |
|
|
216 | my $user = $cldb->getUser($r->param("user")); |
|
|
217 | my $set = $wwdb->getSet($user->id, $setName); |
|
|
218 | my $psvn = $wwdb->getPSVN($user->id, $setName); |
|
|
219 | |
|
|
220 | # decide what to do about problem number |
|
|
221 | my $problem; |
|
|
222 | if ($problemNumber) { |
|
|
223 | $problem = $wwdb->getProblem($user->id, $setName, $problemNumber); |
|
|
224 | } elsif ($pgFile) { |
|
|
225 | $problem = WeBWorK::Problem->new( |
|
|
226 | id => 0, |
|
|
227 | set_id => $set->id, |
|
|
228 | login_id => $user->id, |
|
|
229 | source_file => $pgFile, |
|
|
230 | # the rest of Problem's fields are not needed, i think |
|
|
231 | ); |
|
|
232 | } |
| 114 | |
233 | |
| 115 | my $pg = WeBWorK::PG->new( |
234 | my $pg = WeBWorK::PG->new( |
| 116 | $ce, |
235 | $ce, |
| 117 | $r->param('user'), |
236 | $user, |
| 118 | $r->param('key'), |
237 | $r->param('key'), |
| 119 | $setName, |
238 | $set, |
| 120 | $problemNumber, # this may be non-numeric, for headers and the like |
239 | $problem, |
|
|
240 | $psvn, |
|
|
241 | {}, # no form fields! |
| 121 | { # translation options |
242 | { # translation options |
| 122 | displayMode => "tex", |
243 | displayMode => "tex", |
| 123 | showHints => 0, |
244 | showHints => 0, |
| 124 | showSolutions => 0, |
245 | showSolutions => 0, |
| 125 | processAnswers => 0, |
246 | processAnswers => 0, |
| 126 | }, |
247 | }, |
| 127 | WeBWorK::Form->new->Vars # this is silly, i should say {} instead |
|
|
| 128 | ); |
248 | ); |
| 129 | |
249 | |
| 130 | # *** # handle errors/warnings here! |
250 | # *** # handle errors/warnings here! |
| 131 | return $pg->{body_text}; |
251 | return $pg->{body_text}; |
| 132 | } |
252 | } |
| 133 | |
253 | |
| 134 | sub texInclude { |
254 | sub texInclude { |
| 135 | my ($self, $texFile) = @_; |
255 | my ($self, $texFile) = @_; |
| 136 | my $texFH = $self->{texFH}; |
256 | my $tex = ""; |
| 137 | |
257 | |
| 138 | print $texFH texBlockComment("BEGIN: $texFile"); |
258 | $tex .= texBlockComment("BEGIN: $texFile"); |
| 139 | eval { |
259 | eval { |
| 140 | print $texFH readFile($texFile) |
260 | $tex .= readFile($texFile) |
| 141 | }; |
261 | }; |
| 142 | if ($@) { |
262 | if ($@) { |
| 143 | print $texFH texBlockComment($@); |
263 | $tex .= texBlockComment($@); |
| 144 | } |
264 | } |
|
|
265 | |
|
|
266 | return $tex; |
| 145 | } |
267 | } |
| 146 | |
268 | |
| 147 | 1; |
269 | 1; |