[system] / branches / gage_dev / webwork2 / lib / WeBWorK / ContentGenerator / Hardcopy.pm Repository:
ViewVC logotype

Annotation of /branches/gage_dev/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3722 - (view) (download) (as text)
Original Path: trunk/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm

1 : sh002i 478 ################################################################################
2 : sh002i 1663 # WeBWorK Online Homework Delivery System
3 :     # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4 : gage 3722 # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Hardcopy.pm,v 1.68 2005/10/14 19:10:04 sh002i Exp $
5 : sh002i 1663 #
6 :     # This program is free software; you can redistribute it and/or modify it under
7 :     # the terms of either: (a) the GNU General Public License as published by the
8 :     # Free Software Foundation; either version 2, or (at your option) any later
9 :     # version, or (b) the "Artistic License" which comes with this package.
10 :     #
11 :     # This program is distributed in the hope that it will be useful, but WITHOUT
12 :     # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 :     # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
14 :     # Artistic License for more details.
15 : sh002i 478 ################################################################################
16 :    
17 :     package WeBWorK::ContentGenerator::Hardcopy;
18 : sh002i 1146 use base qw(WeBWorK::ContentGenerator);
19 : sh002i 478
20 :     =head1 NAME
21 :    
22 : sh002i 3649 WeBWorK::ContentGenerator::Hardcopy - generate printable versions of one or more
23 : sh002i 478 problem sets.
24 :    
25 :     =cut
26 :    
27 :     use strict;
28 :     use warnings;
29 : sh002i 3649 use Apache::Constants qw/:common REDIRECT/;
30 :     use CGI qw//;
31 :     use String::ShellQuote;
32 : sh002i 3663 use WeBWorK::DB::Utils qw/user2global/;
33 : sh002i 3485 use WeBWorK::Debug;
34 : sh002i 3649 use WeBWorK::Form;
35 :     use WeBWorK::HTML::ScrollingRecordList qw/scrollingRecordList/;
36 : sh002i 1636 use WeBWorK::PG;
37 : sh002i 3649 use WeBWorK::Utils qw/readFile makeTempDirectory surePathToFile/;
38 : sh002i 478
39 : sh002i 2765 =head1 CONFIGURATION VARIABLES
40 :    
41 :     =over
42 :    
43 :     =item $PreserveTempFiles
44 :    
45 :     If true, don't delete temporary files.
46 :    
47 :     =cut
48 :    
49 : sh002i 2939 our $PreserveTempFiles = 0 unless defined $PreserveTempFiles;
50 : sh002i 2765
51 :     =back
52 :    
53 :     =cut
54 :    
55 : sh002i 3649 our $HC_DEFAULT_FORMAT = "pdf"; # problems if this is not an allowed format for the user...
56 :     our %HC_FORMATS = (
57 :     tex => { name => "TeX Source", subr => "generate_hardcopy_tex" },
58 :     pdf => { name => "Adobe PDF", subr => "generate_hardcopy_pdf" },
59 :     );
60 :    
61 :     # custom fields used in $self hash
62 : sh002i 3663 # FOR HEAVEN'S SAKE, PLEASE KEEP THIS UP-TO-DATE!
63 : sh002i 3649 #
64 :     # final_file_url
65 :     # contains the URL of the final hardcopy file generated
66 :     # set by generate_hardcopy(), used by pre_header_initialize() and body()
67 : sh002i 3663 #
68 : sh002i 3649 # temp_file_map
69 :     # reference to a hash mapping temporary file names to URL
70 :     # set by pre_header_initialize(), used by body()
71 : sh002i 3663 #
72 : sh002i 3649 # hardcopy_errors
73 :     # reference to array containing HTML strings describing generation errors (and warnings)
74 :     # used by add_errors(), get_errors(), get_errors_ref()
75 : sh002i 3663 #
76 :     # at_least_one_problem_rendered_without_error
77 :     # set to a true value by write_problem_tex if it is able to sucessfully render
78 :     # a problem. checked by generate_hardcopy to determine whether to continue
79 :     # with the generation process.
80 : sh002i 3649
81 :     ################################################################################
82 :     # UI subroutines
83 :     ################################################################################
84 :    
85 : malsyned 1409 sub pre_header_initialize {
86 : sh002i 1841 my ($self) = @_;
87 : sh002i 1892 my $r = $self->r;
88 :     my $ce = $r->ce;
89 :     my $db = $r->db;
90 : sh002i 2764 my $authz = $r->authz;
91 : sh002i 641
92 : sh002i 3649 my $userID = $r->param("user");
93 :     my $eUserID = $r->param("effectiveUser");
94 :     my @setIDs = $r->param("selected_sets");
95 :     my @userIDs = $r->param("selected_users");
96 :     my $hardcopy_format = $r->param("hardcopy_format");
97 :     my $generate_hardcopy = $r->param("generate_hardcopy");
98 :     my $send_existing_hardcopy = $r->param("send_existing_hardcopy");
99 :     my $final_file_url = $r->param("final_file_url");
100 : sh002i 3485
101 : sh002i 3649 # if there's an existing hardcopy file that can be sent, get set up to do that
102 :     if ($send_existing_hardcopy) {
103 :     $self->reply_with_redirect($final_file_url);
104 :     $self->{final_file_url} = $final_file_url;
105 :     $self->{send_hardcopy} = 1;
106 :     return;
107 : sh002i 641 }
108 : gage 3666
109 : sh002i 2939 # this should never happen, but apparently it did once (see bug #714), so we check for it
110 : sh002i 3649 die "Parameter 'user' not defined -- this should never happen" unless defined $userID;
111 : sh002i 2939
112 : sh002i 3649 if ($generate_hardcopy) {
113 :     my $validation_failed = 0;
114 :    
115 : sh002i 3659 # set default format
116 :     $hardcopy_format = $HC_DEFAULT_FORMAT unless defined $hardcopy_format;
117 :    
118 : sh002i 3649 # make sure format is valid
119 :     unless (grep { $_ eq $hardcopy_format } keys %HC_FORMATS) {
120 :     $self->addbadmessage("'$hardcopy_format' is not a valid hardcopy format.");
121 :     $validation_failed = 1;
122 :     }
123 :    
124 :     # make sure we are allowed to generate hardcopy in this format
125 :     unless ($authz->hasPermissions($userID, "download_hardcopy_format_$hardcopy_format")) {
126 :     $self->addbadmessage("You do not have permission to generate hardcopy in $hardcopy_format format.");
127 :     $validation_failed = 1;
128 :     }
129 :    
130 :     # is there at least one user and set selected?
131 :     unless (@userIDs) {
132 :     $self->addbadmessage("Please select at least one user and try again.");
133 :     $validation_failed = 1;
134 :     }
135 :     unless (@setIDs) {
136 :     $self->addbadmessage("Please select at least one set and try again.");
137 :     $validation_failed = 1;
138 :     }
139 :    
140 :     # is the user allowed to request multiple sets/users at a time?
141 :     my $perm_multiset = $authz->hasPermissions($userID, "download_hardcopy_multiset");
142 :     my $perm_multiuser = $authz->hasPermissions($userID, "download_hardcopy_multiuser");
143 :    
144 :     if (@setIDs > 1 and not $perm_multiset) {
145 :     $self->addbadmessage("You are not permitted to generate hardcopy for multiple sets. Please select a single set and try again.");
146 :     $validation_failed = 1;
147 :     }
148 :     if (@userIDs > 1 and not $perm_multiuser) {
149 :     $self->addbadmessage("You are not permitted to generate hardcopy for multiple users. Please select a single user and try again.");
150 :     $validation_failed = 1;
151 :     }
152 :     if (@userIDs and $userIDs[0] ne $eUserID and not $perm_multiuser) {
153 :     $self->addbadmessage("You are not permitted to generate hardcopy for other users.");
154 :     $validation_failed = 1;
155 : sh002i 3663 # FIXME -- download_hardcopy_multiuser controls both whether a user can generate hardcopy
156 :     # that contains sets for multiple users AND whether she can generate hardcopy that contains
157 :     # sets for users other than herself. should these be separate permission levels?
158 : sh002i 3649 }
159 :    
160 :     unless ($validation_failed) {
161 :     my ($final_file_url, %temp_file_map) = $self->generate_hardcopy($hardcopy_format, \@userIDs, \@setIDs);
162 :     if ($self->get_errors) {
163 :     # store the URLs in self hash so that body() can make a link to it
164 :     $self->{final_file_url} = $final_file_url;
165 :     $self->{temp_file_map} = \%temp_file_map;
166 : sh002i 737 } else {
167 : sh002i 3649 # send the file only
168 :     $self->reply_with_redirect($final_file_url);
169 : sh002i 737 }
170 :     }
171 :     }
172 : malsyned 1409 }
173 :    
174 : sh002i 3649 sub body {
175 : malsyned 1409 my ($self) = @_;
176 : sh002i 737
177 : sh002i 3649 if (my $num = $self->get_errors) {
178 :     my $final_file_url = $self->{final_file_url};
179 :     my %temp_file_map = %{$self->{temp_file_map}};
180 :    
181 :     my $errors_str = $num > 1 ? "errors" : "error";
182 :     print CGI::p("$num $errors_str occured while generating hardcopy:");
183 :    
184 :     print CGI::ul(CGI::li($self->get_errors_ref));
185 :    
186 :     if ($final_file_url) {
187 :     print CGI::p(
188 :     "A hardcopy file was generated, but it may not be complete or correct: ",
189 :     CGI::a({href=>$final_file_url}, "Download Hardcopy")
190 :     );
191 :     }
192 :    
193 :     if (%temp_file_map) {
194 :     print CGI::start_p();
195 :     print "You can also examine the following temporary files: ";
196 :     my $first = 1;
197 :     while (my ($temp_file_name, $temp_file_url) = each %temp_file_map) {
198 :     if ($first) {
199 :     $first = 0;
200 :     } else {
201 :     print ", ";
202 :     }
203 :     print CGI::a({href=>$temp_file_url}, " $temp_file_name");
204 :     }
205 :     print CGI::end_p();
206 :     }
207 :    
208 :     print CGI::hr();
209 : malsyned 1409 }
210 : sh002i 3649
211 :     $self->display_form();
212 : sh002i 502 }
213 :    
214 : sh002i 3649 sub display_form {
215 : sh002i 1892 my ($self) = @_;
216 : sh002i 3649 my $r = $self->r;
217 :     my $db = $r->db;
218 :     my $authz = $r->authz;
219 :     my $userID = $r->param("user");
220 :     my $eUserID = $r->param("effectiveUser");
221 : sh002i 478
222 : sh002i 3649 # first time we show up here, fill in some values
223 :     unless ($r->param("in_hc_form")) {
224 :     # if a set was passed in via the path_info, add that to the list of sets.
225 :     my $singleSet = $r->urlpath->arg("setID");
226 :     if (defined $singleSet and $singleSet ne "") {
227 :     my @selected_sets = $r->param("selected_sets");
228 :     $r->param("selected_sets" => [ @selected_sets, $singleSet]) unless grep { $_ eq $singleSet } @selected_sets;
229 : sh002i 669 }
230 : sh002i 3649
231 :     # if no users are selected, select the effective user
232 :     my @selected_users = $r->param("selected_users");
233 :     unless (@selected_users) {
234 :     $r->param("selected_users" => $eUserID);
235 :     }
236 : sh002i 737 }
237 : sh002i 3649
238 :     my $perm_multiset = $authz->hasPermissions($userID, "download_hardcopy_multiset");
239 :     my $perm_multiuser = $authz->hasPermissions($userID, "download_hardcopy_multiuser");
240 :     my $perm_texformat = $authz->hasPermissions($userID, "download_hardcopy_format_tex");
241 :     my $perm_unopened = $authz->hasPermissions($userID, "view_unopened_sets");
242 :     my $perm_unpublished = $authz->hasPermissions($userID, "view_unpublished_sets");
243 :    
244 :     # get formats
245 :     my @formats;
246 :     foreach my $format (keys %HC_FORMATS) {
247 :     push @formats, $format if $authz->hasPermissions($userID, "download_hardcopy_format_$format");
248 : sh002i 1146 }
249 : sh002i 737
250 : sh002i 3649 # get format names hash for radio buttons
251 :     my %format_labels = map { $_ => $HC_FORMATS{$_}{name} || $_ } @formats;
252 :    
253 :     # get users for selection
254 :     my @Users;
255 :     if ($perm_multiuser) {
256 :     # if we're allowed to select multiple users, get all the users
257 :     @Users = $db->getUsers($db->listUsers);
258 :     } else {
259 :     # otherwise, we get our own record only
260 :     @Users = $db->getUser($eUserID);
261 : sh002i 737 }
262 : sh002i 1146
263 : sh002i 3649 # get sets for selection
264 : sh002i 3663 my @GlobalSets;
265 : sh002i 3649 if ($perm_multiuser) {
266 : sh002i 3663 # if we're allowed to select sets for multiple users, get all sets.
267 :     @GlobalSets = $db->getGlobalSets($db->listGlobalSets);
268 : sh002i 3649 } else {
269 : sh002i 3663 # otherwise, only get the sets assigned to the effective user.
270 :     # note that we are getting GlobalSets, but using the list of UserSets assigned to the
271 :     # effective user. this is because if we pass UserSets to ScrollingRecordList it will
272 :     # give us composite IDs back, which is a pain in the ass to deal with.
273 :     @GlobalSets = $db->getGlobalSets($db->listUserSets($eUserID));
274 : sh002i 1146 }
275 : apizer 2785
276 : sh002i 3649 # filter out unwanted sets
277 : sh002i 3663 foreach my $i (0 .. $#GlobalSets) {
278 :     my $Set = $GlobalSets[$i];
279 :     unless (defined $Set) {
280 :     warn "\$GlobalSets[\$i] not defined -- skipping";
281 :     next;
282 :     }
283 :     splice @GlobalSets, $i, 1 unless $Set->open_date <= time or $perm_unopened;
284 :     splice @GlobalSets, $i, 1 unless $Set->published or $perm_unpublished;
285 : sh002i 737 }
286 :    
287 : sh002i 3649 my $scrolling_user_list = scrollingRecordList({
288 :     name => "selected_users",
289 :     request => $r,
290 :     default_sort => "lnfn",
291 :     default_format => "lnfn_uid",
292 :     default_filters => ["all"],
293 :     size => 20,
294 :     multiple => $perm_multiuser,
295 :     }, @Users);
296 : gage 1176
297 : sh002i 3649 my $scrolling_set_list = scrollingRecordList({
298 :     name => "selected_sets",
299 :     request => $r,
300 :     default_sort => "set_id",
301 :     default_format => "set_id",
302 :     default_filters => ["all"],
303 :     size => 20,
304 :     multiple => $perm_multiset,
305 : sh002i 3663 }, @GlobalSets);
306 : gage 1309
307 : sh002i 3649 # we change the text a little bit depending on whether the user has multiuser privileges
308 :     my $ss = $perm_multiuser ? "s" : "";
309 :     my $aa = $perm_multiuser ? " " : " a ";
310 :     my $phrase_for_privileged_users = $perm_multiuser ? "to privileged users or" : "";
311 : apizer 3668 my $button_label = $perm_multiuser ? "Generate hardcopy for selected sets and selected users" :"Generate hardcopy";
312 : sh002i 737
313 : gage 3666 # print CGI::start_p();
314 :     # print "Select the homework set$ss for which to generate${aa}hardcopy version$ss.";
315 :     # if ($authz->hasPermissions($userID, "download_hardcopy_multiuser")) {
316 :     # print "You may also select multiple users from the users list. You will receive hardcopy for each (set, user) pair.";
317 :     # }
318 :     # print CGI::end_p();
319 : sh002i 641
320 : sh002i 3649 print CGI::start_form(-method=>"POST", -action=>$r->uri);
321 :     print $self->hidden_authen_fields();
322 :     print CGI::hidden("in_hc_form", 1);
323 : sh002i 669
324 : gage 3666 if ($perm_multiuser and $perm_multiset) {
325 :     print CGI::p("Select the homework sets for which to generate hardcopy versions. You may"
326 :     ." also select multiple users from the users list. You will receive hardcopy"
327 :     ." for each (set, user) pair.");
328 :    
329 :     print CGI::table({class=>"FormLayout"},
330 :     CGI::Tr(
331 :     CGI::th("Users"),
332 :     CGI::th("Sets"),
333 :     ),
334 :     CGI::Tr(
335 :     CGI::td($scrolling_user_list),
336 :     CGI::td($scrolling_set_list),
337 :     ),
338 :     );
339 :     } else { # single user mode
340 :     #FIXME -- do a better job of getting the set and the user when in the single set mode
341 :     my $selected_set_id = $r->param("selected_sets");
342 :     my $selected_user_id = $Users[0]->user_id;
343 :     print CGI::hidden("selected_sets", $selected_set_id ),
344 :     CGI::hidden( "selected_users", $selected_user_id);
345 :    
346 :     print CGI::p("Download hardcopy of set ", $selected_set_id, " for ", $Users[0]->first_name, " ",$Users[0]->last_name,"?");
347 :    
348 :     }
349 : sh002i 3649 print CGI::table({class=>"FormLayout"},
350 :     CGI::Tr(
351 :     CGI::td({colspan=>2, class=>"ButtonRow"},
352 :     CGI::small("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."),
353 :     CGI::br(),
354 :     CGI::b("Show:"), " ",
355 :     CGI::checkbox(
356 :     -name => "showCorrectAnswers",
357 :     -checked => scalar($r->param("showCorrectAnswers")) || 0,
358 :     -label => "Correct answers",
359 :     ),
360 :     CGI::checkbox(
361 :     -name => "showHints",
362 :     -checked => scalar($r->param("showHints")) || 0,
363 :     -label => "Hints",
364 :     ),
365 :     CGI::checkbox(
366 :     -name => "showSolutions",
367 :     -checked => scalar($r->param("showSolutions")) || 0,
368 :     -label => "Solutions",
369 :     ),
370 :     ),
371 :     ),
372 :     CGI::Tr(
373 :     CGI::td({colspan=>2, class=>"ButtonRow"},
374 :     CGI::b("Hardcopy Format:"), " ",
375 : gage 1176 CGI::radio_group(
376 : sh002i 3649 -name => "hardcopy_format",
377 :     -values => \@formats,
378 :     -default => scalar($r->param("hardcopy_format")) || $HC_DEFAULT_FORMAT,
379 :     -labels => \%format_labels,
380 : gage 1176 ),
381 : sh002i 3649 ),
382 :     ),
383 :     CGI::Tr(
384 :     CGI::td({colspan=>2, class=>"ButtonRow"},
385 :     CGI::submit(
386 :     -name => "generate_hardcopy",
387 : apizer 3668 -value => $button_label,
388 : sh002i 3649 #-style => "width: 45ex",
389 :     ),
390 :     ),
391 :     ),
392 :     );
393 :    
394 : sh002i 737 print CGI::end_form();
395 :    
396 : sh002i 641 return "";
397 : sh002i 502 }
398 :    
399 : sh002i 3649 ################################################################################
400 :     # harddcopy generating subroutines
401 :     ################################################################################
402 :    
403 :     sub generate_hardcopy {
404 :     my ($self, $format, $userIDsRef, $setIDsRef) = @_;
405 :     my $r = $self->r;
406 :     my $ce = $r->ce;
407 :     my $db = $r->db;
408 :     my $authz = $r->authz;
409 : sh002i 737
410 : sh002i 3649 my $courseID = $r->urlpath->arg("courseID");
411 :     my $userID = $r->param("user");
412 :     my $eUserID = $r->param("effectiveUser");
413 : sh002i 1146
414 : sh002i 3649 # we want to make the temp directory web-accessible, for error reporting
415 :     my $temp_dir_parent_path = $ce->{courseDirs}{html_temp} . "/hardcopy"; # makeTempDirectory will ensure that .../hardcopy exists
416 :     my $temp_dir_path = eval { makeTempDirectory($temp_dir_parent_path, "work") };
417 :     if ($@) {
418 : sh002i 3711 $self->add_errors(CGI::escapeHTML($@));
419 : sh002i 3649 return;
420 :     }
421 : sh002i 737
422 : sh002i 3649 # do some error checking
423 : sh002i 3663 unless (-e $temp_dir_path) {
424 : sh002i 3711 $self->add_errors("Temporary directory '".CGI::code(CGI::escapeHTML($temp_dir_path))
425 :     ."' does not exist, but creation didn't fail. This shouldn't happen.");
426 : sh002i 3663 return;
427 :     }
428 : sh002i 3649 unless (-w $temp_dir_path) {
429 : sh002i 3711 $self->add_errors("Temporary directory '".CGI::code(CGI::escapeHTML($temp_dir_path))
430 :     ."' is not writeable.");
431 : sh002i 3663 $self->delete_temp_dir($temp_dir_path);
432 : sh002i 3649 return;
433 : sh002i 737 }
434 :    
435 : sh002i 3663 my $tex_file_name = "hardcopy.tex";
436 :     my $tex_file_path = "$temp_dir_path/$tex_file_name";
437 :    
438 : sh002i 3649 # write TeX
439 :     my $open_result = open my $FH, ">", $tex_file_path;
440 :     unless ($open_result) {
441 : sh002i 3711 $self->add_errors("Failed to open file '".CGI::code(CGI::escapeHTML($tex_file_path))
442 :     ."' for writing: ".CGI::code(CGI::escapeHTML($!)));
443 : sh002i 3663 $self->delete_temp_dir($temp_dir_path);
444 : sh002i 3649 return;
445 : sh002i 737 }
446 : sh002i 3649 $self->write_multiuser_tex($FH, $userIDsRef, $setIDsRef);
447 :     close $FH;
448 : sh002i 737
449 : sh002i 3663 # if no problems got rendered successfully, we can't continue
450 :     unless ($self->{at_least_one_problem_rendered_without_error}) {
451 :     $self->add_errors("No problems rendered. Can't continue.");
452 :     $self->delete_temp_dir($temp_dir_path);
453 :     return;
454 :     }
455 :    
456 :     # if no hardcopy.tex file was generated, fail now
457 :     unless (-e "$temp_dir_path/hardcopy.tex") {
458 : sh002i 3711 $self->add_errors("'".CGI::code("hardcopy.tex")."' not written to temporary directory '"
459 :     .CGI::code(CGI::escapeHTML($temp_dir_path))."'. Can't continue.");
460 : sh002i 3663 $self->delete_temp_dir($temp_dir_path);
461 :     return;
462 :     }
463 :    
464 : sh002i 3649 # determine base name of final file
465 :     my $final_file_user = @$userIDsRef > 1 ? "multiuser" : $userIDsRef->[0];
466 :     my $final_file_set = @$setIDsRef > 1 ? "multiset" : $setIDsRef->[0];
467 :     my $final_file_basename = "$courseID.$final_file_user.$final_file_set";
468 : sh002i 1146
469 : sh002i 3649 # call format subroutine
470 :     # $final_file_name is the name of final hardcopy file
471 :     # @temp_files is a list of temporary files of interest used by the subroutine
472 :     # (all are relative to $temp_dir_path)
473 :     my $format_subr = $HC_FORMATS{$format}{subr};
474 :     my ($final_file_name, @temp_files) = $self->$format_subr($temp_dir_path, $final_file_basename);
475 :     my $final_file_path = "$temp_dir_path/$final_file_name";
476 : gage 1175
477 : sh002i 3649 #warn "final_file_name=$final_file_name\n";
478 :     #warn "temp_files=@temp_files\n";
479 : sh002i 2495
480 : sh002i 3649 # calculate URLs for each temp file of interest
481 :     # makeTempDirectory's interface forces us to reverse-engineer the name of the temp dir from the path
482 :     my $temp_dir_parent_url = $ce->{courseURLs}{html_temp} . "/hardcopy";
483 :     (my $temp_dir_url = $temp_dir_path) =~ s/^$temp_dir_parent_path/$temp_dir_parent_url/;
484 :     my %temp_file_map;
485 :     foreach my $temp_file_name (@temp_files) {
486 :     $temp_file_map{$temp_file_name} = "$temp_dir_url/$temp_file_name";
487 :     }
488 : gage 1175
489 : sh002i 3649 my $final_file_url;
490 : gage 1175
491 : sh002i 3649 # make sure final file exists
492 :     unless (-e $final_file_path) {
493 : sh002i 3711 $self->add_errors("Final hardcopy file '".CGI::code(CGI::escapeHTML($final_file_path))
494 :     ."' not found after calling '".CGI::code(CGI::escapeHTML($format_subr))."': "
495 :     .CGI::code(CGI::escapeHTML($!)));
496 : sh002i 3649 return $final_file_url, %temp_file_map;
497 :     }
498 : gage 1175
499 : sh002i 3649 # try to move the hardcopy file out of the temp directory
500 :     # set $final_file_url accordingly
501 :     my $final_file_final_path = "$temp_dir_parent_path/$final_file_name";
502 : sh002i 3663 my $mv_cmd = "2>&1 /bin/mv " . shell_quote($final_file_path, $final_file_final_path);
503 :     my $mv_out = readpipe $mv_cmd;
504 :     if ($?) {
505 : sh002i 3711 $self->add_errors("Failed to move hardcopy file '".CGI::code(CGI::escapeHTML($final_file_name))
506 :     ."' from '".CGI::code(CGI::escapeHTML($temp_dir_path))."' to '"
507 :     .CGI::code(CGI::escapeHTML($temp_dir_parent_path))."':".CGI::br()
508 :     .CGI::pre(CGI::escapeHTML($mv_out)));
509 : sh002i 3649 $final_file_url = "$temp_dir_url/$final_file_name";
510 :     } else {
511 :     $final_file_url = "$temp_dir_parent_url/$final_file_name";
512 : sh002i 737 }
513 : sh002i 3649
514 :     # remove the temp directory if there are no errors
515 :     unless ($self->get_errors or $PreserveTempFiles) {
516 : sh002i 3663 $self->delete_temp_dir($temp_dir_path);
517 : sh002i 3649 }
518 :    
519 :     warn "Preserved temporary files in directory '$temp_dir_path'.\n" if $PreserveTempFiles;
520 :    
521 :     return $final_file_url, %temp_file_map;
522 : sh002i 737 }
523 :    
524 : sh002i 3663 # helper function to remove temp dirs
525 :     sub delete_temp_dir {
526 :     my ($self, $temp_dir_path) = @_;
527 :    
528 :     my $rm_cmd = "2>&1 /bin/rm -rf " . shell_quote($temp_dir_path);
529 :     my $rm_out = readpipe $rm_cmd;
530 :     if ($?) {
531 : sh002i 3711 $self->add_errors("Failed to remove temporary directory '".CGI::code(CGI::escapeHTML($temp_dir_path))."':"
532 : sh002i 3663 .CGI::br().CGI::pre($rm_out));
533 :     return 0;
534 :     } else {
535 :     return 1;
536 :     }
537 :     }
538 :    
539 : sh002i 3649 # format subroutines
540 :     #
541 :     # assume that TeX source is located at $temp_dir_path/hardcopy.tex
542 :     # the generated file will being with $final_file_basename
543 :     # first element of return value is the name of the generated file (relative to $temp_dir_path)
544 :     # rest of return value elements are names of temporary files that may be of interest in the
545 :     # case of an error, relative to $temp_dir_path. these are returned whether or not an error
546 :     # actually occured.
547 : sh002i 641
548 : sh002i 3649 sub generate_hardcopy_tex {
549 :     my ($self, $temp_dir_path, $final_file_basename) = @_;
550 : sh002i 1892
551 : sh002i 3649 my $final_file_name;
552 : sh002i 502
553 : sh002i 3649 # try to rename tex file
554 :     my $src_name = "hardcopy.tex";
555 :     my $dest_name = "$final_file_basename.tex";
556 : sh002i 3663 my $mv_cmd = "2>&1 /bin/mv " . shell_quote("$temp_dir_path/$src_name", "$temp_dir_path/$dest_name");
557 :     my $mv_out = readpipe $mv_cmd;
558 :     if ($?) {
559 : sh002i 3711 $self->add_errors("Failed to rename '".CGI::code(CGI::escapeHTML($src_name))."' to '"
560 :     .CGI::code(CGI::escapeHTML($dest_name))."' in directory '"
561 :     .CGI::code(CGI::escapeHTML($temp_dir_path))."':".CGI::br()
562 :     .CGI::pre(CGI::escapeHTML($mv_out)));
563 : sh002i 3649 $final_file_name = $src_name;
564 :     } else {
565 :     $final_file_name = $dest_name;
566 :     }
567 : gage 3609
568 : sh002i 3649 return $final_file_name;
569 :     }
570 :    
571 :     sub generate_hardcopy_pdf {
572 :     my ($self, $temp_dir_path, $final_file_basename) = @_;
573 : gage 3609
574 : sh002i 502 # call pdflatex - we don't want to chdir in the mod_perl process, as
575 :     # that might step on the feet of other things (esp. in Apache 2.0)
576 : sh002i 3649 my $pdflatex_cmd = "cd " . shell_quote($temp_dir_path) . " && "
577 :     . $self->r->ce->{externalPrograms}{pdflatex}
578 :     . " >pdflatex.stdout 2>pdflatex.stderr hardcopy";
579 :     if (system $pdflatex_cmd) {
580 : sh002i 3711 $self->add_errors("Failed to convert TeX to PDF with command '"
581 :     .CGI::code(CGI::escapeHTML($pdflatex_cmd))."'.");
582 :    
583 :     # read hardcopy.log and report first error
584 :     my $hardcopy_log = "$temp_dir_path/hardcopy.log";
585 :     if (-e $hardcopy_log) {
586 :     if (open my $LOG, "<", $hardcopy_log) {
587 :     my $line;
588 :     while ($line = <$LOG>) {
589 :     last if $line =~ /^!\s+/;
590 :     }
591 :     my $first_error = $line;
592 :     while ($line = <$LOG>) {
593 :     last if $line =~ /^!\s+/;
594 :     $first_error .= $line;
595 :     }
596 :     close $LOG;
597 :     if (defined $first_error) {
598 :     $self->add_errors("First error in TeX log is:".CGI::br().
599 :     CGI::pre(CGI::escapeHTML($first_error)));
600 :     } else {
601 :     $self->add_errors("No errors encoundered in TeX log.");
602 :     }
603 :     } else {
604 :     $self->add_errors("Could not read TeX log: ".CGI::code(CGI::escapeHTML($!)));
605 :     }
606 :     } else {
607 :     $self->add_errors("No TeX log was found.");
608 :     }
609 : gage 1153 }
610 : sh002i 478
611 : sh002i 3649 my $final_file_name;
612 : sh002i 502
613 : sh002i 3649 # try rename the pdf file
614 :     my $src_name = "hardcopy.pdf";
615 :     my $dest_name = "$final_file_basename.pdf";
616 : sh002i 3663 my $mv_cmd = "2>&1 /bin/mv " . shell_quote("$temp_dir_path/$src_name", "$temp_dir_path/$dest_name");
617 :     my $mv_out = readpipe $mv_cmd;
618 :     if ($?) {
619 : sh002i 3711 $self->add_errors("Failed to rename '".CGI::code(CGI::escapeHTML($src_name))."' to '"
620 :     .CGI::code(CGI::escapeHTML($dest_name))."' in directory '"
621 :     .CGI::code(CGI::escapeHTML($temp_dir_path))."':".CGI::br()
622 :     .CGI::pre(CGI::escapeHTML($mv_out)));
623 : sh002i 3649 $final_file_name = $src_name;
624 : sh002i 2765 } else {
625 : sh002i 3649 $final_file_name = $dest_name;
626 : sh002i 2765 }
627 : sh002i 502
628 : sh002i 3649 return $final_file_name, qw/hardcopy.tex hardcopy.log hardcopy.aux pdflatex.stdout pdflatex.stderr/;
629 : sh002i 478 }
630 :    
631 : sh002i 3649 ################################################################################
632 :     # TeX aggregating subroutines
633 :     ################################################################################
634 : gage 1157
635 : sh002i 3649 sub write_multiuser_tex {
636 :     my ($self, $FH, $userIDsRef, $setIDsRef) = @_;
637 :     my $r = $self->r;
638 :     my $ce = $r->ce;
639 : gage 1157
640 : sh002i 3649 my @userIDs = @$userIDsRef;
641 :     my @setIDs = @$setIDsRef;
642 : sh002i 492
643 : sh002i 3649 # get snippets
644 :     my $preamble = $ce->{webworkFiles}->{hardcopySnippets}->{preamble};
645 :     my $postamble = $ce->{webworkFiles}->{hardcopySnippets}->{postamble};
646 :     my $divider = $ce->{webworkFiles}->{hardcopySnippets}->{userDivider};
647 : sh002i 492
648 : sh002i 3649 # write preamble
649 :     $self->write_tex_file($FH, $preamble);
650 : gage 1113
651 : sh002i 3649 # write section for each user
652 :     while (defined (my $userID = shift @userIDs)) {
653 :     $self->write_multiset_tex($FH, $userID, @setIDs);
654 :     $self->write_tex_file($FH, $divider) if @userIDs; # divide users, but not after the last user
655 : sh002i 492 }
656 :    
657 : sh002i 3649 # write postamble
658 :     $self->write_tex_file($FH, $postamble);
659 :     }
660 : gage 1113
661 : sh002i 3649 sub write_multiset_tex {
662 :     my ($self, $FH, $targetUserID, @setIDs) = @_;
663 :     my $r = $self->r;
664 :     my $ce = $r->ce;
665 :     my $db = $r->db;
666 : sh002i 502
667 : sh002i 3649 # get user record
668 :     my $TargetUser = $db->getUser($targetUserID); # checked
669 :     unless ($TargetUser) {
670 : sh002i 3711 $self->add_errors("Can't generate hardcopy for user '".CGI::code(CGI::escapeHTML($targetUserID))."' -- no such user exists.\n");
671 : sh002i 3649 return;
672 :     }
673 :    
674 :     # get set divider
675 :     my $divider = $ce->{webworkFiles}->{hardcopySnippets}->{setDivider};
676 :    
677 :     # write each set
678 :     while (defined (my $setID = shift @setIDs)) {
679 :     $self->write_set_tex($FH, $TargetUser, $setID);
680 :     $self->write_tex_file($FH, $divider) if @setIDs; # divide sets, but not after the last set
681 :     }
682 : sh002i 492 }
683 :    
684 : sh002i 3649 sub write_set_tex {
685 :     my ($self, $FH, $TargetUser, $setID) = @_;
686 : sh002i 1892 my $r = $self->r;
687 :     my $ce = $r->ce;
688 :     my $db = $r->db;
689 : sh002i 3649 my $authz = $r->authz;
690 :     my $userID = $r->param("user");
691 : gage 1113
692 : sh002i 3649 # get set record
693 :     my $MergedSet = $db->getMergedSet($TargetUser->user_id, $setID); # checked
694 :     unless ($MergedSet) {
695 : sh002i 3711 $self->add_errors("Can't generate hardcopy for set ''".CGI::code(CGI::escapeHTML($setID))
696 :     ."' for user '".CGI::code(CGI::escapeHTML($TargetUser->user_id))
697 :     ."' -- set is not assigned to that user.");
698 : sh002i 3649 return;
699 :     }
700 : gage 1113
701 : sh002i 3649 # see if the *real* user is allowed to access this problem set
702 :     if ($MergedSet->open_date > time and not $authz->hasPermissions($userID, "view_unopened_sets")) {
703 : sh002i 3711 $self->add_errors("Can't generate hardcopy for set '".CGI::code(CGI::escapeHTML($setID))
704 :     ."' for user '".CGI::code(CGI::escapeHTML($TargetUser->user_id))
705 :     ."' -- set is not yet open.");
706 : sh002i 3649 return;
707 :     }
708 :     if (not $MergedSet->published and not $authz->hasPermissions($userID, "view_unpublished_sets")) {
709 : sh002i 3711 $self->addbadmessage("Can't generate hardcopy for set '".CGI::code(CGI::escapeHTML($setID))
710 :     ."' for user '".CGI::code(CGI::escapeHTML($TargetUser->user_id))
711 :     ."' -- set has not been published.");
712 : sh002i 3649 return;
713 :     }
714 : gage 1113
715 : sh002i 3649 # get snippets
716 :     my $header = $MergedSet->hardcopy_header
717 :     ? $MergedSet->hardcopy_header
718 :     : $ce->{webworkFiles}->{hardcopySnippets}->{setHeader};
719 :     my $footer = $ce->{webworkFiles}->{hardcopySnippets}->{setFooter};
720 :     my $divider = $ce->{webworkFiles}->{hardcopySnippets}->{problemDivider};
721 : sh002i 478
722 : sh002i 3649 # get list of problem IDs
723 :     my @problemIDs = sort { $a <=> $b } $db->listUserProblems($MergedSet->user_id, $MergedSet->set_id);
724 : sh002i 478
725 : sh002i 3649 # write set header
726 :     $self->write_problem_tex($FH, $TargetUser, $MergedSet, 0, $header); # 0 => pg file specified directly
727 : sh002i 502
728 : sh002i 3649 # write each problem
729 :     while (my $problemID = shift @problemIDs) {
730 :     $self->write_tex_file($FH, $divider);
731 :     $self->write_problem_tex($FH, $TargetUser, $MergedSet, $problemID);
732 : sh002i 478 }
733 :    
734 : sh002i 3649 # write footer
735 :     $self->write_problem_tex($FH, $TargetUser, $MergedSet, 0, $footer); # 0 => pg file specified directly
736 : sh002i 478 }
737 :    
738 : sh002i 3649 sub write_problem_tex {
739 :     my ($self, $FH, $TargetUser, $MergedSet, $problemID, $pgFile) = @_;
740 :     my $r = $self->r;
741 :     my $ce = $r->ce;
742 :     my $db = $r->db;
743 : gage 2902 my $authz = $r->authz;
744 : sh002i 3649 my $userID = $r->param("user");
745 : gage 1113
746 : sh002i 3649 my @errors;
747 : sh002i 2764
748 : sh002i 3649 # get problem record
749 :     my $MergedProblem;
750 :     if ($problemID) {
751 :     # a non-zero problem ID was given -- load that problem
752 :     $MergedProblem = $db->getMergedProblem($MergedSet->user_id, $MergedSet->set_id, $problemID); # checked
753 :    
754 :     # handle nonexistent problem
755 :     unless ($MergedProblem) {
756 : sh002i 3711 $self->add_errors("Can't generate hardcopy for problem '"
757 :     .CGI::code(CGI::escapeHTML($problemID))."' in set '"
758 :     .CGI::code(CGI::escapeHTML($MergedSet->set_id))
759 :     ."' for user '".CGI::code(CGI::escapeHTML($MergedSet->user_id))
760 :     ."' -- problem does not exist in that set or is not assigned to that user.");
761 : sh002i 3649 return;
762 :     }
763 : sh002i 502 } elsif ($pgFile) {
764 : sh002i 3649 # otherwise, we try an explicit PG file
765 :     $MergedProblem = $db->newUserProblem(
766 :     user_id => $MergedSet->user_id,
767 :     set_id => $MergedSet->set_id,
768 : sh002i 818 problem_id => 0,
769 : sh002i 502 source_file => $pgFile,
770 :     );
771 : sh002i 3649 die "newUserProblem failed -- WTF?" unless $MergedProblem; # this should never happen
772 :     } else {
773 :     # this shouldn't happen -- error out for real
774 :     die "write_problem_tex needs either a non-zero \$problemID or a \$pgFile";
775 : sh002i 502 }
776 : sh002i 3649
777 :     # figure out if we're allowed to get correct answers, hints, and solutions
778 :     # (eventually, we'd like to be able to use the same code as Problem)
779 : gage 3321 my $showCorrectAnswers = $r->param("showCorrectAnswers") || 0;
780 :     my $showHints = $r->param("showHints") || 0;
781 :     my $showSolutions = $r->param("showSolutions") || 0;
782 : sh002i 3649 unless ($authz->hasPermissions($userID, "view_answers") or time > $MergedSet->answer_date) {
783 : sh002i 738 $showCorrectAnswers = 0;
784 :     $showSolutions = 0;
785 :     }
786 : sh002i 3649
787 :     # FIXME -- there can be a problem if the $siteDefaults{timezone} is not defined? Why is this?
788 : gage 3321 # why does it only occur with hardcopy?
789 : sh002i 478 my $pg = WeBWorK::PG->new(
790 :     $ce,
791 : sh002i 3649 $TargetUser,
792 :     scalar($r->param('key')), # avoid multiple-values problem
793 :     $MergedSet,
794 :     $MergedProblem,
795 :     $MergedSet->psvn,
796 : sh002i 502 {}, # no form fields!
797 : sh002i 478 { # translation options
798 :     displayMode => "tex",
799 : sh002i 3649 showHints => $showHints ? 1 : 0, # insure that this value is numeric
800 :     showSolutions => $showSolutions ? 1 : 0, # (or what? -sam)
801 :     processAnswers => $showCorrectAnswers ? 1 : 0,
802 : sh002i 478 },
803 :     );
804 :    
805 : sh002i 3649 # only bother to generate this info if there were warnings or errors
806 :     my $edit_url;
807 :     my $problem_name;
808 :     my $problem_desc;
809 :     if ($pg->{warnings} ne "" or $pg->{flags}->{error_flag}) {
810 :     my $edit_urlpath = $r->urlpath->newFromModule(
811 :     "WeBWorK::ContentGenerator::Instructor::PGProblemEditor",
812 :     courseID => $r->urlpath->arg("courseID"),
813 :     setID => $MergedProblem->set_id,
814 :     problemID => $MergedProblem->problem_id,
815 :     );
816 :    
817 :     if ($MergedProblem->problem_id == 0) {
818 :     # link for an fake problem (like a header file)
819 :     $edit_url = $self->systemLink($edit_urlpath,
820 :     params => {
821 :     sourceFilePath => $MergedProblem->source_file,
822 :     problemSeed => $MergedProblem->problem_seed,
823 :     },
824 :     );
825 :     } else {
826 :     # link for a real problem
827 : gage 3666 $edit_url = $self->systemLink($edit_urlpath);
828 : sh002i 3649 }
829 :    
830 :     if ($MergedProblem->problem_id == 0) {
831 :     $problem_name = "snippet";
832 : sh002i 3663 $problem_desc = $problem_name." '".$MergedProblem->source_file
833 :     ."' for set '".$MergedProblem->set_id."' and user '"
834 :     .$MergedProblem->user_id."'";
835 : sh002i 3649 } else {
836 :     $problem_name = "problem";
837 : sh002i 3663 $problem_desc = $problem_name." '".$MergedProblem->problem_id
838 :     ."' in set '".$MergedProblem->set_id."' for user '"
839 :     .$MergedProblem->user_id."'";
840 : sh002i 3649 }
841 :     }
842 :    
843 :     # deal with PG warnings
844 : sh002i 641 if ($pg->{warnings} ne "") {
845 : sh002i 3649 $self->add_errors(CGI::a({href=>$edit_url}, "[edit]")
846 :     ."Warnings encountered while processing $problem_desc. "
847 : sh002i 3711 ."Error text:".CGI::br().CGI::pre(CGI::escapeHTML($pg->{warnings}))
848 : sh002i 3649 );
849 : sh002i 641 }
850 : sh002i 607
851 : sh002i 3649 # deal with PG errors
852 : sh002i 641 if ($pg->{flags}->{error_flag}) {
853 : sh002i 3649 $self->add_errors(CGI::a({href=>$edit_url}, "[edit]")
854 :     ."Errors encountered while processing $problem_desc. "
855 :     ."This $problem_name has been omitted from the hardcopy. "
856 : gage 3722 ."Error text:".CGI::br().CGI::pre(CGI::escapeHTML($pg->{errors}))
857 : sh002i 3649 );
858 :     return;
859 :     }
860 :    
861 : sh002i 3663 # if we got here, there were no errors (because errors cause a return above)
862 :     $self->{at_least_one_problem_rendered_without_error} = 1;
863 :    
864 : sh002i 3649 print $FH $pg->{body_text};
865 :    
866 :     # write the list of correct answers is appropriate
867 : sh002i 3700 my @ans_entry_order = @{$pg->{flags}->{ANSWER_ENTRY_ORDER}};
868 :     if ($showCorrectAnswers && $MergedProblem->problem_id != 0 && @ans_entry_order) {
869 : sh002i 3649 my $correctTeX = "\\par{\\small{\\it Correct Answers:}\n"
870 :     . "\\vspace{-\\parskip}\\begin{itemize}\n";
871 :    
872 : sh002i 3700 foreach my $ansName (@ans_entry_order) {
873 : sh002i 3649 my $correctAnswer = $pg->{answers}->{$ansName}->{correct_ans};
874 :     $correctTeX .= "\\item\\begin{verbatim}$correctAnswer\\end{verbatim}\n";
875 :     # FIXME: What about vectors (where TeX will complain about < and > outside of math mode)?
876 : sh002i 739 }
877 : sh002i 3649
878 :     $correctTeX .= "\\end{itemize}}\\par\n";
879 :    
880 :     print $FH $correctTeX;
881 : sh002i 641 }
882 : sh002i 478 }
883 :    
884 : sh002i 3649 sub write_tex_file {
885 :     my ($self, $FH, $file) = @_;
886 : sh002i 492
887 : sh002i 3649 my $tex = eval { readFile($file) };
888 : sh002i 492 if ($@) {
889 : sh002i 3711 $self->add_errors("Failed to include TeX file '".CGI::code(CGI::escapeHTML($file))."': "
890 :     .CGI::escapeHTML($@));
891 : sh002i 3649 } else {
892 :     print $FH $tex;
893 : sh002i 492 }
894 : sh002i 478 }
895 :    
896 : sh002i 3649 ################################################################################
897 :     # utilities
898 :     ################################################################################
899 :    
900 :     sub add_errors {
901 :     my ($self, @errors) = @_;
902 :     push @{$self->{hardcopy_errors}}, @errors;
903 :     }
904 :    
905 :     sub get_errors {
906 :     my ($self) = @_;
907 :     return $self->{hardcopy_errors} ? @{$self->{hardcopy_errors}} : ();
908 :     }
909 :    
910 :     sub get_errors_ref {
911 :     my ($self) = @_;
912 :     return $self->{hardcopy_errors};
913 :     }
914 :    
915 : sh002i 478 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9