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

Diff of /branches/gage_dev/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm

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

Revision 3787 Revision 4918
1################################################################################ 1################################################################################
2# WeBWorK Online Homework Delivery System 2# WeBWorK Online Homework Delivery System
3# Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ 3# Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/
4# 4#
5# 5#
6# This program is free software; you can redistribute it and/or modify it under 6# This program is free software; you can redistribute it and/or modify it under
7# the terms of either: (a) the GNU General Public License as published by the 7# the terms of either: (a) the GNU General Public License as published by the
8# Free Software Foundation; either version 2, or (at your option) any later 8# Free Software Foundation; either version 2, or (at your option) any later
23 23
24=cut 24=cut
25 25
26use strict; 26use strict;
27use warnings; 27use warnings;
28use CGI qw(); 28#use CGI qw(-nosticky );
29use WeBWorK::CGI;
29use WeBWorK::HTML::ComboBox qw/comboBox/; 30use WeBWorK::HTML::ComboBox qw/comboBox/;
30use WeBWorK::Utils qw(readDirectory list2hash listFilesRecursive max); 31use WeBWorK::Utils qw(readDirectory list2hash listFilesRecursive max);
31use WeBWorK::DB::Record::Set;
32use WeBWorK::Utils::Tasks qw(renderProblems); 32use WeBWorK::Utils::Tasks qw(renderProblems);
33use WeBWorK::Debug; 33use WeBWorK::Debug;
34# IP RESTRICT
35use WeBWorK::HTML::ScrollingRecordList qw/scrollingRecordList/;
34 36
35# Important Note: the following two sets of constants may seem similar 37# Important Note: the following two sets of constants may seem similar
36# but they are functionally and semantically different 38# but they are functionally and semantically different
37 39
38# these constants determine which fields belong to what type of record 40# these constants determine which fields belong to what type of record
41# IP RESTRICT
39use constant SET_FIELDS => [qw(set_header hardcopy_header open_date due_date answer_date published assignment_type attempts_per_version version_time_limit versions_per_interval time_interval problem_randorder)]; 42use constant SET_FIELDS => [qw(set_header hardcopy_header open_date due_date answer_date published restrict_ip relax_restrict_ip assignment_type attempts_per_version version_time_limit versions_per_interval time_interval problem_randorder problems_per_page hide_score hide_work)];
40use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts)]; 43use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts)];
41use constant USER_PROBLEM_FIELDS => [qw(problem_seed status num_correct num_incorrect)]; 44use constant USER_PROBLEM_FIELDS => [qw(problem_seed status num_correct num_incorrect)];
42 45
43# these constants determine what order those fields should be displayed in 46# these constants determine what order those fields should be displayed in
44use constant HEADER_ORDER => [qw(set_header hardcopy_header)]; 47use constant HEADER_ORDER => [qw(set_header hardcopy_header)];
46 49
47# we exclude the gateway set fields from the set field order, because they 50# we exclude the gateway set fields from the set field order, because they
48# are only displayed for sets that are gateways. this results in a bit of 51# are only displayed for sets that are gateways. this results in a bit of
49# convoluted logic below, but it saves burdening people who are only using 52# convoluted logic below, but it saves burdening people who are only using
50# homework assignments with all of the gateway parameters 53# homework assignments with all of the gateway parameters
54# FIXME: in the long run, we may want to let hide_score and hide_work be
55# FIXME: set for non-gateway assignments. right now (11/30/06) they are
56# FIXME: only used for gateways
57# IP RESTRICT
51use constant SET_FIELD_ORDER => [qw(open_date due_date answer_date published assignment_type)]; 58use constant SET_FIELD_ORDER => [qw(open_date due_date answer_date published restrict_ip relax_restrict_ip assignment_type)];
52use constant GATEWAY_SET_FIELD_ORDER => [qw(attempts_per_version version_time_limit time_interval versions_per_interval problem_randorder)]; 59# use constant GATEWAY_SET_FIELD_ORDER => [qw(attempts_per_version version_time_limit time_interval versions_per_interval problem_randorder problems_per_page hide_score hide_work)];
60use constant GATEWAY_SET_FIELD_ORDER => [qw(version_time_limit time_limit_cap attempts_per_version time_interval versions_per_interval problem_randorder problems_per_page hide_score hide_work)];
53 61
54# this constant is massive hash of information corresponding to each db field. 62# this constant is massive hash of information corresponding to each db field.
55# override indicates for how many students at a time a field can be overridden 63# override indicates for how many students at a time a field can be overridden
56# this hash should make it possible to NEVER have explicitly: if (somefield) { blah() } 64# this hash should make it possible to NEVER have explicitly: if (somefield) { blah() }
57# 65#
65# default => 0 # if a field cannot default to undefined/empty what should it default to 73# default => 0 # if a field cannot default to undefined/empty what should it default to
66# labels => { # special values can be hashed to display labels 74# labels => { # special values can be hashed to display labels
67# 1 => "Yes", 75# 1 => "Yes",
68# 0 => "No", 76# 0 => "No",
69# }, 77# },
78# convertby => 60, # divide incoming database field values by this, and multiply when saving
79# format => 'string' # for edit entries, we require that the input data match /^string$/
80# # format is not currently used
81
82use constant BLANKPROBLEM => 'blankProblem.pg';
83
70use constant FIELD_PROPERTIES => { 84use constant FIELD_PROPERTIES => {
71 # Set information 85 # Set information
72 set_header => { 86 set_header => {
73 name => "Set Header", 87 name => "Set Header",
74 type => "edit", 88 type => "edit",
123 labels => { 137 labels => {
124 1 => "Yes", 138 1 => "Yes",
125 0 => "No", 139 0 => "No",
126 }, 140 },
127 }, 141 },
142 restrict_ip => {
143 name => "Restrict Access by IP",
144 type => "choose",
145 override => "any",
146 choices => [qw( No RestrictTo DenyFrom )],
147 labels => {
148 No => "No",
149 RestrictTo => "Restrict To",
150 DenyFrom => "Deny From",
151 },
152 default => 'No',
153 },
154 relax_restrict_ip => {
155 name => "Relax IP restrictions when?",
156 type => "choose",
157 override => "any",
158 choices => [qw( No AfterAnswerDate AfterVersionAnswerDate )],
159 labels => {
160 No => "Never",
161 AfterAnswerDate => "After set answer date",
162 AfterVersionAnswerDate => "(gw/quiz) After version answer date",
163 },
164 default => 'No',
165 },
128 assignment_type => { 166 assignment_type => {
129 name => "Assignment type", 167 name => "Assignment type",
130 type => "choose", 168 type => "choose",
131 override => "all", 169 override => "all",
132 choices => [qw( default gateway proctored_gateway )], 170 choices => [qw( default gateway proctored_gateway )],
133 labels => { default => "homework", 171 labels => { default => "homework",
134 gateway => "gateway/quiz", 172 gateway => "gateway/quiz",
135 proctored_gateway => "proctored gateway/quiz", 173 proctored_gateway => "proctored gateway/quiz",
136 }, 174 },
137 }, 175 },
176 version_time_limit => {
177 name => "Test Time Limit (min)",
178 type => "edit",
179 size => "4",
180 override => "any",
181# labels => { "" => 0 }, # I'm not sure this is quite right
182 convertby => 60,
183 format => '[1-9]\d*', # a non-zero integer
184 },
185 time_limit_cap => {
186 name => "Cap Test Time at Set Due Date?",
187 type => "choose",
188 override => "all",
189 choices => [qw(0 1)],
190 labels => { '0' => 'No', '1' => 'Yes' },
191 },
138 attempts_per_version => { 192 attempts_per_version => {
139 name => "Attempts per Version (untested for > 1)", 193 name => "Number of Graded Submissions per Test",
140 type => "edit", 194 type => "edit",
141 size => "3", 195 size => "3",
142 override => "all", 196 override => "any",
197 format => '[1-9]\d*', # a non-zero integer
143# labels => { "" => 1 }, 198# labels => { "" => 1 },
144 }, 199 },
145 version_time_limit => {
146 name => "Test Time Limit (sec)",
147 type => "edit",
148 size => "4",
149 override => "all",
150 labels => { "" => 0 }, # I'm not sure this is quite right
151 },
152 time_interval => { 200 time_interval => {
153 name => "Time Interval for New Versions (sec)", 201 name => "Time Interval for New Test Versions (min; 0=infty)",
154 type => "edit", 202 type => "edit",
155 size => "5", 203 size => "5",
156 override => "all", 204 override => "any",
205 format => '[0-9]+', # an integer, possibly zero
157 labels => { "" => 0 }, 206# labels => { "" => 0 },
207 convertby => 60,
158 }, 208 },
159 versions_per_interval => { 209 versions_per_interval => {
160 name => "Number of New Versions per Time Interval (0=infty)", 210 name => "Number of Tests per Time Interval (0=infty)",
161 type => "edit", 211 type => "edit",
162 size => "3", 212 size => "3",
163 override => "all", 213 override => "any",
214 default => "0",
215 format => '[0-9]+', # an integer, possibly zero
216# labels => { "" => 0 },
164# labels => { "" => 1 }, 217# labels => { "" => 1 },
165 }, 218 },
166 problem_randorder => { 219 problem_randorder => {
167 name => "Order Problems Randomly", 220 name => "Order Problems Randomly",
168 type => "choose", 221 type => "choose",
169 choices => [qw( 0 1 )], 222 choices => [qw( 0 1 )],
170 override => "all", 223 override => "any",
171 labels => { 0 => "No", 1 => "Yes" }, 224 labels => { 0 => "No", 1 => "Yes" },
225 },
226 problems_per_page => {
227 name => "Number of Problems per Page (0=all)",
228 type => "edit",
229 size => "3",
230 override => "any",
231 default => "0",
232 format => '[0-9]+', # an integer, possibly zero
233# labels => { "" => 0 },
234 },
235 hide_score => {
236 name => "Show Score on Finished Assignments",
237 type => "choose",
238 choices => [ qw(N Y BeforeAnswerDate) ],
239 override => "any",
240 labels => { 'N' => "Yes", 'Y' => "No", 'BeforeAnswerDate' => 'Only after set answer date' },
241 },
242 hide_work => {
243 name => "Show Student Work on Finished Tests",
244 type => "choose",
245 choices => [ qw(N Y BeforeAnswerDate) ],
246 override => "any",
247 labels => { 'N' => "Yes", 'Y' => "No", 'BeforeAnswerDate' => 'Only after set answer date' },
172 }, 248 },
173 # Problem information 249 # Problem information
174 source_file => { 250 source_file => {
175 name => "Source File", 251 name => "Source File",
176 type => "edit", 252 type => "edit",
247 my @editForUser = $r->param('editForUser'); 323 my @editForUser = $r->param('editForUser');
248 my $forUsers = scalar(@editForUser); 324 my $forUsers = scalar(@editForUser);
249 my $forOneUser = $forUsers == 1; 325 my $forOneUser = $forUsers == 1;
250 326
251 my @fieldOrder; 327 my @fieldOrder;
328
252 my $gwoutput = ''; 329 my $gwoutput = '';
330# IP RESTRICT
331 my $db = $r->{db};
332 my $ipSelector = '';
333 my $glIPlist;
334 my $numLocations = 0;
335 my $orChecked;
336
253 if (defined $problemID) { 337 if (defined $problemID) {
254 @fieldOrder = @{ PROBLEM_FIELD_ORDER() }; 338 @fieldOrder = @{ PROBLEM_FIELD_ORDER() };
255 } else { 339 } else {
256 @fieldOrder = @{ SET_FIELD_ORDER() }; 340 @fieldOrder = @{ SET_FIELD_ORDER() };
257 341
258 # gateway data fields are included only if the set is a gateway 342 # gateway data fields are included only if the set is a gateway
259 if ( $globalRecord->assignment_type() =~ /gateway/ ) { 343 if ( $globalRecord->assignment_type() =~ /gateway/ ) {
260 $gwoutput = "\n<!-- begin gwoutput table -->\n" . CGI::start_table({border => 0, cellpadding => 1}); 344 my $gwhdr = "\n<!-- begin gwoutput table -->\n";
345 my $nF = 0;
346
261 foreach my $gwfield ( @{ GATEWAY_SET_FIELD_ORDER() } ) { 347 foreach my $gwfield ( @{ GATEWAY_SET_FIELD_ORDER() } ) {
262 $gwoutput .= CGI::Tr({}, CGI::td({}, [$self->FieldHTML($userID, $setID, $problemID, $globalRecord, $userRecord, $gwfield)])); 348 my @fieldData =
349 ($self->FieldHTML($userID, $setID, $problemID,
350 $globalRecord, $userRecord,
351 $gwfield));
352 if ( @fieldData && defined($fieldData[1]) and $fieldData[1] ne '' ) {
353 $nF = @fieldData if ( @fieldData > $nF );
354 $gwoutput .= CGI::Tr({}, CGI::td({}, [@fieldData]));
263 } 355 }
264 $gwoutput .= CGI::end_table() . "\n<!-- end gwoutput table -->\n"; 356 }
357 $gwhdr .= CGI::Tr({},CGI::td({colspan=>$nF},
358 CGI::em("Gateway parameters")))
359 if ( $nF );
360 $gwoutput = "$gwhdr$gwoutput\n" .
361 "<!-- end gwoutput table -->\n";
362 }
363 # IP RESTRICT
364 # similarly, we only include an ip selector if restrict_ip is not 'No'.
365 # we have to know if there are any locations to know if we should show
366 # the restrict_ip option, however, so get those regardless
367 my @locations = sort {$a cmp $b} ($db->listLocations());
368 $numLocations = @locations;
369
370 if ( ( ! $forUsers && $globalRecord->restrict_ip ne 'No' ) ||
371 ( $forUsers && $userRecord->restrict_ip ne 'No' ) ) {
372 my @globalLocations = $db->listGlobalSetLocations($setID);
373 # what ip locations should be selected?
374 my @defaultLocations = ();
375 if ( $forUsers &&
376 ! $db->countUserSetLocations($userID, $setID) ) {
377 @defaultLocations = @globalLocations;
378 $orChecked = 0;
379 } elsif ( $forUsers ) {
380 @defaultLocations = $db->listUserSetLocations($userID, $setID);
381 $orChecked = 1;
382 } else {
383 @defaultLocations = @globalLocations;
384 }
385
386 $ipSelector = CGI::scrolling_list({
387 -name => "set.$setID.selected_ip_locations",
388 -values => [ @locations ],
389 -default => [ @defaultLocations ],
390 -size => 5,
391 -multiple => 'true'});
392
393 # also show global set location list when editing
394 # user sets
395 $glIPlist = join(', ', @globalLocations);
265 } 396 }
266 } 397 }
267 398
268 my $output = CGI::start_table({border => 0, cellpadding => 1}); 399 my $output = CGI::start_table({border => 0, cellpadding => 1});
269 if ($forUsers) { 400 if ($forUsers) {
270 $output .= CGI::Tr( 401 $output .= CGI::Tr({},
271 CGI::th({colspan=>"2"}, "&nbsp;"), 402 CGI::th({colspan=>"2"}, "&nbsp;"),
272 CGI::th({colspan=>"1"}, "User Values"), 403 CGI::th({colspan=>"1"}, "User Values"),
273 CGI::th({}, "Class values"), 404 CGI::th({}, "Class values"),
274 ); 405 );
275 } 406 }
276
277 foreach my $field (@fieldOrder) { 407 foreach my $field (@fieldOrder) {
278 my %properties = %{ FIELD_PROPERTIES()->{$field} }; 408 my %properties = %{ FIELD_PROPERTIES()->{$field} };
409
410 # IP RESTRICT
411 # we don't show the ip restriction option if there are
412 # no defined locations, nor the relax_restrict_ip option
413 # if we're not restricting ip access
414 next if ( $field eq 'restrict_ip' && ! $numLocations );
415 next if ($field eq 'relax_restrict_ip' &&
416 ( ($forUsers && $userRecord->restrict_ip eq 'No') ||
417 (! $forUsers && $globalRecord->restrict_ip eq 'No')));
418
279 unless ($properties{type} eq "hidden") { 419 unless ($properties{type} eq "hidden") {
280 $output .= CGI::Tr({}, CGI::td({}, [$self->FieldHTML($userID, $setID, $problemID, $globalRecord, $userRecord, $field)])) . "\n"; 420 $output .= CGI::Tr({}, CGI::td({}, [$self->FieldHTML($userID, $setID, $problemID, $globalRecord, $userRecord, $field)])) . "\n";
281 } 421 }
422
423 # IP RESTRICT
424 # we insert the list of locations after the restrict_ip
425 # selector, but only if ip restrictions are turned on
426 if ( $field eq 'restrict_ip' && $ipSelector ) {
427
428# FIXME: while the value of the restrict_ip field for the set is defined in
429# the field properties hash, above, the locations selector does not modify
430# set table properties, and so doesn't. this means that we're just
431# assuming that we can override it for users in any case where we're
432# editing the set for users. in the long run this is probably not the
433# best behavior.
434 my $override = ($forUsers) ?
435 CGI::checkbox({ type => "checkbox",
436 name => "set.$setID.selected_ip_locations.override",
437 label => "",
438 checked => $orChecked }) : '';
439 $output .= CGI::Tr({-valign=>'top'},
440 CGI::td({}, [ $override,
441 'Restrict Locations',
442 $ipSelector,
443 $forUsers ? " $glIPlist" : '', ]
444 ),
445 );
446 }
447
282 # this is a rather artifical addition to include gateway fields, which we 448 # this is a rather artifical addition to include gateway fields, which we
283 # only want to show for gateways 449 # only want to show for gateways
284 $output .= CGI::Tr({}, CGI::td({colspan => '4'}, $gwoutput)) . "\n"
285 if ( $field eq 'assignment_type' && 450 if ( $field eq 'assignment_type' && $gwoutput ) {
286 $globalRecord->assignment_type() =~ /gateway/ ); 451 $output .= "$gwoutput\n";
452 }
287 } 453 }
288 454
289 if (defined $problemID) { 455 if (defined $problemID) {
290 #my $problemRecord = $r->{db}->getUserProblem($userID, $setID, $problemID); 456 #my $problemRecord = $r->{db}->getUserProblem($userID, $setID, $problemID);
291 my $problemRecord = $userRecord; # we get this from the caller, hopefully 457 my $problemRecord = $userRecord; # we get this from the caller, hopefully
340 $userValue = (defined($userValue)) ? ($labels{$userValue || ""} || $userValue) : ""; 506 $userValue = (defined($userValue)) ? ($labels{$userValue || ""} || $userValue) : "";
341 507
342 if ($field =~ /_date/) { 508 if ($field =~ /_date/) {
343 $globalValue = $self->formatDateTime($globalValue) if defined $globalValue && $globalValue ne $labels{""}; 509 $globalValue = $self->formatDateTime($globalValue) if defined $globalValue && $globalValue ne $labels{""};
344 $userValue = $self->formatDateTime($userValue) if defined $userValue && $userValue ne $labels{""}; 510 $userValue = $self->formatDateTime($userValue) if defined $userValue && $userValue ne $labels{""};
511 }
512
513 if ( defined($properties{convertby}) && $properties{convertby} ) {
514 $globalValue = $globalValue/$properties{convertby} if $globalValue;
515 $userValue = $userValue/$properties{convertby} if $userValue;
345 } 516 }
346 517
347 # check to make sure that a given value can be overridden 518 # check to make sure that a given value can be overridden
348 my %canOverride = map { $_ => 1 } (@{ PROBLEM_FIELDS() }, @{ SET_FIELDS() }); 519 my %canOverride = map { $_ => 1 } (@{ PROBLEM_FIELDS() }, @{ SET_FIELDS() });
349 my $check = $canOverride{$field}; 520 my $check = $canOverride{$field};
374 # so we have to use the actual db record field values to select our default here. 545 # so we have to use the actual db record field values to select our default here.
375 $inputType = CGI::popup_menu({ 546 $inputType = CGI::popup_menu({
376 name => "$recordType.$recordID.$field", 547 name => "$recordType.$recordID.$field",
377 values => $properties{choices}, 548 values => $properties{choices},
378 labels => \%labels, 549 labels => \%labels,
379 default => $r->param("$recordType.$recordID.$field") || ($forUsers ? $userRecord->$field : $globalRecord->$field), 550 default => $r->param("$recordType.$recordID.$field") || ($forUsers && $userRecord->$field ne '' ? $userRecord->$field : $globalRecord->$field),
380 }); 551 });
381 } 552 }
382 553
554 my $gDisplVal = defined($properties{labels}) && defined($properties{labels}->{$globalValue}) ? $properties{labels}->{$globalValue} : $globalValue;
555
383 return (($forUsers && $edit && $check) ? CGI::checkbox({ 556# return (($forUsers && $edit && $check) ? CGI::checkbox({
557 return (($forUsers && $check) ? CGI::checkbox({
384 type => "checkbox", 558 type => "checkbox",
385 name => "$recordType.$recordID.$field.override", 559 name => "$recordType.$recordID.$field.override",
386 label => "", 560 label => "",
387 value => $field, 561 value => $field,
388 checked => $r->param("$recordType.$recordID.$field.override") || ($userValue ne ($labels{""} || "") ? 1 : 0), 562 checked => $r->param("$recordType.$recordID.$field.override") || ($userValue ne ($labels{""} || "") ? 1 : 0),
389 }) : "", 563 }) : "",
390 $properties{name}, 564 $properties{name},
391 $inputType, 565 $inputType,
392 $forUsers ? " $globalValue" : "", 566 $forUsers ? " $gDisplVal" : "",
393 ); 567 );
394} 568}
395 569
396# creates a popup menu of all possible problem numbers (for possible rearranging) 570# creates a popup menu of all possible problem numbers (for possible rearranging)
397sub problem_number_popup { 571sub problem_number_popup {
411 my $setID = shift; 585 my $setID = shift;
412 my $force = shift || 0; 586 my $force = shift || 0;
413 my @sortme=(); 587 my @sortme=();
414 my ($j, $val); 588 my ($j, $val);
415 589
590 # keys are current problem numbers, values are target problem numbers
416 foreach $j (keys %newProblemNumbers) { 591 foreach $j (keys %newProblemNumbers) {
417 # what happens our first time on this page 592 # we don't want to act unless all problems have been assigned a new problem number, so if any have not, return
418 return "" if (not defined $newProblemNumbers{"$j"}); 593 return "" if (not defined $newProblemNumbers{"$j"});
594 # if the problem has been given a new number, we reduce the "score" of the problem by the original number of the problem
595 # when multiple problems are assigned the same number, this results in the last one ending up first -- FIXME?
419 if ($newProblemNumbers{"$j"} != $j) { 596 if ($newProblemNumbers{"$j"} != $j) {
597 # force always gets set if reordering is done, so don't expect to be able to delete a problem,
598 # reorder some other problems, and end up with a hole -- FIXME
420 $force = 1; 599 $force = 1;
421 $val = 1000 * $newProblemNumbers{$j} - $j; 600 $val = 1000 * $newProblemNumbers{$j} - $j;
422 } else { 601 } else {
423 $val = 1000 * $newProblemNumbers{$j}; 602 $val = 1000 * $newProblemNumbers{$j};
424 } 603 }
604 # store a mapping between current problem number and score (based on currnet and new problem number)
425 push @sortme, [$j, $val]; 605 push @sortme, [$j, $val];
606 # replace new problem numbers in hash with the (global) problems themselves
426 $newProblemNumbers{$j} = $db->getGlobalProblem($setID, $j); 607 $newProblemNumbers{$j} = $db->getGlobalProblem($setID, $j);
427 die "global $j for set $setID not found." unless $newProblemNumbers{$j}; 608 die "global $j for set $setID not found." unless $newProblemNumbers{$j};
428 } 609 }
429 610
611 # we don't have to do anything if we're not getting rid of holes
430 return "" unless $force; 612 return "" unless $force;
431 613
614 # sort the curr. prob. num./score pairs by score
432 @sortme = sort {$a->[1] <=> $b->[1]} @sortme; 615 @sortme = sort {$a->[1] <=> $b->[1]} @sortme;
433 # now, for global and each user with this set, loop through problem list 616 # now, for global and each user with this set, loop through problem list
434 # get all of the problem records 617 # get all of the problem records
435 # assign new problem numbers 618 # assign new problem numbers
436 # loop - if number is new, put the problem record 619 # loop - if number is new, put the problem record
438 621
439 622
440 # Now, three stages. First global values 623 # Now, three stages. First global values
441 624
442 for ($j = 0; $j < scalar @sortme; $j++) { 625 for ($j = 0; $j < scalar @sortme; $j++) {
443 if($sortme[$j]->[0] == $j + 1) { 626 if($sortme[$j][0] == $j + 1) {
627 # if the jth problem (according to the new ordering) is in the right place (problem IDs are numbered from 1, hence $j+1)
444 # do nothing 628 # do nothing
445 } elsif (not defined $newProblemNumbers{$j + 1}) { 629 } elsif (not defined $newProblemNumbers{$j + 1}) {
630 # otherwise, if there's a hole for it, add it there
446 $newProblemNumbers{$sortme[$j]->[0]}->problem_id($j + 1); 631 $newProblemNumbers{$sortme[$j][0]}->problem_id($j + 1);
447 $db->addGlobalProblem($newProblemNumbers{$sortme[$j]->[0]}); 632 $db->addGlobalProblem($newProblemNumbers{$sortme[$j][0]});
448 } else { 633 } else {
634 # otherwise, overwrite the data for the problem that's already there with the jth problem's data (with a changed problemID)
449 $newProblemNumbers{$sortme[$j]->[0]}->problem_id($j + 1); 635 $newProblemNumbers{$sortme[$j][0]}->problem_id($j + 1);
450 $db->putGlobalProblem($newProblemNumbers{$sortme[$j]->[0]}); 636 $db->putGlobalProblem($newProblemNumbers{$sortme[$j][0]});
451 } 637 }
452 } 638 }
453 639
454 my @setUsers = $db->listSetUsers($setID); 640 my @setUsers = $db->listSetUsers($setID);
455 my (@problist, $user); 641 my (@problist, $user);
456 my $globalUserID = $db->{set}->{params}->{globalUserID} || '';
457 642
458 foreach $user (@setUsers) { 643 foreach $user (@setUsers) {
459 # if this is gdbm, the global user has been taken care of above. 644 # grab a copy of each UserProblem for this user. @problist can be sparse (if problems were deleted)
460 # we can't do it again. This relies on the global user not having
461 # a blank name.
462 next if $globalUserID eq $user;
463 for $j (keys %newProblemNumbers) { 645 for $j (keys %newProblemNumbers) {
464 $problist[$j] = $db->getUserProblem($user, $setID, $j); 646 $problist[$j] = $db->getUserProblem($user, $setID, $j);
465 die " problem $j for set $setID and effective user $user not found"
466 unless $problist[$j];
467 } 647 }
468 # ok, now we have all problem data for $user
469 for($j = 0; $j < scalar @sortme; $j++) { 648 for($j = 0; $j < scalar @sortme; $j++) {
470 if ($sortme[$j]->[0] == $j + 1) { 649 if ($sortme[$j][0] == $j + 1) {
650 # same as above -- the jth problem is in the right place, so don't worry about it
471 # do nothing 651 # do nothing
652 } elsif ($problist[$sortme[$j][0]]) {
653 # we've made sure the user's problem actually exists HERE, since we want to be able to fail gracefullly if it doesn't
654 # the problem with the original conditional below is that %newProblemNumbers maps oldids => global problem record
655 # we need to check if the target USER PROBLEM exists, which is what @problist knows
472 } elsif (not defined $newProblemNumbers{$j + 1}) { 656 #if (not defined $newProblemNumbers{$j + 1}) {
657 if (not defined $problist[$j+1]) {
658 # same as above -- there's a hole for that problem to go into, so add it in its new place
473 $problist[$sortme[$j]->[0]]->problem_id($j + 1); 659 $problist[$sortme[$j][0]]->problem_id($j + 1);
474 $db->addUserProblem($problist[$sortme[$j]->[0]]); 660 $db->addUserProblem($problist[$sortme[$j][0]]);
475 } else { 661 } else {
662 # same as above -- there's a problem already there, so overwrite its data with the data from the jth problem
476 $problist[$sortme[$j]->[0]]->problem_id($j + 1); 663 $problist[$sortme[$j][0]]->problem_id($j + 1);
477 $db->putUserProblem($problist[$sortme[$j]->[0]]); 664 $db->putUserProblem($problist[$sortme[$j][0]]);
478 } 665 }
666 } else {
667 warn "UserProblem missing for user=$user set=$setID problem=$sortme[$j][0]. This may indicate database corruption.\n";
668 # when a problem doesn't exist in the target slot, a new problem gets added there, but the original problem
669 # never gets overwritten (because there wan't a problem it would have to get exchanged with)
670 # i think this can get pretty complex. consider 1=>2, 2=>3, 3=>4, 4=>1 where problem 1 doesn't exist for some user:
671 # @sortme[$j][0] will contain: 4, 1, 2, 3
672 # - problem 1 will get **added** with the data from problem 4 (because problem 1 doesn't exist for this user)
673 # - problem 2 will get overwritten with the data from problem 1
674 # - problem 3 will get overwritten with the data from problem 2
675 # - nothing will happend to problem 4, since problem 1 doesn't exit
676 # so the solution is to delete problem 4 altogether!
677 # here's the fix:
678
679 # the data from problem $j+1 was/will be moved to another problem slot,
680 # but there's no problem $sortme[$j][0] to replace it. thus, we delete it now.
681 $db->deleteUserProblem($user, $setID, $j+1);
682 }
479 } 683 }
480 } 684 }
481 685
482 686 # any problems with IDs above $maxNum get deleted -- presumably their data has been copied into problems with lower IDs
483 foreach ($j = scalar @sortme; $j < $maxNum; $j++) { 687 foreach ($j = scalar @sortme; $j < $maxNum; $j++) {
484 if (defined $newProblemNumbers{$j + 1}) { 688 if (defined $newProblemNumbers{$j + 1}) {
485 $db->deleteGlobalProblem($setID, $j+1); 689 $db->deleteGlobalProblem($setID, $j+1);
486 } 690 }
487 } 691 }
488 692
693 # return a string form of the old problem IDs in the new order (not used by caller, incidentally)
489 return join(', ', map {$_->[0]} @sortme); 694 return join(', ', map {$_->[0]} @sortme);
490} 695}
491 696
492# swap index given with next bigger index 697# swap index given with next bigger index
493# leftover from when we had up/down buttons 698# leftover from when we had up/down buttons
618 } 823 }
619 if ($answer_date > $cutoff) { 824 if ($answer_date > $cutoff) {
620 $self->addbadmessage("Error: answer date cannot be more than 10 years from now in set $setID"); 825 $self->addbadmessage("Error: answer date cannot be more than 10 years from now in set $setID");
621 $error = $r->param('submit_changes'); 826 $error = $r->param('submit_changes');
622 } 827 }
623 828 }
624 829########
830# commented out
831# this runs afoul of the conversion of a set to a gateway
832# assignment, when perforce fields may be empty or zero.
833#
834# #####################################################################
835# # Check for invalid input data
836# #####################################################################
837# # should this be done here?
838# #
839# if ( defined($r->param('submit_changes')) && ! $error ) {
840# foreach my $field ( @{ SET_FIELDS() } ) {
841# if ( $properties{$field}->{type} eq 'choose' &&
842# ! grep {$r->param("set.$setID.$field") !~ /^$_$/} @{$properties{$field}->{choices}} ) {
843# $self->addbadmessage("Error: invalid value given for " . $properties{$field}->{name} . " (valid values are " . join(', ', values(%{$properties{$field}->{labels}})) . ")");
844# $error = $r->param('submit_changes');
845# } elsif ( $properties{$field}->{type} eq 'edit' &&
846# $properties{$field}->{format} &&
847# $field !~ /_date$/ &&
848# $r->param("set.$setID.$field") !~ /^$properties{$field}->{format}$/ ) {
849# $self->addbadmessage("Error: invalid value given for " . $properties{$field}->{name});
850# $error = $r->param('submit_changes');
851# }
852# }
853# }
854
625 if ($error) { 855 if ($error) {
626 $self->addbadmessage("No changes were saved!"); 856 $self->addbadmessage("No changes were saved!");
627 }
628 } 857 }
629 858
630 if (defined $r->param('submit_changes') && !$error) { 859 if (defined $r->param('submit_changes') && !$error) {
631 860
632 #my $setRecord = $db->getGlobalSet($setID); # already fetched above --sam 861 #my $setRecord = $db->getGlobalSet($setID); # already fetched above --sam
634 ##################################################################### 863 #####################################################################
635 # Save general set information (including headers) 864 # Save general set information (including headers)
636 ##################################################################### 865 #####################################################################
637 866
638 if ($forUsers) { 867 if ($forUsers) {
868 # DBFIXME use a WHERE clause, iterator
639 my @userRecords = $db->getUserSets(map { [$_, $setID] } @editForUser); 869 my @userRecords = $db->getUserSets(map { [$_, $setID] } @editForUser);
640 foreach my $record (@userRecords) { 870 foreach my $record (@userRecords) {
641 foreach my $field ( @{ SET_FIELDS() } ) { 871 foreach my $field ( @{ SET_FIELDS() } ) {
642 next unless canChange($forUsers, $field); 872 next unless canChange($forUsers, $field);
643 my $override = $r->param("set.$setID.$field.override"); 873 my $override = $r->param("set.$setID.$field.override");
649 my $unlabel = $undoLabels{$field}->{$param}; 879 my $unlabel = $undoLabels{$field}->{$param};
650 $param = $unlabel if defined $unlabel; 880 $param = $unlabel if defined $unlabel;
651# $param = $undoLabels{$field}->{$param} || $param; 881# $param = $undoLabels{$field}->{$param} || $param;
652 if ($field =~ /_date/) { 882 if ($field =~ /_date/) {
653 $param = $self->parseDateTime($param) unless defined $unlabel; 883 $param = $self->parseDateTime($param) unless defined $unlabel;
884 }
885 if (defined($properties{$field}->{convertby}) && $properties{$field}->{convertby}) {
886 $param = $param*$properties{$field}->{convertby};
654 } 887 }
655 $record->$field($param); 888 $record->$field($param);
656 } else { 889 } else {
657 $record->$field(undef); 890 $record->$field(undef);
658 } 891 }
659 892
660 } 893 }
661 $db->putUserSet($record); 894 $db->putUserSet($record);
662 } 895 }
896
897 # IP RESTRICT
898 # the locations for ip restrictions are saved in the
899 # set_locations_user table, so we have to update
900 # these separately
901# FIXME: need && $check for canoverride; requires adding selected_ip_locations
902# FIXME: to the field values hash, above
903 if ( $r->param("set.$setID.selected_ip_locations.override") ) {
904 foreach my $record ( @userRecords ) {
905 my $userID = $record->user_id;
906 my @selectedLocations = $r->param("set.$setID.selected_ip_locations");
907 my @userSetLocations = $db->listUserSetLocations($userID,$setID);
908 my @addSetLocations = ();
909 my @delSetLocations = ();
910 foreach my $loc ( @selectedLocations ) {
911 push( @addSetLocations, $loc ) if ( ! grep( /^$loc$/, @userSetLocations ) );
912 }
913 foreach my $loc ( @userSetLocations ) {
914 push( @delSetLocations, $loc ) if ( ! grep( /^$loc$/, @selectedLocations ) );
915 }
916 # then update the user set_locations
917 foreach ( @addSetLocations ) {
918 my $Loc = $db->newUserSetLocation;
919 $Loc->set_id( $setID );
920 $Loc->user_id( $userID );
921 $Loc->location_id($_);
922 $db->addUserSetLocation($Loc);
923 }
924 foreach ( @delSetLocations ) {
925 $db->deleteUserSetLocation($userID,$setID,$_);
926 }
927 }
928 } else {
929 # if override isn't selected, then we want
930 # to be sure that there are no
931 # set_locations_user entries setting around
932 foreach my $record ( @userRecords ) {
933 my $userID = $record->user_id;
934 my @userLocations = $db->listUserSetLocations($userID,$setID);
935 foreach ( @userLocations ) {
936 $db->deleteUserSetLocation($userID,$setID,$_);
937 }
938 }
939 }
663 } else { 940 } else {
664 foreach my $field ( @{ SET_FIELDS() } ) { 941 foreach my $field ( @{ SET_FIELDS() } ) {
665 next unless canChange($forUsers, $field); 942 next unless canChange($forUsers, $field);
666 943
667 my $param = $r->param("set.$setID.$field"); 944 my $param = $r->param("set.$setID.$field");
668 $param = $properties{$field}->{default} || "" unless defined $param && $param ne ""; 945 $param = $properties{$field}->{default} || "" unless defined $param && $param ne "";
946
669 my $unlabel = $undoLabels{$field}->{$param}; 947 my $unlabel = $undoLabels{$field}->{$param};
670 $param = $unlabel if defined $unlabel; 948 $param = $unlabel if defined $unlabel;
671 if ($field =~ /_date/) { 949 if ($field =~ /_date/) {
672 $param = $self->parseDateTime($param) unless defined $unlabel; 950 $param = $self->parseDateTime($param) unless defined $unlabel;
673 } 951 }
952 if (defined($properties{$field}->{convertby}) && $properties{$field}->{convertby}) {
953 $param = $param*$properties{$field}->{convertby};
954 }
674 $setRecord->$field($param); 955 $setRecord->$field($param);
675 } 956 }
676 $db->putGlobalSet($setRecord); 957 $db->putGlobalSet($setRecord);
958
959 # IP RESTRICT
960 # the locations for ip restrictions are saved in the
961 # set_locations table, so we have to update these
962 # separately
963# FIXME: need && $check for canoverride; requires adding selected_ip_locations
964# FIXME: to the field values hash, above
965 if ( $r->param("set.$setID.restrict_ip") ne 'No' ) {
966 my @selectedLocations = $r->param("set.$setID.selected_ip_locations");
967 my @globalSetLocations = $db->listGlobalSetLocations($setID);
968 my @addSetLocations = ();
969 my @delSetLocations = ();
970 foreach my $loc ( @selectedLocations ) {
971 push( @addSetLocations, $loc ) if ( ! grep( /^$loc$/, @globalSetLocations ) );
972 }
973 foreach my $loc ( @globalSetLocations ) {
974 push( @delSetLocations, $loc ) if ( ! grep( /^$loc$/, @selectedLocations ) );
975 }
976 # then update the global set_locations
977 foreach ( @addSetLocations ) {
978 my $Loc = $db->newGlobalSetLocation;
979 $Loc->set_id( $setID );
980 $Loc->location_id($_);
981 $db->addGlobalSetLocation($Loc);
982 }
983 foreach ( @delSetLocations ) {
984 $db->deleteGlobalSetLocation($setID,$_);
985 }
986 } else {
987 my @globalSetLocations = $db->listGlobalSetLocations($setID);
988 foreach ( @globalSetLocations ) {
989 $db->deleteGlobalSetLocation($setID,$_);
990 }
991 }
677 } 992 }
678 993
679 ##################################################################### 994 #####################################################################
680 # Save problem information 995 # Save problem information
681 ##################################################################### 996 #####################################################################
682 997
998 # DBFIXME use a WHERE clause, iterator?
683 my @problemIDs = sort { $a <=> $b } $db->listGlobalProblems($setID);; 999 my @problemIDs = sort { $a <=> $b } $db->listGlobalProblems($setID);;
684 my @problemRecords = $db->getGlobalProblems(map { [$setID, $_] } @problemIDs); 1000 my @problemRecords = $db->getGlobalProblems(map { [$setID, $_] } @problemIDs);
685 foreach my $problemRecord (@problemRecords) { 1001 foreach my $problemRecord (@problemRecords) {
686 my $problemID = $problemRecord->problem_id; 1002 my $problemID = $problemRecord->problem_id;
687 die "Global problem $problemID for set $setID not found." unless $problemRecord; 1003 die "Global problem $problemID for set $setID not found." unless $problemRecord;
691 # So we only need to make changes to the UserProblem record and only then if we are overriding a value 1007 # So we only need to make changes to the UserProblem record and only then if we are overriding a value
692 # in the GlobalProblem record or for fields unique to the UserProblem record. 1008 # in the GlobalProblem record or for fields unique to the UserProblem record.
693 1009
694 my @userIDs = @editForUser; 1010 my @userIDs = @editForUser;
695 my @userProblemIDs = map { [$_, $setID, $problemID] } @userIDs; 1011 my @userProblemIDs = map { [$_, $setID, $problemID] } @userIDs;
1012 # DBFIXME where clause? iterator?
696 my @userProblemRecords = $db->getUserProblems(@userProblemIDs); 1013 my @userProblemRecords = $db->getUserProblems(@userProblemIDs);
697 foreach my $record (@userProblemRecords) { 1014 foreach my $record (@userProblemRecords) {
698 1015
699 my $changed = 0; # keep track of any changes, if none are made, avoid unnecessary db accesses 1016 my $changed = 0; # keep track of any changes, if none are made, avoid unnecessary db accesses
700 foreach my $field ( @{ PROBLEM_FIELDS() } ) { 1017 foreach my $field ( @{ PROBLEM_FIELDS() } ) {
761 my $param = $r->param("problem.$problemID.$field"); 1078 my $param = $r->param("problem.$problemID.$field");
762 $useful{$field} = 1 if defined $param and $param ne ""; 1079 $useful{$field} = 1 if defined $param and $param ne "";
763 } 1080 }
764 1081
765 if (keys %useful) { 1082 if (keys %useful) {
1083 # DBFIXME where clause, iterator
766 my @userIDs = $db->listProblemUsers($setID, $problemID); 1084 my @userIDs = $db->listProblemUsers($setID, $problemID);
767 my @userProblemIDs = map { [$_, $setID, $problemID] } @userIDs; 1085 my @userProblemIDs = map { [$_, $setID, $problemID] } @userIDs;
768 my @userProblemRecords = $db->getUserProblems(@userProblemIDs); 1086 my @userProblemRecords = $db->getUserProblems(@userProblemIDs);
769 foreach my $record (@userProblemRecords) { 1087 foreach my $record (@userProblemRecords) {
770 my $changed = 0; # keep track of any changes, if none are made, avoid unnecessary db accesses 1088 my $changed = 0; # keep track of any changes, if none are made, avoid unnecessary db accesses
784 } 1102 }
785 } 1103 }
786 1104
787 # Mark the specified problems as correct for all users 1105 # Mark the specified problems as correct for all users
788 foreach my $problemID ($r->param('markCorrect')) { 1106 foreach my $problemID ($r->param('markCorrect')) {
1107 # DBFIXME where clause, iterator
789 my @userProblemIDs = map { [$_, $setID, $problemID] } ($forUsers ? @editForUser : $db->listProblemUsers($setID, $problemID)); 1108 my @userProblemIDs = map { [$_, $setID, $problemID] } ($forUsers ? @editForUser : $db->listProblemUsers($setID, $problemID));
790 my @userProblemRecords = $db->getUserProblems(@userProblemIDs); 1109 my @userProblemRecords = $db->getUserProblems(@userProblemIDs);
791 foreach my $record (@userProblemRecords) { 1110 foreach my $record (@userProblemRecords) {
792 if (defined $record && ($record->status eq "" || $record->status < 1)) { 1111 if (defined $record && ($record->status eq "" || $record->status < 1)) {
793 $record->status(1); 1112 $record->status(1);
805 ##################################################################### 1124 #####################################################################
806 # Add blank problem if needed 1125 # Add blank problem if needed
807 ##################################################################### 1126 #####################################################################
808 if (defined($r->param("add_blank_problem") ) and $r->param("add_blank_problem") == 1) { 1127 if (defined($r->param("add_blank_problem") ) and $r->param("add_blank_problem") == 1) {
809 my $targetProblemNumber = 1+ WeBWorK::Utils::max( $self->r->db->listGlobalProblems($setID)); 1128 my $targetProblemNumber = 1+ WeBWorK::Utils::max( $self->r->db->listGlobalProblems($setID));
1129 ##################################################
1130 # make local copy of the blankProblem
1131 ##################################################
810 my $blank_file_path = $ce->{webworkFiles}->{screenSnippets}->{blankProblem}; 1132 my $blank_file_path = $ce->{webworkFiles}->{screenSnippets}->{blankProblem};
811 my $new_file_path = $ce->{courseDirs}->{templates}."/set$setID/blank.pg"; 1133 my $problemContents = WeBWorK::Utils::readFile($blank_file_path);
1134 my $new_file_path = "set$setID/".BLANKPROBLEM();
1135 my $fullPath = WeBWorK::Utils::surePathToFile($ce->{courseDirs}->{templates},'/'.$new_file_path);
1136 local(*TEMPFILE);
1137 open(TEMPFILE, ">$fullPath") or warn "Can't write to file $fullPath";
1138 print TEMPFILE $problemContents;
1139 close(TEMPFILE);
1140
812 ################################################# 1141 #################################################
813 # Update problem record 1142 # Update problem record
814 ################################################# 1143 #################################################
815 my $problemRecord = $self->addProblemToSet( 1144 my $problemRecord = $self->addProblemToSet(
816 setName => $setID, 1145 setName => $setID,
817 sourceFile => $blank_file_path, 1146 sourceFile => $new_file_path,
818 problemID => $targetProblemNumber, #added to end of set 1147 problemID => $targetProblemNumber, #added to end of set
819 ); 1148 );
820 $self->assignProblemToAllSetUsers($problemRecord); 1149 $self->assignProblemToAllSetUsers($problemRecord);
821 $self->addgoodmessage("Added $blank_file_path to ". $setID. " as problem $targetProblemNumber") ; 1150 $self->addgoodmessage("Added $new_file_path to ". $setID. " as problem $targetProblemNumber") ;
822 } 1151 }
823 1152
824 # Sets the specified header to "" so that the default file will get used. 1153 # Sets the specified header to "" so that the default file will get used.
825 foreach my $header ($r->param('defaultHeader')) { 1154 foreach my $header ($r->param('defaultHeader')) {
826 $setRecord->$header(""); 1155 $setRecord->$header("");
958 # Check that every user that we're editing for has a valid UserSet 1287 # Check that every user that we're editing for has a valid UserSet
959 my @assignedUsers; 1288 my @assignedUsers;
960 my @unassignedUsers; 1289 my @unassignedUsers;
961 if (scalar @editForUser) { 1290 if (scalar @editForUser) {
962 foreach my $ID (@editForUser) { 1291 foreach my $ID (@editForUser) {
1292 # DBFIXME iterator
963 if ($db->getUserSet($ID, $setID)) { 1293 if ($db->getUserSet($ID, $setID)) {
964 unshift @assignedUsers, $ID; 1294 unshift @assignedUsers, $ID;
965 } else { 1295 } else {
966 unshift @unassignedUsers, $ID; 1296 unshift @unassignedUsers, $ID;
967 } 1297 }
968 } 1298 }
969 @editForUser = @assignedUsers; 1299 @editForUser = sort @assignedUsers;
970 $r->param("editForUser", \@editForUser); 1300 $r->param("editForUser", \@editForUser);
971 1301
972 if (scalar @editForUser && scalar @unassignedUsers) { 1302 if (scalar @editForUser && scalar @unassignedUsers) {
973 print CGI::div({class=>"ResultsWithError"}, "The following users are NOT assigned to this set and will be ignored: " . CGI::b(join(", ", @unassignedUsers))); 1303 print CGI::div({class=>"ResultsWithError"}, "The following users are NOT assigned to this set and will be ignored: " . CGI::b(join(", ", @unassignedUsers)));
974 } elsif (scalar @editForUser == 0) { 1304 } elsif (scalar @editForUser == 0) {
984 # If you're editing for users, initially their records will be different but 1314 # If you're editing for users, initially their records will be different but
985 # if you make any changes to them they will be the same. 1315 # if you make any changes to them they will be the same.
986 # if you're editing for one user, the problems shown should be his/hers 1316 # if you're editing for one user, the problems shown should be his/hers
987 my $userToShow = $forUsers ? $editForUser[0] : $userID; 1317 my $userToShow = $forUsers ? $editForUser[0] : $userID;
988 1318
1319 # DBFIXME no need to get ID lists -- counts would be fine
989 my $userCount = $db->listUsers(); 1320 my $userCount = $db->listUsers();
990 my $setCount = $db->listGlobalSets() if $forOneUser; 1321 my $setCount = $db->listGlobalSets(); # if $forOneUser;
991 my $setUserCount = $db->countSetUsers($setID); 1322 my $setUserCount = $db->countSetUsers($setID);
992 my $userSetCount = $db->countUserSets($editForUser[0]) if $forOneUser; 1323 my $userSetCount = $db->countUserSets($editForUser[0]) if $forOneUser;
993 1324
994 1325
995 my $editUsersAssignedToSetURL = $self->systemLink( 1326 my $editUsersAssignedToSetURL = $self->systemLink(
996 $urlpath->newFromModule( 1327 $urlpath->newFromModule(
997 "WeBWorK::ContentGenerator::Instructor::UsersAssignedToSet", 1328 "WeBWorK::ContentGenerator::Instructor::UsersAssignedToSet",
998 courseID => $courseID, setID => $setID)); 1329 courseID => $courseID, setID => $setID));
999 my $editSetsAssignedToUserURL = $self->systemLink( 1330 my $editSetsAssignedToUserURL = $self->systemLink(
1000 $urlpath->newFromModule( 1331 $urlpath->newFromModule(
1001 "WeBWorK::ContentGenerator::Instructor::SetsAssignedToUser", 1332 "WeBWorK::ContentGenerator::Instructor::UserDetail",
1002 courseID => $courseID, userID => $editForUser[0])) if $forOneUser; 1333 courseID => $courseID, userID => $editForUser[0])) if $forOneUser;
1003 1334
1004 1335
1005 my $setDetailPage = $urlpath -> newFromModule($urlpath->module, courseID => $courseID, setID => $setID); 1336 my $setDetailPage = $urlpath -> newFromModule($urlpath->module, courseID => $courseID, setID => $setID);
1006 my $setDetailURL = $self->systemLink($setDetailPage,authen=>0); 1337 my $setDetailURL = $self->systemLink($setDetailPage, authen=>0);
1007 1338
1008 1339
1009 my $userCountMessage = CGI::a({href=>$editUsersAssignedToSetURL}, $self->userCountMessage($setUserCount, $userCount)); 1340 my $userCountMessage = CGI::a({href=>$editUsersAssignedToSetURL}, $self->userCountMessage($setUserCount, $userCount));
1010 my $setCountMessage = CGI::a({href=>$editSetsAssignedToUserURL}, $self->setCountMessage($userSetCount, $setCount)) if $forOneUser; 1341 my $setCountMessage = CGI::a({href=>$editSetsAssignedToUserURL}, $self->setCountMessage($userSetCount, $setCount)) if $forOneUser;
1011 1342
1012 $userCountMessage = "The set $setID is assigned to " . $userCountMessage . "."; 1343 $userCountMessage = "The set $setID is assigned to " . $userCountMessage . ".";
1013 $setCountMessage = "The user $editForUser[0] has been assigned " . $setCountMessage . "." if $forOneUser; 1344 $setCountMessage = "The user $editForUser[0] has been assigned " . $setCountMessage . "." if $forOneUser;
1014 1345
1015 if ($forUsers) { 1346 if ($forUsers) {
1016 print CGI::p("$userCountMessage Editing user-specific overrides for ". CGI::b(join ", ", @editForUser)); 1347 ##############################################
1017 if ($forOneUser) { 1348 # calculate links for the users being edited:
1018 print CGI::p($setCountMessage); 1349 ##############################################
1350 my @userLinks = ();
1351 foreach my $userID (@editForUser) {
1352 my $u = $db->getUser($userID);
1353 my $email_address = $u->email_address;
1354 my $line = $u->last_name.", ".$u->first_name."&nbsp;&nbsp;(".CGI::a({-href=>"mailto:$email_address"},"email "). $u->user_id."). Assigned to ";
1355 my $editSetsAssignedToUserURL = $self->systemLink(
1356 $urlpath->newFromModule(
1357 "WeBWorK::ContentGenerator::Instructor::UserDetail",
1358 courseID => $courseID, userID => $u->user_id));
1359 $line .= CGI::a({href=>$editSetsAssignedToUserURL},
1360 $self->setCountMessage($db->countUserSets($u->user_id), $setCount));
1361 unshift @userLinks,$line;
1019 } 1362 }
1363 @userLinks = sort @userLinks;
1364
1365 print CGI::table({border=>2,cellpadding=>10},
1366 CGI::Tr({},
1367 CGI::td([
1368 "Editing problem set ".CGI::strong($setID)." data for these individual students:".CGI::br().
1369 CGI::strong(join CGI::br(), @userLinks),
1370 CGI::a({href=>$self->systemLink($setDetailPage) },"Edit set ".CGI::strong($setID)." data for ALL students assigned to this set."),
1371
1372 ])
1373 )
1374 );
1020 } else { 1375 } else {
1021 print CGI::p($userCountMessage); 1376 print CGI::table({border=>2,cellpadding=>10},
1377 CGI::Tr({},
1378 CGI::td([
1379 "This set ".CGI::strong($setID)." is assigned to ".$self->userCountMessage($setUserCount, $userCount).'.' ,
1380 'Edit '.CGI::a({href=>$editUsersAssignedToSetURL},'individual versions '). "of set $setID.",
1381
1382 ])
1383 )
1384 );
1022 } 1385 }
1023 1386
1024 # handle renumbering of problems if necessary 1387 # handle renumbering of problems if necessary
1025 print CGI::a({name=>"problems"}); 1388 print CGI::a({name=>"problems"});
1026 1389
1045 ##################################################################### 1408 #####################################################################
1046 # Browse available header/problem files 1409 # Browse available header/problem files
1047 ##################################################################### 1410 #####################################################################
1048 1411
1049 my $templates = $r->ce->{courseDirs}->{templates}; 1412 my $templates = $r->ce->{courseDirs}->{templates};
1050 my %probLibs = %{ $r->ce->{courseFiles}->{problibs} }; 1413 my $skip = join("|", keys %{ $r->ce->{courseFiles}->{problibs} });
1051 my $skip = join("|", keys %probLibs);
1052 1414
1053 my @headerFileList = listFilesRecursive( 1415 my @headerFileList = listFilesRecursive(
1054 $templates, 1416 $templates,
1055 qr/header.*\.pg$/i, # match these files 1417 qr/header.*\.pg$/i, # match these files
1056 qr/^(?:$skip|CVS)$/, # prune these directories 1418 qr/^(?:$skip|CVS)$/, # prune these directories
1130 my %error; 1492 my %error;
1131 foreach my $header (@headers) { 1493 foreach my $header (@headers) {
1132 my $headerFile = $r->param("set.$setID.$header") || $setRecord->{$header} || $headerDefaults{$header}; 1494 my $headerFile = $r->param("set.$setID.$header") || $setRecord->{$header} || $headerDefaults{$header};
1133 1495
1134 $error{$header} = $self->checkFile($headerFile); 1496 $error{$header} = $self->checkFile($headerFile);
1497 my $this_set = $db->getMergedSet($userToShow, $setID);
1135 unless ($error{$header}) { 1498 unless ($error{$header}) {
1136 my @temp = renderProblems( r=> $r, 1499 my @temp = renderProblems(
1500 r=> $r,
1137 user => $db->getUser($userToShow), 1501 user => $db->getUser($userToShow),
1138 displayMode=> $default_header_mode, 1502 displayMode=> $default_header_mode,
1139 problem_number=> 0, 1503 problem_number=> 0,
1140 this_set => $db->getMergedSet($userToShow, $setID), 1504 this_set => $this_set,
1141 problem_list => [$headerFile], 1505 problem_list => [$headerFile],
1142 ); 1506 );
1143 $header_html{$header} = $temp[0]; 1507 $header_html{$header} = $temp[0];
1144 } 1508 }
1145 } 1509 }
1146 1510
1153 my $viewHeaderLink = $self->systemLink($viewHeaderPage); 1517 my $viewHeaderLink = $self->systemLink($viewHeaderPage);
1154 1518
1155 print CGI::Tr({}, CGI::td({}, [ 1519 print CGI::Tr({}, CGI::td({}, [
1156 CGI::start_table({border => 0, cellpadding => 0}) . 1520 CGI::start_table({border => 0, cellpadding => 0}) .
1157 CGI::Tr({}, CGI::td({}, $properties{$header}->{name})) . 1521 CGI::Tr({}, CGI::td({}, $properties{$header}->{name})) .
1158 CGI::Tr({}, CGI::td({}, CGI::a({href => $editHeaderLink}, "Edit it"))) . 1522 CGI::Tr({}, CGI::td({}, CGI::a({href => $editHeaderLink, target=>"WW_Editor"}, "Edit it"))) .
1159 CGI::Tr({}, CGI::td({}, CGI::a({href => $viewHeaderLink}, "View it"))) . 1523 CGI::Tr({}, CGI::td({}, CGI::a({href => $viewHeaderLink, target=>"WW_View"}, "View it"))) .
1160# CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "defaultHeader", value => $header, label => "Use Default"}))) . 1524# CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "defaultHeader", value => $header, label => "Use Default"}))) .
1161 CGI::end_table(), 1525 CGI::end_table(),
1162# "", 1526# "",
1163# CGI::input({ name => "set.$setID.$header", value => $setRecord->{$header}, size => 50}) . 1527# CGI::input({ name => "set.$setID.$header", value => $setRecord->{$header}, size => 50}) .
1164# join ("\n", $self->FieldHTML($userToShow, $setID, $problemID, "source_file")) . 1528# join ("\n", $self->FieldHTML($userToShow, $setID, $problemID, "source_file")) .
1192 # Display problem information 1556 # Display problem information
1193 ##################################################################### 1557 #####################################################################
1194 1558
1195 my @problemIDList = sort { $a <=> $b } $db->listGlobalProblems($setID); 1559 my @problemIDList = sort { $a <=> $b } $db->listGlobalProblems($setID);
1196 1560
1561 # DBFIXME use iterators instead of getting all at once
1562
1197 # get global problem records for all problems in one go 1563 # get global problem records for all problems in one go
1198 my %GlobalProblems; 1564 my %GlobalProblems;
1199 my @globalKeypartsRef = map { [$setID, $_] } @problemIDList; 1565 my @globalKeypartsRef = map { [$setID, $_] } @problemIDList;
1566 # DBFIXME shouldn't need to get key list here
1200 @GlobalProblems{@problemIDList} = $db->getGlobalProblems(@globalKeypartsRef); 1567 @GlobalProblems{@problemIDList} = $db->getGlobalProblems(@globalKeypartsRef);
1201 1568
1202 # if needed, get user problem records for all problems in one go 1569 # if needed, get user problem records for all problems in one go
1203 my (%UserProblems, %MergedProblems); 1570 my (%UserProblems, %MergedProblems);
1204 if ($forOneUser) { 1571 if ($forOneUser) {
1205 my @userKeypartsRef = map { [$editForUser[0], $setID, $_] } @problemIDList; 1572 my @userKeypartsRef = map { [$editForUser[0], $setID, $_] } @problemIDList;
1573 # DBFIXME shouldn't need to get key list here
1206 @UserProblems{@problemIDList} = $db->getUserProblems(@userKeypartsRef); 1574 @UserProblems{@problemIDList} = $db->getUserProblems(@userKeypartsRef);
1207 @MergedProblems{@problemIDList} = $db->getMergedProblems(@userKeypartsRef); 1575 @MergedProblems{@problemIDList} = $db->getMergedProblems(@userKeypartsRef);
1208 } 1576 }
1209 1577
1210 if (scalar @problemIDList) { 1578 if (scalar @problemIDList) {
1255 $shownYet{$problemFile} = $problemID; 1623 $shownYet{$problemFile} = $problemID;
1256 $repeatFile = ""; 1624 $repeatFile = "";
1257 } 1625 }
1258 1626
1259 my $error = $self->checkFile($problemFile); 1627 my $error = $self->checkFile($problemFile);
1628 my $this_set = $db->getMergedSet($userToShow, $setID);
1260 my @problem_html; 1629 my @problem_html;
1261 unless ($error) { 1630 unless ($error) {
1262 @problem_html = renderProblems( r=> $r, 1631 @problem_html = renderProblems(
1632 r=> $r,
1263 user => $db->getUser($userToShow), 1633 user => $db->getUser($userToShow),
1264 displayMode=> $default_problem_mode, 1634 displayMode=> $default_problem_mode,
1265 problem_number=> $problemID, 1635 problem_number=> $problemID,
1266 this_set => $db->getMergedSet($userToShow, $setID), 1636 this_set => $this_set,
1267 problem_seed => $forOneUser ? $problemRecord->problem_seed : 0, 1637 problem_seed => $forOneUser ? $problemRecord->problem_seed : 0,
1268 problem_list => [$problemRecord->source_file], 1638 problem_list => [$problemRecord->source_file],
1269 ); 1639 );
1270 } 1640 }
1271 1641
1272 print CGI::Tr({}, CGI::td({}, [ 1642 print CGI::Tr({}, CGI::td({}, [
1273 CGI::start_table({border => 0, cellpadding => 1}) . 1643 CGI::start_table({border => 0, cellpadding => 1}) .
1274 CGI::Tr({}, CGI::td({}, problem_number_popup($problemID, $maxProblemNumber))) . 1644 CGI::Tr({}, CGI::td({}, problem_number_popup($problemID, $maxProblemNumber))) .
1275 CGI::Tr({}, CGI::td({}, CGI::a({href => $editProblemLink}, "Edit it"))) . 1645 CGI::Tr({}, CGI::td({}, CGI::a({href => $editProblemLink, target=>"WW_Editor"}, "Edit it"))) .
1276 CGI::Tr({}, CGI::td({}, CGI::a({href => $viewProblemLink}, "Try it" . ($forOneUser ? " (as $editForUser[0])" : "")))) . 1646 CGI::Tr({}, CGI::td({}, CGI::a({href => $viewProblemLink, target=>"WW_View"}, "Try it" . ($forOneUser ? " (as $editForUser[0])" : "")))) .
1277 ($forUsers ? "" : CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "deleteProblem", value => $problemID, label => "Delete it?"})))) . 1647 ($forUsers ? "" : CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "deleteProblem", value => $problemID, label => "Delete it?"})))) .
1278# CGI::Tr({}, CGI::td({}, "Delete&nbsp;it?" . CGI::input({type => "checkbox", name => "deleteProblem", value => $problemID}))) . 1648# CGI::Tr({}, CGI::td({}, "Delete&nbsp;it?" . CGI::input({type => "checkbox", name => "deleteProblem", value => $problemID}))) .
1279 ($forOneUser ? "" : CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "markCorrect", value => $problemID, label => "Mark Correct?"})))) . 1649 ($forOneUser ? "" : CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "markCorrect", value => $problemID, label => "Mark Correct?"})))) .
1280 CGI::end_table(), 1650 CGI::end_table(),
1281 $self->FieldTable($userToShow, $setID, $problemID, $GlobalProblems{$problemID}, $UserProblems{$problemID}), 1651 $self->FieldTable($userToShow, $setID, $problemID, $GlobalProblems{$problemID}, $UserProblems{$problemID}),
1307 1677
1308 1678
1309# print final lines 1679# print final lines
1310 print CGI::end_table(); 1680 print CGI::end_table();
1311 print CGI::checkbox({ 1681 print CGI::checkbox({
1312 label=> "Force problems to be numbered consecutively from one", 1682 label=> "Force problems to be numbered consecutively from one (always done when reordering problems)",
1313 name=>"force_renumber", value=>"1"}), 1683 name=>"force_renumber", value=>"1"});
1314 CGI::br(),
1315 CGI::checkbox({
1316 label=> "Add blank problem to set",
1317 name=>"add_blank_problem", value=>"1"}),
1318
1319 CGI::br();
1320 print CGI::input({type=>"submit", name=>"submit_changes", value=>"Save Changes"});
1321 print CGI::input({type=>"submit", name=>"handle_numbers", value=>"Reorder problems only"}) . "(Any unsaved changes will be lost.)";
1322 print CGI::p(<<HERE); 1684 print CGI::p(<<EOF);
1323Any time problem numbers are intentionally changed, the problems will 1685Any time problem numbers are intentionally changed, the problems will
1324always be renumbered consecutively, starting from one. When deleting 1686always be renumbered consecutively, starting from one. When deleting
1325problems, gaps will be left in the numbering unless the box above is 1687problems, gaps will be left in the numbering unless the box above is
1326checked. 1688checked.
1327HERE 1689EOF
1328 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()); 1690 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());
1329 print CGI::p("When changing problem numbers, we will move 1691 print CGI::p("When changing problem numbers, we will move the problem to be ". CGI::em("before"). " the chosen number.");
1330 the problem to be ", CGI::em("before"), " the chosen number.");
1331 1692
1332 } else { 1693 } else {
1333 print CGI::p(CGI::b("This set doesn't contain any problems yet.")); 1694 print CGI::p(CGI::b("This set doesn't contain any problems yet."));
1334 } 1695 }
1335 # always allow one to add a new problem. 1696 # always allow one to add a new problem.
1697 print CGI::checkbox({
1698 label=> "Add blank problem template to end of homework set",
1699 name=>"add_blank_problem", value=>"1"}
1700 ),CGI::br(),CGI::br(),
1701 CGI::input({type=>"submit", name=>"submit_changes", value=>"Save Changes"}),
1702 CGI::input({type=>"submit", name=>"handle_numbers", value=>"Reorder problems only"}),
1703 "(Any unsaved changes will be lost.)"
1704 ;
1705
1706
1707
1336 my $editNewProblemPage = $urlpath->new(type => 'instructor_problem_editor_withset_withproblem', args => { courseID => $courseID, setID => $setID, problemID =>'new_problem' }); 1708 #my $editNewProblemPage = $urlpath->new(type => 'instructor_problem_editor_withset_withproblem', args => { courseID => $courseID, setID => $setID, problemID =>'new_problem' });
1337 my $editNewProblemLink = $self->systemLink($editNewProblemPage, params => { make_local_copy => 1, file_type => 'blank_problem' }); 1709 #my $editNewProblemLink = $self->systemLink($editNewProblemPage, params => { make_local_copy => 1, file_type => 'blank_problem' });
1338 1710 # This next feature isn't fully supported and is causing problems. Remove for now. #FIXME
1339 print CGI::p( CGI::a({href=>$editNewProblemLink},'Edit'). 'a new blank problem'); 1711 #print CGI::p( CGI::a({href=>$editNewProblemLink},'Edit'). ' a new blank problem');
1340 1712
1341 print CGI::end_form(); 1713 print CGI::end_form();
1342 1714
1343 return ""; 1715 return "";
1344} 1716}

Legend:
Removed from v.3787  
changed lines
  Added in v.4918

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9