[system] / trunk / webwork2 / lib / WeBWorK / ContentGenerator / Instructor / ProblemSetDetail.pm Repository:
ViewVC logotype

Annotation of /trunk/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2966 - (view) (download) (as text)

1 : toenail 2794 ################################################################################
2 :     # WeBWorK Online Homework Delivery System
3 :     # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4 :     #
5 :     #
6 :     # This program is free software; you can redistribute it and/or modify it under
7 :     # the terms of either: (a) the GNU General Public License as published by the
8 :     # Free Software Foundation; either version 2, or (at your option) any later
9 :     # version, or (b) the "Artistic License" which comes with this package.
10 :     #
11 :     # This program is distributed in the hope that it will be useful, but WITHOUT
12 :     # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 :     # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
14 :     # Artistic License for more details.
15 :     ################################################################################
16 :    
17 :     package WeBWorK::ContentGenerator::Instructor::ProblemSetDetail;
18 :     use base qw(WeBWorK::ContentGenerator::Instructor);
19 :    
20 :     =head1 NAME
21 :    
22 :     WeBWorK::ContentGenerator::Instructor::ProblemSetDetail - Edit general set and specific user/set information as well as problem information
23 :    
24 :     =cut
25 :    
26 :     use strict;
27 :     use warnings;
28 :     use CGI qw();
29 : toenail 2816 use WeBWorK::HTML::ComboBox qw/comboBox/;
30 :     use WeBWorK::Utils qw(readDirectory list2hash listFilesRecursive max);
31 : toenail 2794 use WeBWorK::DB::Record::Set;
32 :     use WeBWorK::Utils::Tasks qw(renderProblems);
33 : toenail 2901 use WeBWorK::Debug;
34 : toenail 2794
35 :     # Important Note: the following two sets of constants may seem similar
36 :     # but they are functionally and semantically different
37 :    
38 :     # these constants determine which fields belong to what type of record
39 :     use constant SET_FIELDS => [qw(set_header hardcopy_header open_date due_date answer_date published)];
40 :     use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts)];
41 :     use constant USER_PROBLEM_FIELDS => [qw(problem_seed status num_correct num_incorrect)];
42 :    
43 :     # these constants determine what order those fields should be displayed in
44 :     use constant HEADER_ORDER => [qw(set_header hardcopy_header)];
45 :     use constant PROBLEM_FIELD_ORDER => [qw(problem_seed status value max_attempts attempted last_answer num_correct num_incorrect)];
46 :     use constant SET_FIELD_ORDER => [qw(open_date due_date answer_date published)];
47 :    
48 :     # this constant is massive hash of information corresponding to each db field.
49 :     # override indicates for how many students at a time a field can be overridden
50 :     # this hash should make it possible to NEVER have explicitly: if (somefield) { blah() }
51 :     #
52 :     # All but name are optional
53 :     # some_field => {
54 :     # name => "Some Field",
55 :     # type => "edit", # edit, choose, hidden, view - defines how the data is displayed
56 :     # size => "50", # size of the edit box (if any)
57 :     # override => "none", # none, one, any, all - defines for whom this data can/must be overidden
58 :     # module => "problem_list", # WeBWorK module
59 :     # default => 0 # if a field cannot default to undefined/empty what should it default to
60 :     # labels => { # special values can be hashed to display labels
61 :     # 1 => "Yes",
62 :     # 0 => "No",
63 :     # },
64 :     use constant FIELD_PROPERTIES => {
65 :     # Set information
66 :     set_header => {
67 :     name => "Set Header",
68 :     type => "edit",
69 :     size => "50",
70 :     override => "all",
71 :     module => "problem_list",
72 :     default => "",
73 :     },
74 :     hardcopy_header => {
75 :     name => "Hardcopy Header",
76 :     type => "edit",
77 :     size => "50",
78 :     override => "all",
79 :     module => "hardcopy_preselect_set",
80 :     default => "",
81 :     },
82 :     open_date => {
83 :     name => "Opens",
84 :     type => "edit",
85 : toenail 2901 size => "26",
86 : toenail 2794 override => "any",
87 : toenail 2841 labels => {
88 :     0 => "None Specified",
89 :     "" => "None Specified",
90 :     },
91 : toenail 2794 },
92 :     due_date => {
93 :     name => "Answers Due",
94 :     type => "edit",
95 : toenail 2901 size => "26",
96 : toenail 2794 override => "any",
97 : toenail 2841 labels => {
98 :     0 => "None Specified",
99 :     "" => "None Specified",
100 :     },
101 : toenail 2794 },
102 :     answer_date => {
103 :     name => "Answers Available",
104 :     type => "edit",
105 : toenail 2901 size => "26",
106 : toenail 2794 override => "any",
107 : toenail 2841 labels => {
108 :     0 => "None Specified",
109 :     "" => "None Specified",
110 :     },
111 : toenail 2794 },
112 :     published => {
113 :     name => "Visible to Students",
114 :     type => "choose",
115 :     override => "all",
116 :     choices => [qw( 0 1 )],
117 :     labels => {
118 :     1 => "Yes",
119 :     0 => "No",
120 :     },
121 :     },
122 :     # Problem information
123 :     source_file => {
124 :     name => "Source File",
125 :     type => "edit",
126 :     size => 50,
127 :     override => "any",
128 :     default => "",
129 :     },
130 :     value => {
131 :     name => "Weight",
132 :     type => "edit",
133 : toenail 2901 size => 6,
134 : toenail 2794 override => "any",
135 :     },
136 :     max_attempts => {
137 :     name => "Max attempts",
138 :     type => "edit",
139 : toenail 2901 size => 6,
140 : toenail 2794 override => "any",
141 :     labels => {
142 :     "-1" => "unlimited",
143 :     },
144 :     },
145 :     problem_seed => {
146 :     name => "Seed",
147 :     type => "edit",
148 : toenail 2901 size => 6,
149 : toenail 2794 override => "one",
150 :    
151 :     },
152 :     status => {
153 :     name => "Status",
154 :     type => "edit",
155 : toenail 2901 size => 6,
156 :     override => "one",
157 : toenail 2794 default => 0,
158 :     },
159 :     attempted => {
160 :     name => "Attempted",
161 :     type => "hidden",
162 :     override => "none",
163 :     choices => [qw( 0 1 )],
164 :     labels => {
165 :     1 => "Yes",
166 :     0 => "No",
167 :     },
168 :     default => 0,
169 :     },
170 :     last_answer => {
171 :     name => "Last Answer",
172 :     type => "hidden",
173 :     override => "none",
174 :     },
175 :     num_correct => {
176 :     name => "Correct",
177 :     type => "hidden",
178 :     override => "none",
179 :     default => 0,
180 :     },
181 :     num_incorrect => {
182 :     name => "Incorrect",
183 :     type => "hidden",
184 :     override => "none",
185 :     default => 0,
186 :     },
187 :     };
188 :    
189 :     # Create a table of fields for the given parameters, one row for each db field
190 :     # if only the setID is included, it creates a table of set information
191 :     # if the problemID is included, it creates a table of problem information
192 :     sub FieldTable {
193 : sh002i 2913 my ($self, $userID, $setID, $problemID, $globalRecord, $userRecord) = @_;
194 : toenail 2794
195 :     my $r = $self->r;
196 :     my @editForUser = $r->param('editForUser');
197 :     my $forUsers = scalar(@editForUser);
198 :     my $forOneUser = $forUsers == 1;
199 :    
200 :     my @fieldOrder;
201 :     if (defined $problemID) {
202 :     @fieldOrder = @{ PROBLEM_FIELD_ORDER() };
203 :     } else {
204 :     @fieldOrder = @{ SET_FIELD_ORDER() };
205 :     }
206 :    
207 :     my $output = CGI::start_table({border => 0, cellpadding => 1});
208 : sh002i 2948 if ($forUsers) {
209 :     $output .= CGI::Tr(
210 : gage 2952 CGI::th({colspan=>"2"}, " "),
211 :     CGI::th({colspan=>"1"}, "Individual set dates"),
212 :     CGI::th({}, "Class set dates"),
213 : sh002i 2948 );
214 :     }
215 :    
216 : toenail 2794 foreach my $field (@fieldOrder) {
217 :     my %properties = %{ FIELD_PROPERTIES()->{$field} };
218 :     unless ($properties{type} eq "hidden") {
219 : sh002i 2913 $output .= CGI::Tr({}, CGI::td({}, [$self->FieldHTML($userID, $setID, $problemID, $globalRecord, $userRecord, $field)]));
220 : toenail 2794 }
221 :     }
222 :    
223 :     if (defined $problemID) {
224 : sh002i 2913 #my $problemRecord = $r->{db}->getUserProblem($userID, $setID, $problemID);
225 :     my $problemRecord = $userRecord; # we get this from the caller, hopefully
226 : toenail 2794 $output .= CGI::Tr({}, CGI::td({}, ["","Attempts", ($problemRecord->num_correct || 0) + ($problemRecord->num_incorrect || 0)])) if $forOneUser;
227 :     }
228 :     $output .= CGI::end_table();
229 :    
230 :     return $output;
231 :     }
232 :    
233 :     # Returns a list of information and HTML widgets
234 :     # for viewing and editing the specified db fields
235 :     # if only the setID is included, it creates a list of set information
236 :     # if the problemID is included, it creates a list of problem information
237 :     sub FieldHTML {
238 : sh002i 2913 my ($self, $userID, $setID, $problemID, $globalRecord, $userRecord, $field) = @_;
239 : toenail 2794
240 :     my $r = $self->r;
241 :     my $db = $r->db;
242 :     my @editForUser = $r->param('editForUser');
243 :     my $forUsers = scalar(@editForUser);
244 :     my $forOneUser = $forUsers == 1;
245 :    
246 : sh002i 2913 #my ($globalRecord, $userRecord, $mergedRecord);
247 :     #if (defined $problemID) {
248 :     # $globalRecord = $db->getGlobalProblem($setID, $problemID);
249 :     # $userRecord = $db->getUserProblem($userID, $setID, $problemID);
250 :     # #$mergedRecord = $db->getMergedProblem($userID, $setID, $problemID); # never used --sam
251 :     #} else {
252 :     # $globalRecord = $db->getGlobalSet($setID);
253 :     # $userRecord = $db->getUserSet($userID, $setID);
254 :     # #$mergedRecord = $db->getMergedSet($userID, $setID); # never user --sam
255 :     #}
256 : toenail 2794
257 :     return "No data exists for set $setID and problem $problemID" unless $globalRecord;
258 :     return "No user specific data exists for user $userID" if $forOneUser and $globalRecord and not $userRecord;
259 :    
260 :     my %properties = %{ FIELD_PROPERTIES()->{$field} };
261 :     my %labels = %{ $properties{labels} };
262 :     return "" if $properties{type} eq "hidden";
263 :     return "" if $properties{override} eq "one" && not $forOneUser;
264 :     return "" if $properties{override} eq "none" && not $forOneUser;
265 :     return "" if $properties{override} eq "all" && $forUsers;
266 :    
267 :     my $edit = ($properties{type} eq "edit") && ($properties{override} ne "none");
268 :     my $choose = ($properties{type} eq "choose") && ($properties{override} ne "none");
269 :    
270 :     my $globalValue = $globalRecord->{$field};
271 :     $globalValue = $globalValue ? ($labels{$globalValue || ""} || $globalValue) : "";
272 :     my $userValue = $userRecord->{$field};
273 :     $userValue = $userValue ? ($labels{$userValue || ""} || $userValue) : "";
274 :    
275 :     if ($field =~ /_date/) {
276 :     $globalValue = $self->formatDateTime($globalValue) if $globalValue;
277 :     $userValue = $self->formatDateTime($userValue) if $userValue;
278 :     }
279 :    
280 :     # check to make sure that a given value can be overridden
281 :     my %canOverride = map { $_ => 1 } (@{ PROBLEM_FIELDS() }, @{ SET_FIELDS() });
282 :     my $check = $canOverride{$field};
283 :    
284 :     # $recordType is a shorthand in the return statement for problem or set
285 :     # $recordID is a shorthand in the return statement for $problemID or $setID
286 :     my $recordType = "";
287 :     my $recordID = "";
288 :     if (defined $problemID) {
289 :     $recordType = "problem";
290 :     $recordID = $problemID;
291 :     } else {
292 :     $recordType = "set";
293 :     $recordID = $setID;
294 :     }
295 :    
296 :     # $inputType contains either an input box or a popup_menu for changing a given db field
297 :     my $inputType = "";
298 :     if ($edit) {
299 :     $inputType = CGI::input({
300 :     name => "$recordType.$recordID.$field",
301 : toenail 2834 value => $r->param("$recordType.$recordID.$field") || ($forUsers ? $userValue : $globalValue),
302 : toenail 2794 size => $properties{size} || 5,
303 :     });
304 :     } elsif ($choose) {
305 :     # Note that in popup menus, you're almost guaranteed to have the choices hashed to labels in %properties
306 :     # but $userValue and and $globalValue are the values in the hash not the keys
307 :     # so we have to use the actual db record field values to select our default here.
308 :     $inputType = CGI::popup_menu({
309 :     name => "$recordType.$recordID.$field",
310 :     values => $properties{choices},
311 :     labels => \%labels,
312 : toenail 2834 default => $r->param("$recordType.$recordID.$field") || ($forUsers ? $userRecord->$field : $globalRecord->$field),
313 : toenail 2794 });
314 :     }
315 :    
316 :     return (($forUsers && $edit && $check) ? CGI::checkbox({
317 :     type => "checkbox",
318 :     name => "$recordType.$recordID.$field.override",
319 :     label => "",
320 :     value => $field,
321 : toenail 2834 checked => $r->param("$recordType.$recordID.$field.override") || ($userValue ne "" ? 1 : 0),
322 : toenail 2794 }) : "",
323 :     $properties{name},
324 :     $inputType,
325 :     $forUsers ? " $globalValue" : "",
326 :     );
327 :     }
328 :    
329 :     # creates a popup menu of all possible problem numbers (for possible rearranging)
330 :     sub problem_number_popup {
331 :     my $num = shift;
332 :     my $total = shift;
333 :     return (CGI::popup_menu(-name => "problem_num_$num",
334 :     -values => [1..$total],
335 :     -default => $num));
336 :     }
337 :    
338 :     # handles rearrangement necessary after changes to problem ordering
339 :     sub handle_problem_numbers {
340 :     my $newProblemNumbersref = shift;
341 :     my %newProblemNumbers = %$newProblemNumbersref;
342 :     my $maxNum = shift;
343 :     my $db = shift;
344 : toenail 2816 my $setID = shift;
345 : toenail 2794 my $force = shift || 0;
346 :     my @sortme=();
347 :     my ($j, $val);
348 :    
349 :     foreach $j (keys %newProblemNumbers) {
350 :     # what happens our first time on this page
351 :     return "" if (not defined $newProblemNumbers{"$j"});
352 :     if ($newProblemNumbers{"$j"} != $j) {
353 :     $force = 1;
354 :     $val = 1000 * $newProblemNumbers{$j} - $j;
355 :     } else {
356 :     $val = 1000 * $newProblemNumbers{$j};
357 :     }
358 :     push @sortme, [$j, $val];
359 : toenail 2816 $newProblemNumbers{$j} = $db->getGlobalProblem($setID, $j);
360 :     die "global $j for set $setID not found." unless $newProblemNumbers{$j};
361 : toenail 2794 }
362 :    
363 :     return "" unless $force;
364 :    
365 :     @sortme = sort {$a->[1] <=> $b->[1]} @sortme;
366 :     # now, for global and each user with this set, loop through problem list
367 :     # get all of the problem records
368 :     # assign new problem numbers
369 :     # loop - if number is new, put the problem record
370 :     # print "Sorted to get ". join(', ', map {$_->[0] } @sortme) ."<p>\n";
371 :    
372 :    
373 :     # Now, three stages. First global values
374 :    
375 :     for ($j = 0; $j < scalar @sortme; $j++) {
376 :     if($sortme[$j]->[0] == $j + 1) {
377 :     # do nothing
378 :     } elsif (not defined $newProblemNumbers{$j + 1}) {
379 :     $newProblemNumbers{$sortme[$j]->[0]}->problem_id($j + 1);
380 :     $db->addGlobalProblem($newProblemNumbers{$sortme[$j]->[0]});
381 :     } else {
382 :     $newProblemNumbers{$sortme[$j]->[0]}->problem_id($j + 1);
383 :     $db->putGlobalProblem($newProblemNumbers{$sortme[$j]->[0]});
384 :     }
385 :     }
386 :    
387 : toenail 2816 my @setUsers = $db->listSetUsers($setID);
388 : toenail 2794 my (@problist, $user);
389 :     my $globalUserID = $db->{set}->{params}->{globalUserID} || '';
390 :    
391 :     foreach $user (@setUsers) {
392 :     # if this is gdbm, the global user has been taken care of above.
393 :     # we can't do it again. This relies on the global user not having
394 :     # a blank name.
395 :     next if $globalUserID eq $user;
396 :     for $j (keys %newProblemNumbers) {
397 : toenail 2816 $problist[$j] = $db->getUserProblem($user, $setID, $j);
398 :     die " problem $j for set $setID and effective user $user not found"
399 : toenail 2794 unless $problist[$j];
400 :     }
401 :     # ok, now we have all problem data for $user
402 :     for($j = 0; $j < scalar @sortme; $j++) {
403 :     if ($sortme[$j]->[0] == $j + 1) {
404 :     # do nothing
405 :     } elsif (not defined $newProblemNumbers{$j + 1}) {
406 :     $problist[$sortme[$j]->[0]]->problem_id($j + 1);
407 :     $db->addUserProblem($problist[$sortme[$j]->[0]]);
408 :     } else {
409 :     $problist[$sortme[$j]->[0]]->problem_id($j + 1);
410 :     $db->putUserProblem($problist[$sortme[$j]->[0]]);
411 :     }
412 :     }
413 :     }
414 :    
415 :    
416 :     foreach ($j = scalar @sortme; $j < $maxNum; $j++) {
417 :     if (defined $newProblemNumbers{$j + 1}) {
418 : toenail 2816 $db->deleteGlobalProblem($setID, $j+1);
419 : toenail 2794 }
420 :     }
421 :    
422 :     return join(', ', map {$_->[0]} @sortme);
423 :     }
424 :    
425 :     # swap index given with next bigger index
426 :     # leftover from when we had up/down buttons
427 :     # maybe we will bring them back
428 :    
429 : sh002i 2913 #sub moveme {
430 :     # my $index = shift;
431 :     # my $db = shift;
432 :     # my $setID = shift;
433 :     # my (@problemIDList) = @_;
434 :     # my ($prob1, $prob2, $prob);
435 :     #
436 :     # foreach my $problemID (@problemIDList) {
437 :     # my $problemRecord = $db->getGlobalProblem($setID, $problemID); # checked
438 :     # die "global $problemID for set $setID not found." unless $problemRecord;
439 :     # if ($problemRecord->problem_id == $index) {
440 :     # $prob1 = $problemRecord;
441 :     # } elsif ($problemRecord->problem_id == $index + 1) {
442 :     # $prob2 = $problemRecord;
443 :     # }
444 :     # }
445 :     # if (not defined $prob1 or not defined $prob2) {
446 :     # die "cannot find problem $index or " . ($index + 1);
447 :     # }
448 :     #
449 :     # $prob1->problem_id($index + 1);
450 :     # $prob2->problem_id($index);
451 :     # $db->putGlobalProblem($prob1);
452 :     # $db->putGlobalProblem($prob2);
453 :     #
454 :     # my @setUsers = $db->listSetUsers($setID);
455 :     #
456 :     # my $user;
457 :     # foreach $user (@setUsers) {
458 :     # $prob1 = $db->getUserProblem($user, $setID, $index); #checked
459 :     # die " problem $index for set $setID and effective user $user not found"
460 :     # unless $prob1;
461 :     # $prob2 = $db->getUserProblem($user, $setID, $index+1); #checked
462 :     # die " problem $index for set $setID and effective user $user not found"
463 :     # unless $prob2;
464 :     # $prob1->problem_id($index+1);
465 :     # $prob2->problem_id($index);
466 :     # $db->putUserProblem($prob1);
467 :     # $db->putUserProblem($prob2);
468 :     # }
469 :     #}
470 : toenail 2794
471 :     # primarily saves any changes into the correct set or problem records (global vs user)
472 :     # also deals with deleting or rearranging problems
473 :     sub initialize {
474 :     my ($self) = @_;
475 :     my $r = $self->r;
476 :     my $db = $r->db;
477 :     my $ce = $r->ce;
478 :     my $authz = $r->authz;
479 :     my $user = $r->param('user');
480 :     my $setID = $r->urlpath->arg("setID");
481 :     my $setRecord = $db->getGlobalSet($setID); # checked
482 :     die "global set $setID not found." unless $setRecord;
483 :    
484 :     $self->{set} = $setRecord;
485 :     my @editForUser = $r->param('editForUser');
486 :     # some useful booleans
487 :     my $forUsers = scalar(@editForUser);
488 :     my $forOneUser = $forUsers == 1;
489 :    
490 :     # Check permissions
491 :     return unless ($authz->hasPermissions($user, "access_instructor_tools"));
492 :     return unless ($authz->hasPermissions($user, "modify_problem_sets"));
493 :    
494 :    
495 :     my %properties = %{ FIELD_PROPERTIES() };
496 :    
497 :     # takes a hash of hashes and inverts it
498 :     my %undoLabels;
499 :     foreach my $key (keys %properties) {
500 :     %{ $undoLabels{$key} } = map { $properties{$key}->{labels}->{$_} => $_ } keys %{ $properties{$key}->{labels} };
501 :     }
502 :    
503 : toenail 2816 # Unfortunately not everyone uses Javascript enabled browsers so
504 :     # we must fudge the information coming from the ComboBoxes
505 :     # Since the textfield and menu both have the same name, we get an array of two elements
506 :     # We then reset the param to the first if its not-empty or the second (empty or not).
507 :     foreach ( @{ HEADER_ORDER() } ) {
508 :     my @values = $r->param("set.$setID.$_");
509 : toenail 2817 my $value = $values[0] || $values[1] || "";
510 : toenail 2816 $r->param("set.$setID.$_", $value);
511 :     }
512 : toenail 2794
513 : toenail 2901 #####################################################################
514 :     # Check date information
515 :     #####################################################################
516 :    
517 :     my ($open_date, $due_date, $answer_date);
518 :     my $error = 0;
519 : toenail 2794 if (defined $r->param('submit_changes')) {
520 : toenail 2901
521 :     my $od_param = $r->param("set.$setID.open_date");
522 :     my $dd_param = $r->param("set.$setID.due_date");
523 :     my $ad_param = $r->param("set.$setID.answer_date");
524 : sh002i 2913 #my $setRecord = $db->getGlobalSet($setID); # already fetched above --sam
525 : toenail 2794
526 : toenail 2901 $open_date = $od_param ? $self->parseDateTime($od_param) : $setRecord->open_date;
527 :     $due_date = $dd_param ? $self->parseDateTime($dd_param) : $setRecord->due_date;
528 :     $answer_date = $ad_param ? $self->parseDateTime($ad_param) : $setRecord->answer_date;
529 :    
530 :     if ($answer_date < $due_date || $answer_date < $open_date) {
531 :     $self->addbadmessage("Answers cannot be made available until on or after the due date!");
532 :     $error = $r->param('submit_changes');
533 :     }
534 :    
535 :     if ($due_date < $open_date) {
536 :     $self->addbadmessage("Answers cannot be due until on or after the open date!");
537 :     $error = $r->param('submit_changes');
538 :     }
539 :    
540 :     if ($error) {
541 :     $self->addbadmessage("No changes were saved!");
542 :     }
543 :     }
544 :    
545 :    
546 :     if (defined $r->param('submit_changes') && !$error) {
547 :    
548 : sh002i 2913 #my $setRecord = $db->getGlobalSet($setID); # already fetched above --sam
549 : toenail 2901
550 : toenail 2794 #####################################################################
551 :     # Save general set information (including headers)
552 :     #####################################################################
553 :    
554 :     if ($forUsers) {
555 :     my @userRecords = $db->getUserSets(map { [$_, $setID] } @editForUser);
556 :     foreach my $record (@userRecords) {
557 :     foreach my $field ( @{ SET_FIELDS() } ) {
558 :     next unless canChange($forUsers, $field);
559 :    
560 :     my $override = $r->param("set.$setID.$field.override");
561 :     if (defined $override && $override eq $field) {
562 :    
563 :     my $param = $r->param("set.$setID.$field");
564 : toenail 2841 $param = $properties{$field}->{default} || "" unless defined $param && $param ne "";
565 : toenail 2794 $param = $undoLabels{$field}->{$param} || $param;
566 :     if ($field =~ /_date/) {
567 :     $param = $self->parseDateTime($param);
568 :     }
569 :     $record->$field($param);
570 :     } else {
571 :     $record->$field(undef);
572 :     }
573 :     }
574 :     $db->putUserSet($record);
575 :     }
576 :     } else {
577 :     foreach my $field ( @{ SET_FIELDS() } ) {
578 :     next unless canChange($forUsers, $field);
579 :    
580 :     my $param = $r->param("set.$setID.$field");
581 : toenail 2841 $param = $properties{$field}->{default} || "" unless defined $param && $param ne "";
582 : toenail 2794 $param = $undoLabels{$field}->{$param} || $param;
583 :     if ($field =~ /_date/) {
584 :     $param = $self->parseDateTime($param);
585 :     }
586 :     $setRecord->$field($param);
587 :     }
588 :     $db->putGlobalSet($setRecord);
589 :     }
590 :    
591 :    
592 :     #####################################################################
593 :     # Save problem information
594 :     #####################################################################
595 :    
596 : toenail 2901 my @problemIDs = sort { $a <=> $b } $db->listGlobalProblems($setID);;
597 : toenail 2816 my @problemRecords = $db->getGlobalProblems(map { [$setID, $_] } @problemIDs);
598 :     foreach my $problemRecord (@problemRecords) {
599 : toenail 2794 my $problemID = $problemRecord->problem_id;
600 :     die "Global problem $problemID for set $setID not found." unless $problemRecord;
601 :    
602 :     if ($forUsers) {
603 :     # Since we're editing for specific users, we don't allow the GlobalProblem record to be altered on that same page
604 :     # So we only need to make changes to the UserProblem record and only then if we are overriding a value
605 :     # in the GlobalProblem record or for fields unique to the UserProblem record.
606 :    
607 :     my @userIDs = @editForUser;
608 :     my @userProblemIDs = map { [$_, $setID, $problemID] } @userIDs;
609 :     my @userProblemRecords = $db->getUserProblems(@userProblemIDs);
610 :     foreach my $record (@userProblemRecords) {
611 :    
612 :     my $changed = 0; # keep track of any changes, if none are made, avoid unnecessary db accesses
613 :     foreach my $field ( @{ PROBLEM_FIELDS() } ) {
614 :     next unless canChange($forUsers, $field);
615 :    
616 :     my $override = $r->param("problem.$problemID.$field.override");
617 :     if (defined $override && $override eq $field) {
618 :    
619 :     my $param = $r->param("problem.$problemID.$field");
620 : toenail 2841 $param = $properties{$field}->{default} || "" unless defined $param && $param ne "";
621 : toenail 2794 $param = $undoLabels{$field}->{$param} || $param;
622 :     $changed ||= changed($record->$field, $param);
623 :     $record->$field($param);
624 :     } else {
625 :     $changed ||= changed($record->$field, undef);
626 :     $record->$field(undef);
627 :     }
628 :    
629 :     }
630 :    
631 :     foreach my $field ( @{ USER_PROBLEM_FIELDS() } ) {
632 :     next unless canChange($forUsers, $field);
633 :    
634 :     my $param = $r->param("problem.$problemID.$field");
635 : toenail 2841 $param = $properties{$field}->{default} || "" unless defined $param && $param ne "";
636 : toenail 2794 $param = $undoLabels{$field}->{$param} || $param;
637 :     $changed ||= changed($record->$field, $param);
638 :     $record->$field($param);
639 :     }
640 :     $db->putUserProblem($record) if $changed;
641 :     }
642 :     } else {
643 :     # Since we're editing for ALL set users, we will make changes to the GlobalProblem record.
644 :     # We may also have instances where a field is unique to the UserProblem record but we want
645 :     # all users to (at least initially) have the same value
646 :    
647 :     # this only edits a globalProblem record
648 :     my $changed = 0; # keep track of any changes, if none are made, avoid unnecessary db accesses
649 :     foreach my $field ( @{ PROBLEM_FIELDS() } ) {
650 :     next unless canChange($forUsers, $field);
651 :    
652 :     my $param = $r->param("problem.$problemID.$field");
653 : toenail 2841 $param = $properties{$field}->{default} || "" unless defined $param && $param ne "";
654 : toenail 2794 $param = $undoLabels{$field}->{$param} || $param;
655 :     $changed ||= changed($problemRecord->$field, $param);
656 :     $problemRecord->$field($param);
657 :     }
658 :     $db->putGlobalProblem($problemRecord) if $changed;
659 :    
660 :    
661 :     # sometimes (like for status) we might want to change an attribute in
662 :     # the userProblem record for every assigned user
663 :     # However, since this data is stored in the UserProblem records,
664 :     # it won't be displayed once its been changed and if you hit "Save Changes" again
665 :     # it gets erased
666 :    
667 :     # So we'll enforce that there be something worth putting in all the UserProblem records
668 :     # This also will make hitting "Save Changes" on the global page MUCH faster
669 :     my %useful;
670 :     foreach my $field ( @{ USER_PROBLEM_FIELDS() } ) {
671 :     my $param = $r->param("problem.$problemID.$field");
672 :     $useful{$field} = 1 if defined $param and $param ne "";
673 :     }
674 :    
675 :     if (keys %useful) {
676 :     my @userIDs = $db->listProblemUsers($setID, $problemID);
677 :     my @userProblemIDs = map { [$_, $setID, $problemID] } @userIDs;
678 :     my @userProblemRecords = $db->getUserProblems(@userProblemIDs);
679 :     foreach my $record (@userProblemRecords) {
680 :     my $changed = 0; # keep track of any changes, if none are made, avoid unnecessary db accesses
681 : toenail 2901 foreach my $field ( keys %useful ) {
682 : toenail 2794 next unless canChange($forUsers, $field);
683 : toenail 2901
684 : toenail 2794 my $param = $r->param("problem.$problemID.$field");
685 : toenail 2841 $param = $properties{$field}->{default} || "" unless defined $param && $param ne "";
686 : toenail 2794 $param = $undoLabels{$field}->{$param} || $param;
687 :     $changed ||= changed($record->$field, $param);
688 :     $record->$field($param);
689 :     }
690 :     $db->putUserProblem($record) if $changed;
691 :     }
692 :     }
693 :     }
694 :     }
695 : toenail 2834
696 : toenail 2794 # Delete all problems marked for deletion
697 :     foreach my $problemID ($r->param('deleteProblem')) {
698 :     $db->deleteGlobalProblem($setID, $problemID);
699 :     }
700 :    
701 : toenail 2834 # Sets the specified header to "" so that the default file will get used.
702 :     foreach my $header ($r->param('defaultHeader')) {
703 : toenail 2794 $setRecord->$header("");
704 :     }
705 : toenail 2834
706 : toenail 2901 # Mark the specified problems as correct for all users
707 :     foreach my $problemID ($r->param('markCorrect')) {
708 :     my @userProblemIDs = map { [$_, $setID, $problemID] } ($forUsers ? @editForUser : $db->listProblemUsers($setID, $problemID));
709 :     my @userProblemRecords = $db->getUserProblems(@userProblemIDs);
710 :     foreach my $record (@userProblemRecords) {
711 :     $self->addbadmessage($record->user_id);
712 :     if (defined $record && ($record->status eq "" || $record->status < 1)) {
713 :     $record->status(1);
714 :     $record->attempted(1);
715 :     $db->putUserProblem($record);
716 :     }
717 :     }
718 : toenail 2834 }
719 : toenail 2901 }
720 : toenail 2834
721 : toenail 2794 # Leftover code from when there were up/down buttons
722 :    
723 :     # } else {
724 :     # # Look for up and down buttons
725 :     # my $index = 2;
726 :     # while ($index <= scalar @problemList) {
727 :     # if (defined $r->param("move.up.$index.x")) {
728 :     # moveme($index-1, $db, $setID, @problemList);
729 :     # }
730 :     # $index++;
731 :     # }
732 :     # $index = 1;
733 :     #
734 :     # while ($index < scalar @problemList) {
735 :     # if (defined $r->param("move.down.$index.x")) {
736 :     # moveme($index, $db, $setID, @problemList);
737 :     # }
738 :     # $index++;
739 :     # }
740 : toenail 2834 # }
741 :    
742 : toenail 2794
743 : toenail 2901 # This erases any sticky fields if the user saves changes, resets the form, or reorders problems
744 :     # It may not be obvious why this is necessary when saving changes or reordering problems
745 :     # but when the problems are reorder the param problem.1.source_file needs to be the source
746 :     # file of the problem that is NOW #1 and not the problem that WAS #1.
747 :     unless (defined $r->param('refresh')) {
748 :    
749 :     # reset all the parameters dealing with set/problem/header information
750 :     # if the current naming scheme is changed/broken, this could reek havoc
751 :     # on all kinds of things
752 :     foreach my $param ($r->param) {
753 :     $r->param($param, "") if $param =~ /^(set|problem|header)\./;
754 :     }
755 :     }
756 : toenail 2794
757 :     }
758 :    
759 :     # helper method for debugging
760 : toenail 2901 sub definedness ($) {
761 : toenail 2794 my ($variable) = @_;
762 :    
763 :     return "undefined" unless defined $variable;
764 :     return "empty" unless $variable ne "";
765 :     return $variable;
766 :     }
767 :    
768 :     # helper method for checking if two things are different
769 :     # the return values will usually be thrown away, but they could be useful for debugging
770 :     sub changed ($$) {
771 :     my ($first, $second) = @_;
772 :    
773 :     return "def/undef" if defined $first and not defined $second;
774 :     return "undef/def" if not defined $first and defined $second;
775 : toenail 2834 return "" if not defined $first and not defined $second;
776 : toenail 2794 return "ne" if $first ne $second;
777 : toenail 2834 return ""; # if they're equal, there's no change
778 : toenail 2794 }
779 :    
780 : toenail 2834 # helper method that determines for how many users at a time a field can be changed
781 : toenail 2794 # none means it can't be changed for anyone
782 :     # any means it can be changed for anyone
783 :     # one means it can ONLY be changed for one at a time. (eg problem_seed)
784 :     # all means it can ONLY be changed for all at a time. (eg set_header)
785 :     sub canChange ($$) {
786 :     my ($forUsers, $field) = @_;
787 :    
788 :     my %properties = %{ FIELD_PROPERTIES() };
789 :     my $forOneUser = $forUsers == 1;
790 :    
791 :     my $howManyCan = $properties{$field}->{override};
792 :    
793 :     return 0 if $howManyCan eq "none";
794 :     return 1 if $howManyCan eq "any";
795 :     return 1 if $howManyCan eq "one" && $forOneUser;
796 : toenail 2816 return 1 if $howManyCan eq "all" && !$forUsers;
797 : toenail 2794 return 0; # FIXME: maybe it should default to 1?
798 :     }
799 :    
800 : toenail 2834 # helper method that determines if a file is valid and returns a pretty error message
801 :     sub checkFile ($) {
802 :     my ($self, $file) = @_;
803 :    
804 :     my $r = $self->r;
805 :     my $ce = $r->ce;
806 :    
807 :     return "No source file specified" unless $file;
808 :     $file = $ce->{courseDirs}->{templates} . '/' . $file unless $file =~ m|^/|;
809 :    
810 :     my $text = "This source file ";
811 :     my $fileError;
812 :     return "" if -e $file && -f $file && -r $file;
813 :     return $text . "is not readable!" if -e $file && -f $file;
814 :     return $text . "is a directory!" if -d $file;
815 :     return $text . "does not exist!" unless -e $file;
816 :     return $text . "is not a plain file!";
817 :     }
818 :    
819 : toenail 2794 # Creates two separate tables, first of the headers, and the of the problems in a given set
820 :     # If one or more users are specified in the "editForUser" param, only the data for those users
821 :     # becomes editable, not all the data
822 :     sub body {
823 :    
824 :     my ($self) = @_;
825 :     my $r = $self->r;
826 :     my $db = $r->db;
827 :     my $ce = $r->ce;
828 :     my $authz = $r->authz;
829 :     my $userID = $r->param('user');
830 :     my $urlpath = $r->urlpath;
831 : toenail 2901 my $courseID = $urlpath->arg("courseID");
832 :     my $setID = $urlpath->arg("setID");
833 :     my $setRecord = $db->getGlobalSet($setID) or die "No record for global set $setID.";
834 :    
835 :     my $userRecord = $db->getUser($userID) or die "No record for user $userID.";
836 :     # Check permissions
837 :     return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.")
838 :     unless $authz->hasPermissions($userRecord->user_id, "access_instructor_tools");
839 :    
840 :     return CGI::div({class=>"ResultsWithError"}, "You are not authorized to modify problems.")
841 :     unless $authz->hasPermissions($userRecord->user_id, "modify_problem_sets");
842 :    
843 : toenail 2794 my @editForUser = $r->param('editForUser');
844 :    
845 : toenail 2901 # Check that every user that we're editing for has a valid UserSet
846 :     my @assignedUsers;
847 :     my @unassignedUsers;
848 :     if (scalar @editForUser) {
849 :     foreach my $ID (@editForUser) {
850 :     if ($db->getUserSet($ID, $setID)) {
851 :     unshift @assignedUsers, $ID;
852 :     } else {
853 :     unshift @unassignedUsers, $ID;
854 :     }
855 :     }
856 :     @editForUser = @assignedUsers;
857 :     $r->param("editForUser", \@editForUser);
858 :    
859 :     if (scalar @editForUser && scalar @unassignedUsers) {
860 :     print CGI::div({class=>"ResultsWithError"}, "The following users are NOT assigned to this set and will be ignored: " . CGI::b(join(", ", @unassignedUsers)));
861 :     } elsif (scalar @editForUser == 0) {
862 :     print CGI::div({class=>"ResultsWithError"}, "None of the selected users are assigned to this set: " . CGI::b(join(", ", @unassignedUsers)));
863 :     print CGI::div({class=>"ResultsWithError"}, "Global set data will be shown instead of user specific data");
864 :     }
865 :     }
866 :    
867 : toenail 2794 # some useful booleans
868 :     my $forUsers = scalar(@editForUser);
869 :     my $forOneUser = $forUsers == 1;
870 :    
871 : toenail 2901 # If you're editing for users, initially their records will be different but
872 : toenail 2794 # if you make any changes to them they will be the same.
873 :     # if you're editing for one user, the problems shown should be his/hers
874 : toenail 2901 my $userToShow = $forUsers ? $editForUser[0] : $userID;
875 : toenail 2794
876 :     my $userCount = $db->listUsers();
877 : toenail 2807 my $setCount = $db->listGlobalSets() if $forOneUser;
878 : toenail 2816 my $setUserCount = $db->countSetUsers($setID);
879 : toenail 2807 my $userSetCount = $db->countUserSets($editForUser[0]) if $forOneUser;
880 : toenail 2901
881 :    
882 : toenail 2794 my $editUsersAssignedToSetURL = $self->systemLink(
883 :     $urlpath->newFromModule(
884 :     "WeBWorK::ContentGenerator::Instructor::UsersAssignedToSet",
885 : toenail 2816 courseID => $courseID, setID => $setID));
886 : toenail 2807 my $editSetsAssignedToUserURL = $self->systemLink(
887 :     $urlpath->newFromModule(
888 :     "WeBWorK::ContentGenerator::Instructor::SetsAssignedToUser",
889 : toenail 2816 courseID => $courseID, userID => $editForUser[0])) if $forOneUser;
890 : toenail 2794
891 : toenail 2807
892 : toenail 2816 my $setDetailPage = $urlpath -> newFromModule($urlpath->module, courseID => $courseID, setID => $setID);
893 : toenail 2794 my $setDetailURL = $self->systemLink($setDetailPage,authen=>0);
894 :    
895 :    
896 : toenail 2807 my $userCountMessage = CGI::a({href=>$editUsersAssignedToSetURL}, $self->userCountMessage($setUserCount, $userCount));
897 :     my $setCountMessage = CGI::a({href=>$editSetsAssignedToUserURL}, $self->setCountMessage($userSetCount, $setCount)) if $forOneUser;
898 : toenail 2794
899 : toenail 2816 $userCountMessage = "The set $setID is assigned to " . $userCountMessage . ".";
900 : toenail 2807 $setCountMessage = "The user $editForUser[0] has been assigned " . $setCountMessage . "." if $forOneUser;
901 : toenail 2794
902 : toenail 2807 if ($forUsers) {
903 : toenail 2794 print CGI::p("$userCountMessage Editing user-specific overrides for ". CGI::b(join ", ", @editForUser));
904 : toenail 2807 if ($forOneUser) {
905 :     print CGI::p($setCountMessage);
906 :     }
907 : toenail 2794 } else {
908 :     print CGI::p($userCountMessage);
909 :     }
910 :    
911 : toenail 2901 # handle renumbering of problems if necessary
912 :     print CGI::a({name=>"problems"});
913 : toenail 2794
914 : toenail 2901 my %newProblemNumbers = ();
915 :     my $maxProblemNumber = -1;
916 :     for my $jj (sort { $a <=> $b } $db->listGlobalProblems($setID)) {
917 :     $newProblemNumbers{$jj} = $r->param('problem_num_' . $jj);
918 :     $maxProblemNumber = $jj if $jj > $maxProblemNumber;
919 :     }
920 :    
921 :     my $forceRenumber = $r->param('force_renumber') || 0;
922 :     handle_problem_numbers(\%newProblemNumbers, $maxProblemNumber, $db, $setID, $forceRenumber) unless defined $r->param('undo_changes');
923 :    
924 : toenail 2794 my %properties = %{ FIELD_PROPERTIES() };
925 :    
926 :     my %display_modes = %{WeBWorK::PG::DISPLAY_MODES()};
927 :     my @active_modes = grep { exists $display_modes{$_} } @{$r->ce->{pg}->{displayModes}};
928 :     push @active_modes, 'None';
929 :     my $default_header_mode = $r->param('header.displaymode') || 'None';
930 :     my $default_problem_mode = $r->param('problem.displaymode') || 'None';
931 :    
932 : toenail 2816 #####################################################################
933 :     # Browse available header/problem files
934 :     #####################################################################
935 :    
936 :     my $templates = $r->ce->{courseDirs}->{templates};
937 :     my %probLibs = %{ $r->ce->{courseFiles}->{problibs} };
938 :     my $skip = join("|", keys %probLibs);
939 : toenail 2794
940 : toenail 2816 my @headerFileList = listFilesRecursive(
941 :     $templates,
942 :     qr/header.*\.pg$/i, # match these files
943 :     qr/^(?:$skip|CVS)$/, # prune these directories
944 :     0, # match against file name only
945 :     1, # prune against path relative to $templates
946 :     );
947 :    
948 :     # this just takes too much time to search
949 :     # my @problemFileList = listFilesRecursive(
950 :     # $templates,
951 :     # qr/\.pg$/i, # problem files don't say problem
952 :     # qr/^(?:$skip|CVS)$/, # prune these directories
953 :     # 0, # match against file name only
954 :     # 1, # prune against path relative to $templates
955 :     # );
956 :    
957 : toenail 2794 # Display a useful warning message
958 :     if ($forUsers) {
959 :     print CGI::p(CGI::b("Any changes made below will be reflected in the set for ONLY the student" .
960 :     ($forOneUser ? "" : "s") . " listed above."));
961 :     } else {
962 :     print CGI::p(CGI::b("Any changes made below will be reflected in the set for ALL students."));
963 :     }
964 :    
965 :     print CGI::start_form({method=>"POST", action=>$setDetailURL});
966 : toenail 2834 print $self->hiddenEditForUserFields(@editForUser);
967 :     print $self->hidden_authen_fields;
968 : toenail 2794 print CGI::input({type=>"submit", name=>"submit_changes", value=>"Save Changes"});
969 : toenail 2834 print CGI::input({type=>"submit", name=>"undo_changes", value => "Reset Form"});
970 :    
971 : toenail 2794 # spacing
972 :     print CGI::p();
973 :    
974 :     #####################################################################
975 :     # Display general set information
976 :     #####################################################################
977 :    
978 :     print CGI::start_table({border=>1, cellpadding=>4});
979 :     print CGI::Tr({}, CGI::th({}, [
980 :     "General Information",
981 :     ]));
982 : sh002i 2913
983 :     # this is kind of a hack -- we need to get a user record here, so we can
984 :     # pass it to FieldTable, so FieldTable can pass it to FieldHTML, so
985 :     # FieldHTML doesn't have to fetch it itself.
986 :     my $userSetRecord = $db->getUserSet($userToShow, $setID);
987 :    
988 : toenail 2794 print CGI::Tr({}, CGI::td({}, [
989 : sh002i 2913 $self->FieldTable($userToShow, $setID, undef, $setRecord, $userSetRecord),
990 : toenail 2794 ]));
991 :     print CGI::end_table();
992 :    
993 :     # spacing
994 :     print CGI::p();
995 :    
996 :    
997 :     #####################################################################
998 :     # Display header information
999 :     #####################################################################
1000 :     my @headers = @{ HEADER_ORDER() };
1001 : toenail 2834 my %headerModules = (set_header => 'problem_list', hardcopy_header => 'hardcopy_preselect_set');
1002 :     my %headerDefaults = (set_header => $ce->{webworkFiles}->{screenSnippets}->{setHeader}, hardcopy_header => $ce->{webworkFiles}->{hardcopySnippets}->{setHeader});
1003 : toenail 2794 my @headerFiles = map { $setRecord->{$_} } @headers;
1004 :     if (scalar @headers and not $forUsers) {
1005 :    
1006 :     print CGI::start_table({border=>1, cellpadding=>4});
1007 :     print CGI::Tr({}, CGI::th({}, [
1008 :     "Headers",
1009 :     # "Data",
1010 :     "Display&nbsp;Mode:&nbsp;" .
1011 :     CGI::popup_menu(-name => "header.displaymode", -values => \@active_modes, -default => $default_header_mode) . '&nbsp;'.
1012 : gage 2904 CGI::input({type => "submit", name => "refresh", value => "Refresh Display"}),
1013 : toenail 2794 ]));
1014 :    
1015 :     my %header_html;
1016 :    
1017 : toenail 2834 my %error;
1018 : toenail 2794 foreach my $header (@headers) {
1019 : toenail 2834 my $headerFile = $r->param("set.$setID.$header") || $setRecord->{$header} || $headerDefaults{$header};
1020 :    
1021 :     $error{$header} = $self->checkFile($headerFile);
1022 :     unless ($error{$header}) {
1023 :     my @temp = renderProblems( r=> $r,
1024 :     user => $db->getUser($userToShow),
1025 :     displayMode=> $default_header_mode,
1026 :     problem_number=> 0,
1027 :     this_set => $db->getMergedSet($userToShow, $setID),
1028 :     problem_list => [$headerFile],
1029 :     );
1030 :     $header_html{$header} = $temp[0];
1031 :     }
1032 : toenail 2794 }
1033 :    
1034 :     foreach my $header (@headers) {
1035 :    
1036 : toenail 2816 my $editHeaderPage = $urlpath->new(type => 'instructor_problem_editor_withset_withproblem', args => { courseID => $courseID, setID => $setID, problemID => 0 });
1037 : toenail 2794 my $editHeaderLink = $self->systemLink($editHeaderPage, params => { file_type => $header, make_local_copy => 1 });
1038 :    
1039 : toenail 2816 my $viewHeaderPage = $urlpath->new(type => $headerModules{$header}, args => { courseID => $courseID, setID => $setID });
1040 : toenail 2794 my $viewHeaderLink = $self->systemLink($viewHeaderPage);
1041 :    
1042 :     print CGI::Tr({}, CGI::td({}, [
1043 :     CGI::start_table({border => 0, cellpadding => 0}) .
1044 :     CGI::Tr({}, CGI::td({}, $properties{$header}->{name})) .
1045 :     CGI::Tr({}, CGI::td({}, CGI::a({href => $editHeaderLink}, "Edit it"))) .
1046 :     CGI::Tr({}, CGI::td({}, CGI::a({href => $viewHeaderLink}, "View it"))) .
1047 : toenail 2834 # CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "defaultHeader", value => $header, label => "Use Default"}))) .
1048 : toenail 2794 CGI::end_table(),
1049 :     # "",
1050 : toenail 2816 # CGI::input({ name => "set.$setID.$header", value => $setRecord->{$header}, size => 50}) .
1051 :     # join ("\n", $self->FieldHTML($userToShow, $setID, $problemID, "source_file")) .
1052 :     # CGI::br() . CGI::div({class=> "RenderSolo"}, $problem_html[0]->{body_text}),
1053 :    
1054 :     comboBox({
1055 :     name => "set.$setID.$header",
1056 :     request => $r,
1057 : toenail 2834 default => $r->param("set.$setID.$header") || $setRecord->{$header},
1058 : toenail 2816 multiple => 0,
1059 :     values => ["", @headerFileList],
1060 :     labels => { "" => "Use Default Header File" },
1061 :     }) .
1062 : toenail 2834 ($error{$header} ?
1063 :     CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $error{$header})
1064 :     : CGI::div({class=> "RenderSolo"}, $header_html{$header}->{body_text})
1065 :     ),
1066 : toenail 2794 ]));
1067 :     }
1068 :    
1069 :     print CGI::end_table();
1070 :     } else {
1071 :     print CGI::p(CGI::b("Screen and Hardcopy set header information can not be overridden for individual students."));
1072 :     }
1073 :    
1074 :     # spacing
1075 :     print CGI::p();
1076 :    
1077 :    
1078 :     #####################################################################
1079 :     # Display problem information
1080 :     #####################################################################
1081 :    
1082 : toenail 2901 my @problemIDList = sort { $a <=> $b } $db->listGlobalProblems($setID);
1083 : sh002i 2913
1084 :     # get global problem records for all problems in one go
1085 :     my %GlobalProblems;
1086 :     my @globalKeypartsRef = map { [$setID, $_] } @problemIDList;
1087 :     @GlobalProblems{@problemIDList} = $db->getGlobalProblems(@globalKeypartsRef);
1088 :    
1089 :     # if needed, get user problem records for all problems in one go
1090 :     my (%UserProblems, %MergedProblems);
1091 :     if ($forOneUser) {
1092 :     my @userKeypartsRef = map { [$editForUser[0], $setID, $_] } @problemIDList;
1093 :     @UserProblems{@problemIDList} = $db->getUserProblems(@userKeypartsRef);
1094 :     @MergedProblems{@problemIDList} = $db->getMergedProblems(@userKeypartsRef);
1095 :     }
1096 :    
1097 : toenail 2816 if (scalar @problemIDList) {
1098 : toenail 2794
1099 :     print CGI::start_table({border=>1, cellpadding=>4});
1100 :     print CGI::Tr({}, CGI::th({}, [
1101 :     "Problems",
1102 :     "Data",
1103 :     "Display&nbsp;Mode:&nbsp;" .
1104 :     CGI::popup_menu(-name => "problem.displaymode", -values => \@active_modes, -default => $default_problem_mode) . '&nbsp;'.
1105 : gage 2904 CGI::input({type => "submit", name => "refresh", value => "Refresh Display"}),
1106 : toenail 2794 ]));
1107 :    
1108 : toenail 2834 my %shownYet;
1109 :     my $repeatFile;
1110 : toenail 2816 foreach my $problemID (@problemIDList) {
1111 : toenail 2794
1112 :     my $problemRecord;
1113 :     if ($forOneUser) {
1114 : sh002i 2913 #$problemRecord = $db->getMergedProblem($editForUser[0], $setID, $problemID);
1115 :     $problemRecord = $MergedProblems{$problemID}; # already fetched above --sam
1116 : toenail 2794 } else {
1117 : sh002i 2913 #$problemRecord = $db->getGlobalProblem($setID, $problemID);
1118 :     $problemRecord = $GlobalProblems{$problemID}; # already fetched above --sam
1119 : toenail 2794 }
1120 :    
1121 : sh002i 2913 #$self->addgoodmessage("");
1122 :     #$self->addbadmessage($problemRecord->toString());
1123 :    
1124 :    
1125 : toenail 2816 my $editProblemPage = $urlpath->new(type => 'instructor_problem_editor_withset_withproblem', args => { courseID => $courseID, setID => $setID, problemID => $problemID });
1126 :     my $editProblemLink = $self->systemLink($editProblemPage, params => { make_local_copy => 0 });
1127 : toenail 2794
1128 :     # FIXME: should we have an "act as" type link here when editing for multiple users?
1129 : toenail 2841 my $viewProblemPage = $urlpath->new(type => 'problem_detail', args => { courseID => $courseID, setID => $setID, problemID => $problemID });
1130 : toenail 2794 my $viewProblemLink = $self->systemLink($viewProblemPage, params => { effectiveUser => ($forOneUser ? $editForUser[0] : $userID)});
1131 :    
1132 :     my @fields = @{ PROBLEM_FIELDS() };
1133 :     push @fields, @{ USER_PROBLEM_FIELDS() } if $forOneUser;
1134 :    
1135 : toenail 2834 my $problemFile = $r->param("problem.$problemID.source_file") || $problemRecord->source_file;
1136 :    
1137 :     # warn of repeat problems
1138 :     if (defined $shownYet{$problemFile}) {
1139 :     $repeatFile = "This problem uses the same source file as number " . $shownYet{$problemFile} . ".";
1140 :     } else {
1141 :     $shownYet{$problemFile} = $problemID;
1142 : toenail 2966 $repeatFile = "";
1143 : toenail 2834 }
1144 :    
1145 :     my $error = $self->checkFile($problemFile);
1146 :     my @problem_html;
1147 :     unless ($error) {
1148 :     @problem_html = renderProblems( r=> $r,
1149 : toenail 2794 user => $db->getUser($userToShow),
1150 :     displayMode=> $default_problem_mode,
1151 : toenail 2816 problem_number=> $problemID,
1152 :     this_set => $db->getMergedSet($userToShow, $setID),
1153 : toenail 2794 problem_seed => $forOneUser ? $problemRecord->problem_seed : 0,
1154 :     problem_list => [$problemRecord->source_file],
1155 : toenail 2834 );
1156 :     }
1157 : toenail 2794
1158 :     print CGI::Tr({}, CGI::td({}, [
1159 :     CGI::start_table({border => 0, cellpadding => 1}) .
1160 : toenail 2816 CGI::Tr({}, CGI::td({}, problem_number_popup($problemID, $maxProblemNumber))) .
1161 : toenail 2794 CGI::Tr({}, CGI::td({}, CGI::a({href => $editProblemLink}, "Edit it"))) .
1162 :     CGI::Tr({}, CGI::td({}, CGI::a({href => $viewProblemLink}, "Try it" . ($forOneUser ? " (as $editForUser[0])" : "")))) .
1163 : toenail 2816 ($forUsers ? "" : CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "deleteProblem", value => $problemID, label => "Delete it?"})))) .
1164 :     # CGI::Tr({}, CGI::td({}, "Delete&nbsp;it?" . CGI::input({type => "checkbox", name => "deleteProblem", value => $problemID}))) .
1165 : toenail 2901 ($forOneUser ? "" : CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "markCorrect", value => $problemID, label => "Mark Correct?"})))) .
1166 : toenail 2794 CGI::end_table(),
1167 : sh002i 2913 $self->FieldTable($userToShow, $setID, $problemID, $GlobalProblems{$problemID}, $UserProblems{$problemID}),
1168 : toenail 2816 # A comprehensive list of problems is just TOO big to be handled well
1169 :     # comboBox({
1170 :     # name => "set.$setID.$problemID",
1171 :     # request => $r,
1172 :     # default => $problemRecord->{problem_id},
1173 :     # multiple => 0,
1174 :     # values => \@problemFileList,
1175 :     # }) .
1176 :    
1177 : sh002i 2913 join ("\n", $self->FieldHTML(
1178 :     $userToShow,
1179 :     $setID,
1180 :     $problemID,
1181 :     $GlobalProblems{$problemID}, # pass previously fetched global record to FieldHTML --sam
1182 :     $UserProblems{$problemID}, # pass previously fetched user record to FieldHTML --sam
1183 :     "source_file"
1184 :     )) .
1185 : toenail 2834 CGI::br() .
1186 :     ($error ?
1187 :     CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $error)
1188 :     : CGI::div({class=> "RenderSolo"}, $problem_html[0]->{body_text})
1189 :     ) .
1190 :     ($repeatFile ? CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $repeatFile) : ''),
1191 : toenail 2794 ]));
1192 :     }
1193 :    
1194 :     print CGI::end_table();
1195 :     print CGI::checkbox({
1196 :     label=> "Force problems to be numbered consecutively from one",
1197 :     name=>"force_renumber", value=>"1"}),
1198 :    
1199 :     CGI::br();
1200 :     print CGI::input({type=>"submit", name=>"submit_changes", value=>"Save Changes"});
1201 : toenail 2901 print CGI::input({type=>"submit", name=>"handle_numbers", value=>"Reorder problems only"}) . "(Any unsaved changes will be lost.)";
1202 : toenail 2794 print CGI::p(<<HERE);
1203 :     Any time problem numbers are intentionally changed, the problems will
1204 :     always be renumbered consecutively, starting from one. When deleting
1205 :     problems, gaps will be left in the numbering unless the box above is
1206 :     checked.
1207 :     HERE
1208 :     print CGI::p("It is before the open date. You probably want to renumber the problems if you are deleting some from the middle.") if ($setRecord->open_date>time());
1209 :     print CGI::p("When changing problem numbers, we will move
1210 :     the problem to be ", CGI::em("before"), " the chosen number.");
1211 :    
1212 :     } else {
1213 :     print CGI::p(CGI::b("This set doesn't contain any problems yet."));
1214 :     }
1215 :    
1216 :     print CGI::end_form();
1217 :    
1218 :     return "";
1219 :     }
1220 :    
1221 :     1;
1222 :    
1223 :     =head1 AUTHOR
1224 :    
1225 :     Written by Robert Van Dam, toenail (at) cif.rochester.edu
1226 :    
1227 :     =cut

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9