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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9