| 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 | |
| 26 | use strict; |
26 | use strict; |
| 27 | use warnings; |
27 | use warnings; |
| 28 | use CGI qw(); |
28 | #use CGI qw(-nosticky ); |
|
|
29 | use WeBWorK::CGI; |
| 29 | use WeBWorK::HTML::ComboBox qw/comboBox/; |
30 | use WeBWorK::HTML::ComboBox qw/comboBox/; |
| 30 | use WeBWorK::Utils qw(readDirectory list2hash listFilesRecursive max); |
31 | use WeBWorK::Utils qw(readDirectory list2hash listFilesRecursive max); |
| 31 | use WeBWorK::DB::Record::Set; |
|
|
| 32 | use WeBWorK::Utils::Tasks qw(renderProblems); |
32 | use WeBWorK::Utils::Tasks qw(renderProblems); |
| 33 | use WeBWorK::Debug; |
33 | use WeBWorK::Debug; |
|
|
34 | # IP RESTRICT |
|
|
35 | use 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 |
| 39 | use 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)]; |
42 | use 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)]; |
| 40 | use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts)]; |
43 | use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts)]; |
| 41 | use constant USER_PROBLEM_FIELDS => [qw(problem_seed status num_correct num_incorrect)]; |
44 | use 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 |
| 44 | use constant HEADER_ORDER => [qw(set_header hardcopy_header)]; |
47 | use 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 |
| 51 | use constant SET_FIELD_ORDER => [qw(open_date due_date answer_date published assignment_type)]; |
58 | use constant SET_FIELD_ORDER => [qw(open_date due_date answer_date published restrict_ip relax_restrict_ip assignment_type)]; |
| 52 | use 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)]; |
|
|
60 | use 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 | |
|
|
82 | use constant BLANKPROBLEM => 'blankProblem.pg'; |
|
|
83 | |
| 70 | use constant FIELD_PROPERTIES => { |
84 | use 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"}, " "), |
402 | CGI::th({colspan=>"2"}, " "), |
| 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) |
| 397 | sub problem_number_popup { |
571 | sub 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." (".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 it?" . CGI::input({type => "checkbox", name => "deleteProblem", value => $problemID}))) . |
1648 | # CGI::Tr({}, CGI::td({}, "Delete 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); |
| 1323 | Any time problem numbers are intentionally changed, the problems will |
1685 | Any time problem numbers are intentionally changed, the problems will |
| 1324 | always be renumbered consecutively, starting from one. When deleting |
1686 | always be renumbered consecutively, starting from one. When deleting |
| 1325 | problems, gaps will be left in the numbering unless the box above is |
1687 | problems, gaps will be left in the numbering unless the box above is |
| 1326 | checked. |
1688 | checked. |
| 1327 | HERE |
1689 | EOF |
| 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 | } |