[system] / branches / rel-2-4-dev / webwork-modperl / lib / WeBWorK / ContentGenerator / Hardcopy.pm Repository:
ViewVC logotype

Diff of /branches/rel-2-4-dev/webwork-modperl/lib/WeBWorK/ContentGenerator/Hardcopy.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

Revision 3659 Revision 4883
1################################################################################ 1################################################################################
2# WeBWorK Online Homework Delivery System 2# WeBWorK Online Homework Delivery System
3# Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ 3# Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/
4# $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm,v 1.62 2005/09/23 23:26:35 sh002i Exp $ 4# $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm,v 1.90 2007/03/13 21:18:18 glarose Exp $
5# 5#
6# This program is free software; you can redistribute it and/or modify it under 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 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 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. 9# version, or (b) the "Artistic License" which comes with this package.
24 24
25=cut 25=cut
26 26
27use strict; 27use strict;
28use warnings; 28use warnings;
29
29use Apache::Constants qw/:common REDIRECT/; 30#use Apache::Constants qw/:common REDIRECT/;
30use CGI qw//; 31#use CGI qw(-nosticky );
32use WeBWorK::CGI;
33
34use File::Path;
35use File::Temp qw/tempdir/;
31use String::ShellQuote; 36use String::ShellQuote;
37use WeBWorK::DB::Utils qw/user2global/;
32use WeBWorK::Debug; 38use WeBWorK::Debug;
33use WeBWorK::Form; 39use WeBWorK::Form;
34use WeBWorK::HTML::ScrollingRecordList qw/scrollingRecordList/; 40use WeBWorK::HTML::ScrollingRecordList qw/scrollingRecordList/;
35use WeBWorK::PG; 41use WeBWorK::PG;
36use WeBWorK::Utils qw/readFile makeTempDirectory surePathToFile/; 42use WeBWorK::Utils qw/readFile decodeAnswers/;
43use PGrandom;
37 44
38=head1 CONFIGURATION VARIABLES 45=head1 CONFIGURATION VARIABLES
39 46
40=over 47=over
41 48
56 tex => { name => "TeX Source", subr => "generate_hardcopy_tex" }, 63 tex => { name => "TeX Source", subr => "generate_hardcopy_tex" },
57 pdf => { name => "Adobe PDF", subr => "generate_hardcopy_pdf" }, 64 pdf => { name => "Adobe PDF", subr => "generate_hardcopy_pdf" },
58); 65);
59 66
60# custom fields used in $self hash 67# custom fields used in $self hash
68# FOR HEAVEN'S SAKE, PLEASE KEEP THIS UP-TO-DATE!
61# 69#
62# final_file_url 70# final_file_url
63# contains the URL of the final hardcopy file generated 71# contains the URL of the final hardcopy file generated
64# set by generate_hardcopy(), used by pre_header_initialize() and body() 72# set by generate_hardcopy(), used by pre_header_initialize() and body()
73#
65# temp_file_map 74# temp_file_map
66# reference to a hash mapping temporary file names to URL 75# reference to a hash mapping temporary file names to URL
67# set by pre_header_initialize(), used by body() 76# set by pre_header_initialize(), used by body()
77#
68# hardcopy_errors 78# hardcopy_errors
69# reference to array containing HTML strings describing generation errors (and warnings) 79# reference to array containing HTML strings describing generation errors (and warnings)
70# used by add_errors(), get_errors(), get_errors_ref() 80# used by add_errors(), get_errors(), get_errors_ref()
81#
82# at_least_one_problem_rendered_without_error
83# set to a true value by write_problem_tex if it is able to sucessfully render
84# a problem. checked by generate_hardcopy to determine whether to continue
85# with the generation process.
86#
87# versioned
88# set to a true value in write_set_tex if the set_id indicates that
89# the set being rendered is a versioned set; this is used in
90# write_problem_tex to determine which problem merging routine from
91# DB.pm to use, and to indicate what problem number in a versioned
92# test we're on
93#
94# mergedSets
95# a reference to a hash { userID!setID => setObject }, where setID is
96# either the set id or the fake versioned set id "setName,vN" depending
97# on whether the set is a versioned set or not. this may include the
98# sets for which the hardcopy is being generated (or may not), depending
99# on whether they were needed to determine the required permissions for
100# generating a hardcopy
101#
102# canShowScore
103# a reference to a hash { userID!setID => boolean }, where setID is either
104# the set id or the fake versioned set id "setName,vN" depending on whether
105# the set is a versioned set or not, and the value of the boolean is
106# determined by the corresponding userSet's value of hide_score and the
107# current time
71 108
72################################################################################ 109################################################################################
73# UI subroutines 110# UI subroutines
74################################################################################ 111################################################################################
75 112
94 $self->reply_with_redirect($final_file_url); 131 $self->reply_with_redirect($final_file_url);
95 $self->{final_file_url} = $final_file_url; 132 $self->{final_file_url} = $final_file_url;
96 $self->{send_hardcopy} = 1; 133 $self->{send_hardcopy} = 1;
97 return; 134 return;
98 } 135 }
99 136
100 # this should never happen, but apparently it did once (see bug #714), so we check for it 137 # this should never happen, but apparently it did once (see bug #714), so we check for it
101 die "Parameter 'user' not defined -- this should never happen" unless defined $userID; 138 die "Parameter 'user' not defined -- this should never happen" unless defined $userID;
102 139
103 if ($generate_hardcopy) { 140 if ($generate_hardcopy) {
104 my $validation_failed = 0; 141 my $validation_failed = 0;
129 } 166 }
130 167
131 # is the user allowed to request multiple sets/users at a time? 168 # is the user allowed to request multiple sets/users at a time?
132 my $perm_multiset = $authz->hasPermissions($userID, "download_hardcopy_multiset"); 169 my $perm_multiset = $authz->hasPermissions($userID, "download_hardcopy_multiset");
133 my $perm_multiuser = $authz->hasPermissions($userID, "download_hardcopy_multiuser"); 170 my $perm_multiuser = $authz->hasPermissions($userID, "download_hardcopy_multiuser");
171 my $perm_viewhidden = $authz->hasPermissions($userID, "view_hidden_work");
134 172
135 if (@setIDs > 1 and not $perm_multiset) { 173 if (@setIDs > 1 and not $perm_multiset) {
136 $self->addbadmessage("You are not permitted to generate hardcopy for multiple sets. Please select a single set and try again."); 174 $self->addbadmessage("You are not permitted to generate hardcopy for multiple sets. Please select a single set and try again.");
137 $validation_failed = 1; 175 $validation_failed = 1;
138 } 176 }
141 $validation_failed = 1; 179 $validation_failed = 1;
142 } 180 }
143 if (@userIDs and $userIDs[0] ne $eUserID and not $perm_multiuser) { 181 if (@userIDs and $userIDs[0] ne $eUserID and not $perm_multiuser) {
144 $self->addbadmessage("You are not permitted to generate hardcopy for other users."); 182 $self->addbadmessage("You are not permitted to generate hardcopy for other users.");
145 $validation_failed = 1; 183 $validation_failed = 1;
184 # FIXME -- download_hardcopy_multiuser controls both whether a user can generate hardcopy
185 # that contains sets for multiple users AND whether she can generate hardcopy that contains
186 # sets for users other than herself. should these be separate permission levels?
187 }
188
189 # to check if the set has a "hide_work" flag, we need the
190 # userset objects; if we've not failed validation yet, get
191 # those to check on this
192 my %canShowScore = ();
193 my %mergedSets = ();
194 unless ($validation_failed ) {
195 foreach my $sid ( @setIDs ) {
196 my($s,undef,$v) = ($sid =~ /([^,]+)(,v(\d+))?$/);
197 foreach my $uid ( @userIDs ) {
198 if ( $perm_viewhidden ) {
199 $canShowScore{"$uid!$sid"} = 1;
200 } else {
201 my $userSet;
202 if ( defined($v) ) {
203 $userSet = $db->getMergedSetVersion($uid,$s,$v);
204 } else {
205 $userSet = $db->getMergedSet($uid,$s);
206 }
207 $mergedSets{"$uid!$sid"} = $userSet;
208 if ( defined( $userSet->hide_work ) &&
209 ( $userSet->hide_work eq 'Y' ||
210 ( $userSet->hide_work eq 'BeforeAnswerDate' &&
211 time < $userSet->answer_date ) ) ) {
212 $validation_failed = 1;
213 $self->addbadmessage("You are not permitted to generate a hardcopy for a set with hidden work.");
214 last;
215 }
216
217 $canShowScore{"$uid!$sid"} =
218 ! ( defined( $userSet->hide_score ) &&
219 ( $userSet ->hide_score eq 'Y' ||
220 ( $userSet->hide_score eq 'BeforeAnswerDate' &&
221 time < $userSet->answer_date ) ) );
222# die("hide_score = ", $userSet->hide_score, "; canshow{$uid!$sid} = ", $canShowScore{"$uid!$sid"}, "\n");
223
224 }
225 last if $validation_failed;
226 }
227 }
146 } 228 }
147 229
148 unless ($validation_failed) { 230 unless ($validation_failed) {
231 $self->{canShowScore} = \%canShowScore;
232 $self->{mergedSets} = \%mergedSets;
149 my ($final_file_url, %temp_file_map) = $self->generate_hardcopy($hardcopy_format, \@userIDs, \@setIDs); 233 my ($final_file_url, %temp_file_map) = $self->generate_hardcopy($hardcopy_format, \@userIDs, \@setIDs);
150 if ($self->get_errors) { 234 if ($self->get_errors) {
151 # store the URLs in self hash so that body() can make a link to it 235 # store the URLs in self hash so that body() can make a link to it
152 $self->{final_file_url} = $final_file_url; 236 $self->{final_file_url} = $final_file_url;
153 $self->{temp_file_map} = \%temp_file_map; 237 $self->{temp_file_map} = \%temp_file_map;
240 324
241 # get users for selection 325 # get users for selection
242 my @Users; 326 my @Users;
243 if ($perm_multiuser) { 327 if ($perm_multiuser) {
244 # if we're allowed to select multiple users, get all the users 328 # if we're allowed to select multiple users, get all the users
329 # DBFIXME shouldn't need to pass list of users, should use iterator for results?
245 @Users = $db->getUsers($db->listUsers); 330 @Users = $db->getUsers($db->listUsers);
246 } else { 331 } else {
247 # otherwise, we get our own record only 332 # otherwise, we get our own record only
248 @Users = $db->getUser($eUserID); 333 @Users = $db->getUser($eUserID);
249 } 334 }
250 335
251 # get sets for selection 336 # get sets for selection
337 # DBFIXME should use WHERE clause to filter on open_date and published, rather then getting all
338 my @globalSetIDs;
252 my @Sets; 339 my @GlobalSets;
253 if ($perm_multiuser) { 340 if ($perm_multiuser) {
254 # if we're allowed to select sets for multiple users, get all sets 341 # if we're allowed to select sets for multiple users, get all sets.
342 @globalSetIDs = $db->listGlobalSets;
255 @Sets = $db->getGlobalSets($db->listGlobalSets); 343 @GlobalSets = $db->getGlobalSets(@globalSetIDs);
256 } else { 344 } else {
257 # otherwise, only get the sets assigned to the effective user 345 # otherwise, only get the sets assigned to the effective user.
258 @Sets = $db->getMergedSets(map [$eUserID,$_], $db->listUserSets($eUserID)); 346 # note that we are getting GlobalSets, but using the list of UserSets assigned to the
347 # effective user. this is because if we pass UserSets to ScrollingRecordList it will
348 # give us composite IDs back, which is a pain in the ass to deal with.
349 @globalSetIDs = $db->listUserSets($eUserID);
350 @GlobalSets = $db->getGlobalSets(@globalSetIDs);
351 }
352 # we also want to get the versioned sets for this user
353 # FIXME: this is another place where we assume that there is a
354 # one-to-one correspondence between assignment_type =~ gateway
355 # and versioned sets. I think we really should have a
356 # "is_versioned" flag on set objects instead.
357 my @versionedSets = grep {$_->assignment_type =~ /gateway/} @GlobalSets;
358 my @SetVersions = ();
359 foreach my $v (@versionedSets) {
360 my @usv = map { [$eUserID, $v->set_id, $_] } ( $db->listSetVersions( $eUserID, $v->set_id ) );
361 push( @SetVersions, $db->getSetVersions( @usv ) );
362 }
363 # FIXME: this is a hideous, horrible hack. the identifying key for
364 # a global set is the set_id. those for a set version are the
365 # set_id and version_id. but this means that we have trouble
366 # displaying them both together in HTML::scrollingRecordList.
367 # so we brutally play tricks with the set_id here, which probably
368 # is not very robust, and certainly is aesthetically displeasing.
369 # yuck.
370 foreach ( @SetVersions ) {
371 $_->set_id($_->set_id . ",v" . $_->version_id);
259 } 372 }
260 373
261 # filter out unwanted sets 374 # filter out unwanted sets
375 my @WantedGlobalSets;
262 foreach my $i (0 .. $#Sets) { 376 foreach my $i (0 .. $#GlobalSets) {
263 my $Set = $Sets[$i]; 377 my $Set = $GlobalSets[$i];
378 unless (defined $Set) {
379 warn "\$GlobalSets[$i] (ID $globalSetIDs[$i]) not defined -- skipping";
380 next;
381 }
264 splice @Sets, $i, 1 unless $Set->open_date <= time or $perm_unopened; 382 next unless $Set->open_date <= time or $perm_unopened;
265 splice @Sets, $i, 1 unless $Set->published or $perm_unpublished; 383 next unless $Set->published or $perm_unpublished;
384 # also skip gateway sets, for which we have to have a
385 # version to print something
386 next if $Set->assignment_type =~ /gateway/;
387 push @WantedGlobalSets, $Set;
266 } 388 }
267 389
268 my $scrolling_user_list = scrollingRecordList({ 390 my $scrolling_user_list = scrollingRecordList({
269 name => "selected_users", 391 name => "selected_users",
270 request => $r, 392 request => $r,
277 399
278 my $scrolling_set_list = scrollingRecordList({ 400 my $scrolling_set_list = scrollingRecordList({
279 name => "selected_sets", 401 name => "selected_sets",
280 request => $r, 402 request => $r,
281 default_sort => "set_id", 403 default_sort => "set_id",
282 default_format => "set_id", 404 default_format => "sid",
283 default_filters => ["all"], 405 default_filters => ["all"],
284 size => 20, 406 size => 20,
285 multiple => $perm_multiset, 407 multiple => $perm_multiset,
286 }, @Sets); 408 }, @WantedGlobalSets, @SetVersions );
287 409
288 # we change the text a little bit depending on whether the user has multiuser privileges 410 # we change the text a little bit depending on whether the user has multiuser privileges
289 my $ss = $perm_multiuser ? "s" : ""; 411 my $ss = $perm_multiuser ? "s" : "";
290 my $aa = $perm_multiuser ? " " : " a "; 412 my $aa = $perm_multiuser ? " " : " a ";
291 my $phrase_for_privileged_users = $perm_multiuser ? "to privileged users or" : ""; 413 my $phrase_for_privileged_users = $perm_multiuser ? "to privileged users or" : "";
414 my $button_label = $perm_multiuser ? "Generate hardcopy for selected sets and selected users" :"Generate hardcopy";
292 415
293 print CGI::start_p(); 416# print CGI::start_p();
294 print "Select the homework set$ss for which to generate${aa}hardcopy version$ss."; 417# print "Select the homework set$ss for which to generate${aa}hardcopy version$ss.";
295 if ($authz->hasPermissions($userID, "download_hardcopy_multiuser")) { 418# if ($authz->hasPermissions($userID, "download_hardcopy_multiuser")) {
296 print "You may also select multiple users from the users list. You will receive hardcopy for each (set, user) pair."; 419# print "You may also select multiple users from the users list. You will receive hardcopy for each (set, user) pair.";
297 } 420# }
298 print CGI::end_p(); 421# print CGI::end_p();
299 422
300 print CGI::start_form(-method=>"POST", -action=>$r->uri); 423 print CGI::start_form(-method=>"POST", -action=>$r->uri);
301 print $self->hidden_authen_fields(); 424 print $self->hidden_authen_fields();
302 print CGI::hidden("in_hc_form", 1); 425 print CGI::hidden("in_hc_form", 1);
303 426
427 if ($perm_multiuser and $perm_multiset) {
428 print CGI::p("Select the homework sets for which to generate hardcopy versions. You may"
429 ." also select multiple users from the users list. You will receive hardcopy"
430 ." for each (set, user) pair.");
431
432 print CGI::table({class=>"FormLayout"},
433 CGI::Tr({},
434 CGI::th("Users"),
435 CGI::th("Sets"),
436 ),
437 CGI::Tr({},
438 CGI::td($scrolling_user_list),
439 CGI::td($scrolling_set_list),
440 ),
441 );
442 } else { # single user mode
443 #FIXME -- do a better job of getting the set and the user when in the single set mode
444 my $selected_set_id = $r->param("selected_sets");
445
446 my $selected_user_id = $Users[0]->user_id;
447 print CGI::hidden("selected_sets", $selected_set_id ),
448 CGI::hidden( "selected_users", $selected_user_id);
449
450 # make display for versioned sets a bit nicer
451 $selected_set_id =~ s/,v(\d+)$/ (test $1)/;
452
453 print CGI::p("Download hardcopy of set ", $selected_set_id, " for ", $Users[0]->first_name, " ",$Users[0]->last_name,"?");
454
455 }
304 print CGI::table({class=>"FormLayout"}, 456 print CGI::table({class=>"FormLayout"},
305 CGI::Tr( 457 CGI::Tr({},
306 CGI::th("Users"),
307 CGI::th("Sets"),
308 ),
309 CGI::Tr(
310 CGI::td($scrolling_user_list),
311 CGI::td($scrolling_set_list),
312 ),
313 CGI::Tr(
314 CGI::td({colspan=>2, class=>"ButtonRow"}, 458 CGI::td({colspan=>2, class=>"ButtonRow"},
315 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."), 459 CGI::small("You may choose to show any of the following data. Correct answers and solutions are only
460 available $phrase_for_privileged_users after the answer date of the homework set."),
316 CGI::br(), 461 CGI::br(),
317 CGI::b("Show:"), " ", 462 CGI::b("Show:"), " ",
318 CGI::checkbox( 463 CGI::checkbox(
319 -name => "showCorrectAnswers", 464 -name => "showCorrectAnswers",
320 -checked => scalar($r->param("showCorrectAnswers")) || 0, 465 -checked => scalar($r->param("showCorrectAnswers")) || 0,
330 -checked => scalar($r->param("showSolutions")) || 0, 475 -checked => scalar($r->param("showSolutions")) || 0,
331 -label => "Solutions", 476 -label => "Solutions",
332 ), 477 ),
333 ), 478 ),
334 ), 479 ),
335 CGI::Tr( 480 CGI::Tr({},
336 CGI::td({colspan=>2, class=>"ButtonRow"}, 481 CGI::td({colspan=>2, class=>"ButtonRow"},
337 CGI::b("Hardcopy Format:"), " ", 482 CGI::b("Hardcopy Format:"), " ",
338 CGI::radio_group( 483 CGI::radio_group(
339 -name => "hardcopy_format", 484 -name => "hardcopy_format",
340 -values => \@formats, 485 -values => \@formats,
341 -default => scalar($r->param("hardcopy_format")) || $HC_DEFAULT_FORMAT, 486 -default => scalar($r->param("hardcopy_format")) || $HC_DEFAULT_FORMAT,
342 -labels => \%format_labels, 487 -labels => \%format_labels,
343 ), 488 ),
344 ), 489 ),
345 ), 490 ),
346 CGI::Tr( 491 CGI::Tr({},
347 CGI::td({colspan=>2, class=>"ButtonRow"}, 492 CGI::td({colspan=>2, class=>"ButtonRow"},
348 CGI::submit( 493 CGI::submit(
349 -name => "generate_hardcopy", 494 -name => "generate_hardcopy",
350 -value => "Generate hardcopy for selected sets and selected users", 495 -value => $button_label,
351 #-style => "width: 45ex", 496 #-style => "width: 45ex",
352 ), 497 ),
353 ), 498 ),
354 ), 499 ),
355 ); 500 );
373 my $courseID = $r->urlpath->arg("courseID"); 518 my $courseID = $r->urlpath->arg("courseID");
374 my $userID = $r->param("user"); 519 my $userID = $r->param("user");
375 my $eUserID = $r->param("effectiveUser"); 520 my $eUserID = $r->param("effectiveUser");
376 521
377 # we want to make the temp directory web-accessible, for error reporting 522 # we want to make the temp directory web-accessible, for error reporting
378 #my $temp_dir_path = eval { makeTempDirectory($ce->{webworkDirs}{tmp}, "webwork-hardcopy") }; 523 # use mkpath to ensure it exists (mkpath is pretty much ``mkdir -p'')
379 my $temp_dir_parent_path = $ce->{courseDirs}{html_temp} . "/hardcopy"; # makeTempDirectory will ensure that .../hardcopy exists 524 my $temp_dir_parent_path = $ce->{courseDirs}{html_temp} . "/hardcopy";
380 my $temp_dir_path = eval { makeTempDirectory($temp_dir_parent_path, "work") }; 525 eval { mkpath($temp_dir_parent_path) };
381 if ($@) { 526 if ($@) {
382 $self->add_errors($@); 527 die "Couldn't create hardcopy directory $temp_dir_parent_path: $@";
528 }
529
530 # create a randomly-named working directory in the hardcopy directory
531 my $temp_dir_path = eval { tempdir("work.XXXXXXXX", DIR => $temp_dir_parent_path) };
532 if ($@) {
533 $self->add_errors("Couldn't create temporary working directory: ".CGI::code(CGI::escapeHTML($@)));
534 return;
535 }
536
537 # do some error checking
538 unless (-e $temp_dir_path) {
539 $self->add_errors("Temporary directory '".CGI::code(CGI::escapeHTML($temp_dir_path))
540 ."' does not exist, but creation didn't fail. This shouldn't happen.");
541 return;
542 }
543 unless (-w $temp_dir_path) {
544 $self->add_errors("Temporary directory '".CGI::code(CGI::escapeHTML($temp_dir_path))
545 ."' is not writeable.");
546 $self->delete_temp_dir($temp_dir_path);
383 return; 547 return;
384 } 548 }
385 549
386 my $tex_file_name = "hardcopy.tex"; 550 my $tex_file_name = "hardcopy.tex";
387 my $tex_file_path = "$temp_dir_path/$tex_file_name"; 551 my $tex_file_path = "$temp_dir_path/$tex_file_name";
388
389 # do some error checking
390 unless (-w $temp_dir_path) {
391 $self->add_errors("Temporary directory '$temp_dir_path' is not writeable: $!");
392 return;
393 }
394 552
395 # write TeX 553 # write TeX
396 my $open_result = open my $FH, ">", $tex_file_path; 554 my $open_result = open my $FH, ">", $tex_file_path;
397 unless ($open_result) { 555 unless ($open_result) {
398 $self->add_errors("Failed to open file '$tex_file_path' for writing: $!"); 556 $self->add_errors("Failed to open file '".CGI::code(CGI::escapeHTML($tex_file_path))
557 ."' for writing: ".CGI::code(CGI::escapeHTML($!)));
558 $self->delete_temp_dir($temp_dir_path);
399 return; 559 return;
400 } 560 }
401 $self->write_multiuser_tex($FH, $userIDsRef, $setIDsRef); 561 $self->write_multiuser_tex($FH, $userIDsRef, $setIDsRef);
402 close $FH; 562 close $FH;
563
564 # if no problems got rendered successfully, we can't continue
565 unless ($self->{at_least_one_problem_rendered_without_error}) {
566 $self->add_errors("No problems rendered. Can't continue.");
567 $self->delete_temp_dir($temp_dir_path);
568 return;
569 }
570
571 # if no hardcopy.tex file was generated, fail now
572 unless (-e "$temp_dir_path/hardcopy.tex") {
573 $self->add_errors("'".CGI::code("hardcopy.tex")."' not written to temporary directory '"
574 .CGI::code(CGI::escapeHTML($temp_dir_path))."'. Can't continue.");
575 $self->delete_temp_dir($temp_dir_path);
576 return;
577 }
403 578
404 # determine base name of final file 579 # determine base name of final file
405 my $final_file_user = @$userIDsRef > 1 ? "multiuser" : $userIDsRef->[0]; 580 my $final_file_user = @$userIDsRef > 1 ? "multiuser" : $userIDsRef->[0];
406 my $final_file_set = @$setIDsRef > 1 ? "multiset" : $setIDsRef->[0]; 581 my $final_file_set = @$setIDsRef > 1 ? "multiset" : $setIDsRef->[0];
407 my $final_file_basename = "$courseID.$final_file_user.$final_file_set"; 582 my $final_file_basename = "$courseID.$final_file_user.$final_file_set";
428 603
429 my $final_file_url; 604 my $final_file_url;
430 605
431 # make sure final file exists 606 # make sure final file exists
432 unless (-e $final_file_path) { 607 unless (-e $final_file_path) {
433 $self->add_errors("Final hardcopy file '$final_file_path' not found after calling '$format_subr': $!"); 608 $self->add_errors("Final hardcopy file '".CGI::code(CGI::escapeHTML($final_file_path))
609 ."' not found after calling '".CGI::code(CGI::escapeHTML($format_subr))."': "
610 .CGI::code(CGI::escapeHTML($!)));
434 return $final_file_url, %temp_file_map; 611 return $final_file_url, %temp_file_map;
435 } 612 }
436 613
437 # try to move the hardcopy file out of the temp directory 614 # try to move the hardcopy file out of the temp directory
438 # set $final_file_url accordingly 615 # set $final_file_url accordingly
439 my $final_file_final_path = "$temp_dir_parent_path/$final_file_name"; 616 my $final_file_final_path = "$temp_dir_parent_path/$final_file_name";
440 my $mv_cmd = "/bin/mv " . shell_quote($final_file_path, $final_file_final_path); 617 my $mv_cmd = "2>&1 " . $ce->{externalPrograms}{mv} . " " . shell_quote($final_file_path, $final_file_final_path);
441 if (system $mv_cmd) { 618 my $mv_out = readpipe $mv_cmd;
442 $self->add_errors("Failed to move hardcopy file '$final_file_name' from '$temp_dir_path' to '$temp_dir_parent_path': $!"); 619 if ($?) {
620 $self->add_errors("Failed to move hardcopy file '".CGI::code(CGI::escapeHTML($final_file_name))
621 ."' from '".CGI::code(CGI::escapeHTML($temp_dir_path))."' to '"
622 .CGI::code(CGI::escapeHTML($temp_dir_parent_path))."':".CGI::br()
623 .CGI::pre(CGI::escapeHTML($mv_out)));
443 $final_file_url = "$temp_dir_url/$final_file_name"; 624 $final_file_url = "$temp_dir_url/$final_file_name";
444 } else { 625 } else {
445 $final_file_url = "$temp_dir_parent_url/$final_file_name"; 626 $final_file_url = "$temp_dir_parent_url/$final_file_name";
446 } 627 }
447 628
448 # remove the temp directory if there are no errors 629 # remove the temp directory if there are no errors
449 unless ($self->get_errors or $PreserveTempFiles) { 630 unless ($self->get_errors or $PreserveTempFiles) {
450 my $rm_cmd = "/bin/rm -rf " . shell_quote($temp_dir_path); 631 $self->delete_temp_dir($temp_dir_path);
451 if (system $rm_cmd) {
452 $self->add_errors("Failed to remove temp directory '$temp_dir_path': $!");
453 }
454 } 632 }
455 633
456 warn "Preserved temporary files in directory '$temp_dir_path'.\n" if $PreserveTempFiles; 634 warn "Preserved temporary files in directory '$temp_dir_path'.\n" if $PreserveTempFiles;
457 635
458 return $final_file_url, %temp_file_map; 636 return $final_file_url, %temp_file_map;
637}
638
639# helper function to remove temp dirs
640sub delete_temp_dir {
641 my ($self, $temp_dir_path) = @_;
642
643 my $rm_cmd = "2>&1 " . $self->r->ce->{externalPrograms}{rm} . " -rf " . shell_quote($temp_dir_path);
644 my $rm_out = readpipe $rm_cmd;
645 if ($?) {
646 $self->add_errors("Failed to remove temporary directory '".CGI::code(CGI::escapeHTML($temp_dir_path))."':"
647 .CGI::br().CGI::pre($rm_out));
648 return 0;
649 } else {
650 return 1;
651 }
459} 652}
460 653
461# format subroutines 654# format subroutines
462# 655#
463# assume that TeX source is located at $temp_dir_path/hardcopy.tex 656# assume that TeX source is located at $temp_dir_path/hardcopy.tex
473 my $final_file_name; 666 my $final_file_name;
474 667
475 # try to rename tex file 668 # try to rename tex file
476 my $src_name = "hardcopy.tex"; 669 my $src_name = "hardcopy.tex";
477 my $dest_name = "$final_file_basename.tex"; 670 my $dest_name = "$final_file_basename.tex";
478 my $mv_cmd = "/bin/mv " . shell_quote("$temp_dir_path/$src_name", "$temp_dir_path/$dest_name"); 671 my $mv_cmd = "2>&1 " . $self->r->ce->{externalPrograms}{mv} . " " . shell_quote("$temp_dir_path/$src_name", "$temp_dir_path/$dest_name");
479 if (system $mv_cmd) { 672 my $mv_out = readpipe $mv_cmd;
480 $self->add_errors("Failed to rename '$src_name' to '$dest_name' in directory '$temp_dir_path': $!"); 673 if ($?) {
674 $self->add_errors("Failed to rename '".CGI::code(CGI::escapeHTML($src_name))."' to '"
675 .CGI::code(CGI::escapeHTML($dest_name))."' in directory '"
676 .CGI::code(CGI::escapeHTML($temp_dir_path))."':".CGI::br()
677 .CGI::pre(CGI::escapeHTML($mv_out)));
481 $final_file_name = $src_name; 678 $final_file_name = $src_name;
482 } else { 679 } else {
483 $final_file_name = $dest_name; 680 $final_file_name = $dest_name;
484 } 681 }
485 682
492 # call pdflatex - we don't want to chdir in the mod_perl process, as 689 # call pdflatex - we don't want to chdir in the mod_perl process, as
493 # that might step on the feet of other things (esp. in Apache 2.0) 690 # that might step on the feet of other things (esp. in Apache 2.0)
494 my $pdflatex_cmd = "cd " . shell_quote($temp_dir_path) . " && " 691 my $pdflatex_cmd = "cd " . shell_quote($temp_dir_path) . " && "
495 . $self->r->ce->{externalPrograms}{pdflatex} 692 . $self->r->ce->{externalPrograms}{pdflatex}
496 . " >pdflatex.stdout 2>pdflatex.stderr hardcopy"; 693 . " >pdflatex.stdout 2>pdflatex.stderr hardcopy";
497 if (system $pdflatex_cmd) { 694 if (my $rawexit = system $pdflatex_cmd) {
695 my $exit = $rawexit >> 8;
696 my $signal = $rawexit & 127;
697 my $core = $rawexit & 128;
498 $self->add_errors("Failed to convert TeX to PDF with command '$pdflatex_cmd'."); 698 $self->add_errors("Failed to convert TeX to PDF with command '"
699 .CGI::code(CGI::escapeHTML($pdflatex_cmd))."' (exit=$exit signal=$signal core=$core).");
700
701 # read hardcopy.log and report first error
702 my $hardcopy_log = "$temp_dir_path/hardcopy.log";
703 if (-e $hardcopy_log) {
704 if (open my $LOG, "<", $hardcopy_log) {
705 my $line;
706 while ($line = <$LOG>) {
707 last if $line =~ /^!\s+/;
708 }
709 my $first_error = $line;
710 while ($line = <$LOG>) {
711 last if $line =~ /^!\s+/;
712 $first_error .= $line;
713 }
714 close $LOG;
715 if (defined $first_error) {
716 $self->add_errors("First error in TeX log is:".CGI::br().
717 CGI::pre(CGI::escapeHTML($first_error)));
718 } else {
719 $self->add_errors("No errors encoundered in TeX log.");
720 }
721 } else {
722 $self->add_errors("Could not read TeX log: ".CGI::code(CGI::escapeHTML($!)));
723 }
724 } else {
725 $self->add_errors("No TeX log was found.");
726 }
499 } 727 }
500 728
501 my $final_file_name; 729 my $final_file_name;
502 730
503 # try rename the pdf file 731 # try rename the pdf file
504 my $src_name = "hardcopy.pdf"; 732 my $src_name = "hardcopy.pdf";
505 my $dest_name = "$final_file_basename.pdf"; 733 my $dest_name = "$final_file_basename.pdf";
506 my $mv_cmd = "/bin/mv " . shell_quote("$temp_dir_path/$src_name", "$temp_dir_path/$dest_name"); 734 my $mv_cmd = "2>&1 " . $self->r->ce->{externalPrograms}{mv} . " " . shell_quote("$temp_dir_path/$src_name", "$temp_dir_path/$dest_name");
507 if (system $mv_cmd) { 735 my $mv_out = readpipe $mv_cmd;
508 $self->add_errors("Failed to rename '$src_name' to '$dest_name' in directory '$temp_dir_path': $!"); 736 if ($?) {
737 $self->add_errors("Failed to rename '".CGI::code(CGI::escapeHTML($src_name))."' to '"
738 .CGI::code(CGI::escapeHTML($dest_name))."' in directory '"
739 .CGI::code(CGI::escapeHTML($temp_dir_path))."':".CGI::br()
740 .CGI::pre(CGI::escapeHTML($mv_out)));
509 $final_file_name = $src_name; 741 $final_file_name = $src_name;
510 } else { 742 } else {
511 $final_file_name = $dest_name; 743 $final_file_name = $dest_name;
512 } 744 }
513 745
551 my $db = $r->db; 783 my $db = $r->db;
552 784
553 # get user record 785 # get user record
554 my $TargetUser = $db->getUser($targetUserID); # checked 786 my $TargetUser = $db->getUser($targetUserID); # checked
555 unless ($TargetUser) { 787 unless ($TargetUser) {
556 $self->add_errors("Can't generate hardcopy for user $targetUserID -- no such user exists.\n"); 788 $self->add_errors("Can't generate hardcopy for user '".CGI::code(CGI::escapeHTML($targetUserID))."' -- no such user exists.\n");
557 return; 789 return;
558 } 790 }
559 791
560 # get set divider 792 # get set divider
561 my $divider = $ce->{webworkFiles}->{hardcopySnippets}->{setDivider}; 793 my $divider = $ce->{webworkFiles}->{hardcopySnippets}->{setDivider};
572 my $r = $self->r; 804 my $r = $self->r;
573 my $ce = $r->ce; 805 my $ce = $r->ce;
574 my $db = $r->db; 806 my $db = $r->db;
575 my $authz = $r->authz; 807 my $authz = $r->authz;
576 my $userID = $r->param("user"); 808 my $userID = $r->param("user");
577 809
578 # get set record 810 # we may already have the MergedSet from checking hide_work and
811 # hide_score in pre_header_initialize; check to see if that's true,
812 # and otherwise, get the set.
813 my %mergedSets = %{$self->{mergedSets}};
814 my $uid = $TargetUser->user_id;
815 my $MergedSet;
816 my $versioned = 0;
817 if ( defined( $mergedSets{"$uid!$setID"} ) ) {
818 $MergedSet = $mergedSets{"$uid!$setID"};
819 $versioned = ($setID =~ /,v(\d+)$/) ? $1 : 0;
820 } else {
821 if ( $setID =~ /(.+),v(\d+)$/ ) {
822 $setID = $1;
823 $versioned = $2;
824 }
825 if ( $versioned ) {
826 $MergedSet = $db->getMergedSetVersion($TargetUser->user_id, $setID, $versioned);
827 } else {
579 my $MergedSet = $db->getMergedSet($TargetUser->user_id, $setID); # checked 828 $MergedSet = $db->getMergedSet($TargetUser->user_id, $setID); # checked
829 }
830 }
831 # save versioned info for use in write_problem_tex
832 $self->{versioned} = $versioned;
833
580 unless ($MergedSet) { 834 unless ($MergedSet) {
581 $self->add_errors("Can't generate hardcopy for set $setID for user ".$TargetUser->user_id." -- set is not assigned to that user."); 835 $self->add_errors("Can't generate hardcopy for set ''".CGI::code(CGI::escapeHTML($setID))
836 ."' for user '".CGI::code(CGI::escapeHTML($TargetUser->user_id))
837 ."' -- set is not assigned to that user.");
582 return; 838 return;
583 } 839 }
584 840
585 # see if the *real* user is allowed to access this problem set 841 # see if the *real* user is allowed to access this problem set
586 if ($MergedSet->open_date > time and not $authz->hasPermissions($userID, "view_unopened_sets")) { 842 if ($MergedSet->open_date > time and not $authz->hasPermissions($userID, "view_unopened_sets")) {
587 $self->add_errors("Can't generate hardcopy for set $setID for user ".$TargetUser->user_id." -- set is not yet open."); 843 $self->add_errors("Can't generate hardcopy for set '".CGI::code(CGI::escapeHTML($setID))
844 ."' for user '".CGI::code(CGI::escapeHTML($TargetUser->user_id))
845 ."' -- set is not yet open.");
588 return; 846 return;
589 } 847 }
590 if (not $MergedSet->published and not $authz->hasPermissions($userID, "view_unpublished_sets")) { 848 if (not $MergedSet->published and not $authz->hasPermissions($userID, "view_unpublished_sets")) {
591 $self->addbadmessage("Can't generate hardcopy for set $setID for user ".$TargetUser->user_id." -- set has not been published."); 849 $self->addbadmessage("Can't generate hardcopy for set '".CGI::code(CGI::escapeHTML($setID))
850 ."' for user '".CGI::code(CGI::escapeHTML($TargetUser->user_id))
851 ."' -- set has not been published.");
592 return; 852 return;
593 } 853 }
594 854
595 # get snippets 855 # get snippets
596 my $header = $MergedSet->hardcopy_header 856 my $header = $MergedSet->hardcopy_header
598 : $ce->{webworkFiles}->{hardcopySnippets}->{setHeader}; 858 : $ce->{webworkFiles}->{hardcopySnippets}->{setHeader};
599 my $footer = $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; 859 my $footer = $ce->{webworkFiles}->{hardcopySnippets}->{setFooter};
600 my $divider = $ce->{webworkFiles}->{hardcopySnippets}->{problemDivider}; 860 my $divider = $ce->{webworkFiles}->{hardcopySnippets}->{problemDivider};
601 861
602 # get list of problem IDs 862 # get list of problem IDs
863 # DBFIXME use ORDER BY in database
603 my @problemIDs = sort { $a <=> $b } $db->listUserProblems($MergedSet->user_id, $MergedSet->set_id); 864 my @problemIDs = sort { $a <=> $b } $db->listUserProblems($MergedSet->user_id, $MergedSet->set_id);
865
866 # for versioned sets (gateways), we might have problems in a random
867 # order; reset the order of the problemIDs if this is the case
868 if ( defined( $MergedSet->problem_randorder ) &&
869 $MergedSet->problem_randorder ) {
870 my @newOrder = ();
871
872 # to set the same order each time we set the random seed to the psvn,
873 # and to avoid messing with the system random number generator we use
874 # our own PGrandom object
875 my $pgrand = PGrandom->new();
876 $pgrand->srand( $MergedSet->psvn );
877 while ( @problemIDs ) {
878 my $i = int($pgrand->rand(scalar(@problemIDs)));
879 push( @newOrder, $problemIDs[$i] );
880 splice(@problemIDs, $i, 1);
881 }
882 @problemIDs = @newOrder;
883 }
884
604 885
605 # write set header 886 # write set header
606 $self->write_problem_tex($FH, $TargetUser, $MergedSet, 0, $header); # 0 => pg file specified directly 887 $self->write_problem_tex($FH, $TargetUser, $MergedSet, 0, $header); # 0 => pg file specified directly
607 888
608 # write each problem 889 # write each problem
890 # for versioned problem sets (gateway tests) we like to include
891 # problem numbers
892 my $i = 1;
609 while (my $problemID = shift @problemIDs) { 893 while (my $problemID = shift @problemIDs) {
610 $self->write_tex_file($FH, $divider); 894 $self->write_tex_file($FH, $divider);
895 $self->{versioned} = $i if $versioned;
611 $self->write_problem_tex($FH, $TargetUser, $MergedSet, $problemID); 896 $self->write_problem_tex($FH, $TargetUser, $MergedSet, $problemID);
897 $i++;
612 } 898 }
613 899
614 # write footer 900 # write footer
615 $self->write_problem_tex($FH, $TargetUser, $MergedSet, 0, $footer); # 0 => pg file specified directly 901 $self->write_problem_tex($FH, $TargetUser, $MergedSet, 0, $footer); # 0 => pg file specified directly
616} 902}
620 my $r = $self->r; 906 my $r = $self->r;
621 my $ce = $r->ce; 907 my $ce = $r->ce;
622 my $db = $r->db; 908 my $db = $r->db;
623 my $authz = $r->authz; 909 my $authz = $r->authz;
624 my $userID = $r->param("user"); 910 my $userID = $r->param("user");
625 911 my $versioned = $self->{versioned};
912 my %canShowScore = %{$self->{canShowScore}};
913
626 my @errors; 914 my @errors;
627 915
628 # get problem record 916 # get problem record
629 my $MergedProblem; 917 my $MergedProblem;
630 if ($problemID) { 918 if ($problemID) {
631 # a non-zero problem ID was given -- load that problem 919 # a non-zero problem ID was given -- load that problem
920 # we use $versioned to determine which merging routine to use
921 if ( $versioned ) {
922 $MergedProblem = $db->getMergedProblemVersion($MergedSet->user_id, $MergedSet->set_id, $MergedSet->version_id, $problemID);
923
924 } else {
632 $MergedProblem = $db->getMergedProblem($MergedSet->user_id, $MergedSet->set_id, $problemID); # checked 925 $MergedProblem = $db->getMergedProblem($MergedSet->user_id, $MergedSet->set_id, $problemID); # checked
926 }
633 927
634 # handle nonexistent problem 928 # handle nonexistent problem
635 unless ($MergedProblem) { 929 unless ($MergedProblem) {
636 $self->add_errors("Can't generate hardcopy for problem $problemID in set ".$MergedSet->set_id." for user ".$MergedSet->user_id." -- problem does not exist in that set or is not assigned to that user."); 930 $self->add_errors("Can't generate hardcopy for problem '"
931 .CGI::code(CGI::escapeHTML($problemID))."' in set '"
932 .CGI::code(CGI::escapeHTML($MergedSet->set_id))
933 ."' for user '".CGI::code(CGI::escapeHTML($MergedSet->user_id))
934 ."' -- problem does not exist in that set or is not assigned to that user.");
637 return; 935 return;
638 } 936 }
639 } elsif ($pgFile) { 937 } elsif ($pgFile) {
640 # otherwise, we try an explicit PG file 938 # otherwise, we try an explicit PG file
641 $MergedProblem = $db->newUserProblem( 939 $MergedProblem = $db->newUserProblem(
650 die "write_problem_tex needs either a non-zero \$problemID or a \$pgFile"; 948 die "write_problem_tex needs either a non-zero \$problemID or a \$pgFile";
651 } 949 }
652 950
653 # figure out if we're allowed to get correct answers, hints, and solutions 951 # figure out if we're allowed to get correct answers, hints, and solutions
654 # (eventually, we'd like to be able to use the same code as Problem) 952 # (eventually, we'd like to be able to use the same code as Problem)
953 my $versionName = $MergedSet->set_id .
954 (( $versioned ) ? ",v" . $MergedSet->version_id : '');
655 my $showCorrectAnswers = $r->param("showCorrectAnswers") || 0; 955 my $showCorrectAnswers = $r->param("showCorrectAnswers") || 0;
656 my $showHints = $r->param("showHints") || 0; 956 my $showHints = $r->param("showHints") || 0;
657 my $showSolutions = $r->param("showSolutions") || 0; 957 my $showSolutions = $r->param("showSolutions") || 0;
658 unless ($authz->hasPermissions($userID, "view_answers") or time > $MergedSet->answer_date) { 958 unless( ( $authz->hasPermissions($userID, "show_correct_answers_before_answer_date") or
959 ( time > $MergedSet->answer_date or
960 ( $versioned &&
961 $MergedProblem->num_correct +
962 $MergedProblem->num_incorrect >=
963 $MergedSet->attempts_per_version &&
964 $MergedSet->due_date == $MergedSet->answer_date ) ) ) &&
965 ( $canShowScore{$MergedSet->user_id . "!$versionName"} ) ) {
659 $showCorrectAnswers = 0; 966 $showCorrectAnswers = 0;
660 $showSolutions = 0; 967 $showSolutions = 0;
661 } 968 }
662 969
663 # FIXME -- there can be a problem if the $siteDefaults{timezone} is not defined? Why is this? 970 # FIXME -- there can be a problem if the $siteDefaults{timezone} is not defined? Why is this?
664 # why does it only occur with hardcopy? 971 # why does it only occur with hardcopy?
972
973 # we need an additional translation option for versioned sets; also,
974 # for versioned sets include old answers in the set if we're also
975 # asking for the answers
976 my $transOpts =
977 { # translation options
978 displayMode => "tex",
979 showHints => $showHints ? 1 : 0, # insure that this value is numeric
980 showSolutions => $showSolutions ? 1 : 0, # (or what? -sam)
981 processAnswers => $showCorrectAnswers ? 1 : 0,
982 };
983 my $formFields = { };
984 if ( $versioned && $MergedProblem->problem_id != 0 ) {
985 $transOpts->{QUIZ_PREFIX} = 'Q' . sprintf("%04d",$MergedProblem->problem_id()) . '_';
986 if ( $showCorrectAnswers ) {
987 my %oldAnswers = decodeAnswers($MergedProblem->last_answer);
988 $formFields->{$_} = $oldAnswers{$_} foreach (keys %oldAnswers);
989 print $FH "%% decoded old answers, saved. (keys = " . join(',', keys(%oldAnswers)) . "\n";
990 }
991 }
992
993# warn("problem ", $MergedProblem->problem_id, ": source = ", $MergedProblem->source_file, "\n");
994
665 my $pg = WeBWorK::PG->new( 995 my $pg = WeBWorK::PG->new(
666 $ce, 996 $ce,
667 $TargetUser, 997 $TargetUser,
668 scalar($r->param('key')), # avoid multiple-values problem 998 scalar($r->param('key')), # avoid multiple-values problem
669 $MergedSet, 999 $MergedSet,
670 $MergedProblem, 1000 $MergedProblem,
671 $MergedSet->psvn, 1001 $MergedSet->psvn,
672 {}, # no form fields! 1002 $formFields, # no form fields!
673 { # translation options 1003 $transOpts,
674 displayMode => "tex",
675 showHints => $showHints ? 1 : 0, # insure that this value is numeric
676 showSolutions => $showSolutions ? 1 : 0, # (or what? -sam)
677 processAnswers => $showCorrectAnswers ? 1 : 0,
678 },
679 ); 1004 );
680 1005
681 # only bother to generate this info if there were warnings or errors 1006 # only bother to generate this info if there were warnings or errors
682 my $edit_url; 1007 my $edit_url;
683 my $problem_name; 1008 my $problem_name;
698 problemSeed => $MergedProblem->problem_seed, 1023 problemSeed => $MergedProblem->problem_seed,
699 }, 1024 },
700 ); 1025 );
701 } else { 1026 } else {
702 # link for a real problem 1027 # link for a real problem
703 $edit_url = CGI::a({href=>$self->systemLink($edit_urlpath)}, "Edit it"); 1028 $edit_url = $self->systemLink($edit_urlpath);
704 } 1029 }
705 1030
706 if ($MergedProblem->problem_id == 0) { 1031 if ($MergedProblem->problem_id == 0) {
707 $problem_name = "snippet"; 1032 $problem_name = "snippet";
708 $problem_desc = $problem_name." ".$MergedProblem->source_file 1033 $problem_desc = $problem_name." '".$MergedProblem->source_file
709 ." for set ".$MergedProblem->set_id." and user " 1034 ."' for set '".$MergedProblem->set_id."' and user '"
710 .$MergedProblem->user_id; 1035 .$MergedProblem->user_id."'";
711 } else { 1036 } else {
712 $problem_name = "problem"; 1037 $problem_name = "problem";
713 $problem_desc = $problem_name." ".$MergedProblem->problem_id 1038 $problem_desc = $problem_name." '".$MergedProblem->problem_id
714 ." in set ".$MergedProblem->set_id." for user " 1039 ."' in set '".$MergedProblem->set_id."' for user '"
715 .$MergedProblem->user_id; 1040 .$MergedProblem->user_id."'";
716 } 1041 }
717 } 1042 }
718 1043
719 # deal with PG warnings 1044 # deal with PG warnings
720 if ($pg->{warnings} ne "") { 1045 if ($pg->{warnings} ne "") {
721 $self->add_errors(CGI::a({href=>$edit_url}, "[edit]") 1046 $self->add_errors(CGI::a({href=>$edit_url, target=>"WW_Editor"}, "[edit]")
722 ."Warnings encountered while processing $problem_desc. " 1047 ." Warnings encountered while processing $problem_desc. "
723 ."Error text:".CGI::br().CGI::pre($pg->{warnings}) 1048 ."Error text:".CGI::br().CGI::pre(CGI::escapeHTML($pg->{warnings}))
724 ); 1049 );
725 } 1050 }
726 1051
727 # deal with PG errors 1052 # deal with PG errors
728 if ($pg->{flags}->{error_flag}) { 1053 if ($pg->{flags}->{error_flag}) {
729 $self->add_errors(CGI::a({href=>$edit_url}, "[edit]") 1054 $self->add_errors(CGI::a({href=>$edit_url, target=>"WW_Editor"}, "[edit]")
730 ."Errors encountered while processing $problem_desc. " 1055 ." Errors encountered while processing $problem_desc. "
731 ."This $problem_name has been omitted from the hardcopy. " 1056 ."This $problem_name has been omitted from the hardcopy. "
732 ."Error text:".CGI::br().CGI::pre($pg->{errors}) 1057 ."Error text:".CGI::br().CGI::pre(CGI::escapeHTML($pg->{errors}))
733 ); 1058 );
734 return; 1059 return;
735 } 1060 }
736 1061
1062 # if we got here, there were no errors (because errors cause a return above)
1063 $self->{at_least_one_problem_rendered_without_error} = 1;
1064
1065 print $FH "{\\bf Problem $versioned.}\n"
1066 if ( $versioned && $MergedProblem->problem_id != 0 );
737 print $FH $pg->{body_text}; 1067 print $FH $pg->{body_text};
1068
1069 my @ans_entry_order = defined($pg->{flags}->{ANSWER_ENTRY_ORDER}) ? @{$pg->{flags}->{ANSWER_ENTRY_ORDER}} : ( );
1070
1071 # write the list of student answers if we're working with a versioned
1072 # set and showing the correct answers
1073 if ( $versioned && $showCorrectAnswers &&
1074 $MergedProblem->problem_id != 0 && @ans_entry_order ) {
1075 my $stuAnswers = "\\par{\\small{\\it Answer submitted:}\n" .
1076 "\\vspace{-\\parskip}\\begin{itemize}\n";
1077 for my $ansName ( @ans_entry_order ) {
1078 my $stuAns = $pg->{answers}->{$ansName}->{original_student_ans};
1079 my $recScore = $pg->{state}->{recorded_score};
1080 my $corrMsg = '';
1081 if ( $recScore == 1 ) {
1082 $corrMsg = ' (correct)';
1083 } elsif ( $recScore == 0 ) {
1084 $corrMsg = ' (incorrect)';
1085 } else {
1086 $corrMsg = " (score $recScore)";
1087 }
1088 $stuAnswers .= "\\item\\begin{verbatim}$stuAns$corrMsg\\end{verbatim}\n";
1089 }
1090
1091 $stuAnswers .= "\\end{itemize}}\\par\n";
1092 print $FH $stuAnswers;
1093 }
738 1094
739 # write the list of correct answers is appropriate 1095 # write the list of correct answers is appropriate; ANSWER_ENTRY_ORDER
1096 # isn't defined for versioned sets? this seems odd FIXME GWCHANGE
740 if ($showCorrectAnswers && $MergedProblem->problem_id != 0) { 1097 if ($showCorrectAnswers && $MergedProblem->problem_id != 0 && @ans_entry_order) {
741 my $correctTeX = "\\par{\\small{\\it Correct Answers:}\n" 1098 my $correctTeX = "\\par{\\small{\\it Correct Answers:}\n"
742 . "\\vspace{-\\parskip}\\begin{itemize}\n"; 1099 . "\\vspace{-\\parskip}\\begin{itemize}\n";
743 1100
744 foreach my $ansName (@{$pg->{flags}->{ANSWER_ENTRY_ORDER}}) { 1101 foreach my $ansName (@ans_entry_order) {
745 my $correctAnswer = $pg->{answers}->{$ansName}->{correct_ans}; 1102 my $correctAnswer = $pg->{answers}->{$ansName}->{correct_ans};
746 $correctTeX .= "\\item\\begin{verbatim}$correctAnswer\\end{verbatim}\n"; 1103 $correctTeX .= "\\item\\begin{verbatim}$correctAnswer\\end{verbatim}\n";
747 # FIXME: What about vectors (where TeX will complain about < and > outside of math mode)? 1104 # FIXME: What about vectors (where TeX will complain about < and > outside of math mode)?
748 } 1105 }
749 1106
756sub write_tex_file { 1113sub write_tex_file {
757 my ($self, $FH, $file) = @_; 1114 my ($self, $FH, $file) = @_;
758 1115
759 my $tex = eval { readFile($file) }; 1116 my $tex = eval { readFile($file) };
760 if ($@) { 1117 if ($@) {
761 $self->add_errors("Failed to include TeX file $file: $@"); 1118 $self->add_errors("Failed to include TeX file '".CGI::code(CGI::escapeHTML($file))."': "
1119 .CGI::escapeHTML($@));
762 } else { 1120 } else {
763 print $FH $tex; 1121 print $FH $tex;
764 } 1122 }
765} 1123}
766 1124
768# utilities 1126# utilities
769################################################################################ 1127################################################################################
770 1128
771sub add_errors { 1129sub add_errors {
772 my ($self, @errors) = @_; 1130 my ($self, @errors) = @_;
773 #warn "add_errors(".join(", ", map("'$_'", @errors)).")";
774 push @{$self->{hardcopy_errors}}, @errors; 1131 push @{$self->{hardcopy_errors}}, @errors;
775} 1132}
776 1133
777sub get_errors { 1134sub get_errors {
778 my ($self) = @_; 1135 my ($self) = @_;

Legend:
Removed from v.3659  
changed lines
  Added in v.4883

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9