| 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; |
|
|
34 | # IP RESTRICT |
|
|
35 | use WeBWorK::HTML::ScrollingRecordList qw/scrollingRecordList/; |
| 33 | |
36 | |
| 34 | # Important Note: the following two sets of constants may seem similar |
37 | # Important Note: the following two sets of constants may seem similar |
| 35 | # but they are functionally and semantically different |
38 | # but they are functionally and semantically different |
| 36 | |
39 | |
| 37 | # these constants determine which fields belong to what type of record |
40 | # these constants determine which fields belong to what type of record |
| 38 | use constant SET_FIELDS => [qw(set_header hardcopy_header open_date due_date answer_date published)]; |
41 | # IP RESTRICT |
|
|
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)]; |
| 39 | use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts)]; |
43 | use constant PROBLEM_FIELDS =>[qw(source_file value max_attempts)]; |
| 40 | 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)]; |
| 41 | |
45 | |
| 42 | # these constants determine what order those fields should be displayed in |
46 | # these constants determine what order those fields should be displayed in |
| 43 | use constant HEADER_ORDER => [qw(set_header hardcopy_header)]; |
47 | use constant HEADER_ORDER => [qw(set_header hardcopy_header)]; |
| 44 | use constant PROBLEM_FIELD_ORDER => [qw(problem_seed status value max_attempts attempted last_answer num_correct num_incorrect)]; |
48 | use constant PROBLEM_FIELD_ORDER => [qw(problem_seed status value max_attempts attempted last_answer num_correct num_incorrect)]; |
|
|
49 | |
|
|
50 | # we exclude the gateway set fields from the set field order, because they |
|
|
51 | # are only displayed for sets that are gateways. this results in a bit of |
|
|
52 | # convoluted logic below, but it saves burdening people who are only using |
|
|
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 |
| 45 | use constant SET_FIELD_ORDER => [qw(open_date due_date answer_date published)]; |
58 | use constant SET_FIELD_ORDER => [qw(open_date due_date answer_date published restrict_ip relax_restrict_ip assignment_type)]; |
|
|
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)]; |
| 46 | |
61 | |
| 47 | # 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. |
| 48 | # 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 |
| 49 | # 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() } |
| 50 | # |
65 | # |
| … | |
… | |
| 58 | # 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 |
| 59 | # labels => { # special values can be hashed to display labels |
74 | # labels => { # special values can be hashed to display labels |
| 60 | # 1 => "Yes", |
75 | # 1 => "Yes", |
| 61 | # 0 => "No", |
76 | # 0 => "No", |
| 62 | # }, |
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 | |
| 63 | use constant FIELD_PROPERTIES => { |
84 | use constant FIELD_PROPERTIES => { |
| 64 | # Set information |
85 | # Set information |
| 65 | set_header => { |
86 | set_header => { |
| 66 | name => "Set Header", |
87 | name => "Set Header", |
| 67 | type => "edit", |
88 | type => "edit", |
| … | |
… | |
| 79 | default => "", |
100 | default => "", |
| 80 | }, |
101 | }, |
| 81 | open_date => { |
102 | open_date => { |
| 82 | name => "Opens", |
103 | name => "Opens", |
| 83 | type => "edit", |
104 | type => "edit", |
| 84 | size => "24", |
105 | size => "26", |
| 85 | override => "any", |
106 | override => "any", |
| 86 | labels => { |
107 | labels => { |
| 87 | 0 => "None Specified", |
108 | #0 => "None Specified", |
| 88 | "" => "None Specified", |
109 | "" => "None Specified", |
| 89 | }, |
110 | }, |
| 90 | }, |
111 | }, |
| 91 | due_date => { |
112 | due_date => { |
| 92 | name => "Answers Due", |
113 | name => "Answers Due", |
| 93 | type => "edit", |
114 | type => "edit", |
| 94 | size => "24", |
115 | size => "26", |
| 95 | override => "any", |
116 | override => "any", |
| 96 | labels => { |
117 | labels => { |
| 97 | 0 => "None Specified", |
118 | #0 => "None Specified", |
| 98 | "" => "None Specified", |
119 | "" => "None Specified", |
| 99 | }, |
120 | }, |
| 100 | }, |
121 | }, |
| 101 | answer_date => { |
122 | answer_date => { |
| 102 | name => "Answers Available", |
123 | name => "Answers Available", |
| 103 | type => "edit", |
124 | type => "edit", |
| 104 | size => "24", |
125 | size => "26", |
| 105 | override => "any", |
126 | override => "any", |
| 106 | labels => { |
127 | labels => { |
| 107 | 0 => "None Specified", |
128 | #0 => "None Specified", |
| 108 | "" => "None Specified", |
129 | "" => "None Specified", |
| 109 | }, |
130 | }, |
| 110 | }, |
131 | }, |
| 111 | published => { |
132 | published => { |
| 112 | name => "Visible to Students", |
133 | name => "Visible to Students", |
| … | |
… | |
| 116 | labels => { |
137 | labels => { |
| 117 | 1 => "Yes", |
138 | 1 => "Yes", |
| 118 | 0 => "No", |
139 | 0 => "No", |
| 119 | }, |
140 | }, |
| 120 | }, |
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 | }, |
|
|
166 | assignment_type => { |
|
|
167 | name => "Assignment type", |
|
|
168 | type => "choose", |
|
|
169 | override => "all", |
|
|
170 | choices => [qw( default gateway proctored_gateway )], |
|
|
171 | labels => { default => "homework", |
|
|
172 | gateway => "gateway/quiz", |
|
|
173 | proctored_gateway => "proctored gateway/quiz", |
|
|
174 | }, |
|
|
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 | }, |
|
|
192 | attempts_per_version => { |
|
|
193 | name => "Number of Graded Submissions per Test", |
|
|
194 | type => "edit", |
|
|
195 | size => "3", |
|
|
196 | override => "any", |
|
|
197 | format => '[1-9]\d*', # a non-zero integer |
|
|
198 | # labels => { "" => 1 }, |
|
|
199 | }, |
|
|
200 | time_interval => { |
|
|
201 | name => "Time Interval for New Test Versions (min; 0=infty)", |
|
|
202 | type => "edit", |
|
|
203 | size => "5", |
|
|
204 | override => "any", |
|
|
205 | format => '[0-9]+', # an integer, possibly zero |
|
|
206 | # labels => { "" => 0 }, |
|
|
207 | convertby => 60, |
|
|
208 | }, |
|
|
209 | versions_per_interval => { |
|
|
210 | name => "Number of Tests per Time Interval (0=infty)", |
|
|
211 | type => "edit", |
|
|
212 | size => "3", |
|
|
213 | override => "any", |
|
|
214 | default => "0", |
|
|
215 | format => '[0-9]+', # an integer, possibly zero |
|
|
216 | # labels => { "" => 0 }, |
|
|
217 | # labels => { "" => 1 }, |
|
|
218 | }, |
|
|
219 | problem_randorder => { |
|
|
220 | name => "Order Problems Randomly", |
|
|
221 | type => "choose", |
|
|
222 | choices => [qw( 0 1 )], |
|
|
223 | override => "any", |
|
|
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' }, |
|
|
248 | }, |
| 121 | # Problem information |
249 | # Problem information |
| 122 | source_file => { |
250 | source_file => { |
| 123 | name => "Source File", |
251 | name => "Source File", |
| 124 | type => "edit", |
252 | type => "edit", |
| 125 | size => 50, |
253 | size => 50, |
| … | |
… | |
| 127 | default => "", |
255 | default => "", |
| 128 | }, |
256 | }, |
| 129 | value => { |
257 | value => { |
| 130 | name => "Weight", |
258 | name => "Weight", |
| 131 | type => "edit", |
259 | type => "edit", |
| 132 | size => 5, |
260 | size => 6, |
| 133 | override => "any", |
261 | override => "any", |
| 134 | }, |
262 | }, |
| 135 | max_attempts => { |
263 | max_attempts => { |
| 136 | name => "Max attempts", |
264 | name => "Max attempts", |
| 137 | type => "edit", |
265 | type => "edit", |
| 138 | size => 5, |
266 | size => 6, |
| 139 | override => "any", |
267 | override => "any", |
| 140 | labels => { |
268 | labels => { |
| 141 | "-1" => "unlimited", |
269 | "-1" => "unlimited", |
| 142 | }, |
270 | }, |
| 143 | }, |
271 | }, |
| 144 | problem_seed => { |
272 | problem_seed => { |
| 145 | name => "Seed", |
273 | name => "Seed", |
| 146 | type => "edit", |
274 | type => "edit", |
| 147 | size => 5, |
275 | size => 6, |
| 148 | override => "one", |
276 | override => "one", |
| 149 | |
277 | |
| 150 | }, |
278 | }, |
| 151 | status => { |
279 | status => { |
| 152 | name => "Status", |
280 | name => "Status", |
| 153 | type => "edit", |
281 | type => "edit", |
| 154 | size => 5, |
282 | size => 6, |
| 155 | override => "any", |
283 | override => "one", |
| 156 | default => 0, |
284 | default => 0, |
| 157 | }, |
285 | }, |
| 158 | attempted => { |
286 | attempted => { |
| 159 | name => "Attempted", |
287 | name => "Attempted", |
| 160 | type => "hidden", |
288 | type => "hidden", |
| … | |
… | |
| 187 | |
315 | |
| 188 | # Create a table of fields for the given parameters, one row for each db field |
316 | # Create a table of fields for the given parameters, one row for each db field |
| 189 | # if only the setID is included, it creates a table of set information |
317 | # if only the setID is included, it creates a table of set information |
| 190 | # if the problemID is included, it creates a table of problem information |
318 | # if the problemID is included, it creates a table of problem information |
| 191 | sub FieldTable { |
319 | sub FieldTable { |
| 192 | my ($self, $userID, $setID, $problemID) = @_; |
320 | my ($self, $userID, $setID, $problemID, $globalRecord, $userRecord) = @_; |
| 193 | |
321 | |
| 194 | my $r = $self->r; |
322 | my $r = $self->r; |
| 195 | my @editForUser = $r->param('editForUser'); |
323 | my @editForUser = $r->param('editForUser'); |
| 196 | my $forUsers = scalar(@editForUser); |
324 | my $forUsers = scalar(@editForUser); |
| 197 | my $forOneUser = $forUsers == 1; |
325 | my $forOneUser = $forUsers == 1; |
| 198 | |
326 | |
| 199 | my @fieldOrder; |
327 | my @fieldOrder; |
|
|
328 | |
|
|
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 | |
| 200 | if (defined $problemID) { |
337 | if (defined $problemID) { |
| 201 | @fieldOrder = @{ PROBLEM_FIELD_ORDER() }; |
338 | @fieldOrder = @{ PROBLEM_FIELD_ORDER() }; |
| 202 | } else { |
339 | } else { |
| 203 | @fieldOrder = @{ SET_FIELD_ORDER() }; |
340 | @fieldOrder = @{ SET_FIELD_ORDER() }; |
|
|
341 | |
|
|
342 | # gateway data fields are included only if the set is a gateway |
|
|
343 | if ( $globalRecord->assignment_type() =~ /gateway/ ) { |
|
|
344 | my $gwhdr = "\n<!-- begin gwoutput table -->\n"; |
|
|
345 | my $nF = 0; |
|
|
346 | |
|
|
347 | foreach my $gwfield ( @{ GATEWAY_SET_FIELD_ORDER() } ) { |
|
|
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])); |
|
|
355 | } |
|
|
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); |
|
|
396 | } |
| 204 | } |
397 | } |
| 205 | |
398 | |
| 206 | my $output = CGI::start_table({border => 0, cellpadding => 1}); |
399 | my $output = CGI::start_table({border => 0, cellpadding => 1}); |
|
|
400 | if ($forUsers) { |
|
|
401 | $output .= CGI::Tr({}, |
|
|
402 | CGI::th({colspan=>"2"}, " "), |
|
|
403 | CGI::th({colspan=>"1"}, "User Values"), |
|
|
404 | CGI::th({}, "Class values"), |
|
|
405 | ); |
|
|
406 | } |
| 207 | foreach my $field (@fieldOrder) { |
407 | foreach my $field (@fieldOrder) { |
| 208 | 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 | |
| 209 | unless ($properties{type} eq "hidden") { |
419 | unless ($properties{type} eq "hidden") { |
| 210 | $output .= CGI::Tr({}, CGI::td({}, [$self->FieldHTML($userID, $setID, $problemID, $field)])); |
420 | $output .= CGI::Tr({}, CGI::td({}, [$self->FieldHTML($userID, $setID, $problemID, $globalRecord, $userRecord, $field)])) . "\n"; |
|
|
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 | |
|
|
448 | # this is a rather artifical addition to include gateway fields, which we |
|
|
449 | # only want to show for gateways |
|
|
450 | if ( $field eq 'assignment_type' && $gwoutput ) { |
|
|
451 | $output .= "$gwoutput\n"; |
| 211 | } |
452 | } |
| 212 | } |
453 | } |
| 213 | |
454 | |
| 214 | if (defined $problemID) { |
455 | if (defined $problemID) { |
| 215 | my $problemRecord = $r->{db}->getUserProblem($userID, $setID, $problemID); |
456 | #my $problemRecord = $r->{db}->getUserProblem($userID, $setID, $problemID); |
|
|
457 | my $problemRecord = $userRecord; # we get this from the caller, hopefully |
| 216 | $output .= CGI::Tr({}, CGI::td({}, ["","Attempts", ($problemRecord->num_correct || 0) + ($problemRecord->num_incorrect || 0)])) if $forOneUser; |
458 | $output .= CGI::Tr({}, CGI::td({}, ["","Attempts", ($problemRecord->num_correct || 0) + ($problemRecord->num_incorrect || 0)])) if $forOneUser; |
| 217 | } |
459 | } |
| 218 | $output .= CGI::end_table(); |
460 | $output .= CGI::end_table(); |
| 219 | |
461 | |
| 220 | return $output; |
462 | return $output; |
| … | |
… | |
| 223 | # Returns a list of information and HTML widgets |
465 | # Returns a list of information and HTML widgets |
| 224 | # for viewing and editing the specified db fields |
466 | # for viewing and editing the specified db fields |
| 225 | # if only the setID is included, it creates a list of set information |
467 | # if only the setID is included, it creates a list of set information |
| 226 | # if the problemID is included, it creates a list of problem information |
468 | # if the problemID is included, it creates a list of problem information |
| 227 | sub FieldHTML { |
469 | sub FieldHTML { |
| 228 | my ($self, $userID, $setID, $problemID, $field) = @_; |
470 | my ($self, $userID, $setID, $problemID, $globalRecord, $userRecord, $field) = @_; |
| 229 | |
471 | |
| 230 | my $r = $self->r; |
472 | my $r = $self->r; |
| 231 | my $db = $r->db; |
473 | my $db = $r->db; |
| 232 | my @editForUser = $r->param('editForUser'); |
474 | my @editForUser = $r->param('editForUser'); |
| 233 | my $forUsers = scalar(@editForUser); |
475 | my $forUsers = scalar(@editForUser); |
| 234 | my $forOneUser = $forUsers == 1; |
476 | my $forOneUser = $forUsers == 1; |
| 235 | |
477 | |
| 236 | my ($globalRecord, $userRecord, $mergedRecord); |
478 | #my ($globalRecord, $userRecord, $mergedRecord); |
| 237 | if (defined $problemID) { |
479 | #if (defined $problemID) { |
| 238 | $globalRecord = $db->getGlobalProblem($setID, $problemID); |
480 | # $globalRecord = $db->getGlobalProblem($setID, $problemID); |
| 239 | $userRecord = $db->getUserProblem($userID, $setID, $problemID); |
481 | # $userRecord = $db->getUserProblem($userID, $setID, $problemID); |
| 240 | $mergedRecord = $db->getMergedProblem($userID, $setID, $problemID); |
482 | # #$mergedRecord = $db->getMergedProblem($userID, $setID, $problemID); # never used --sam |
| 241 | } else { |
483 | #} else { |
| 242 | $globalRecord = $db->getGlobalSet($setID); |
484 | # $globalRecord = $db->getGlobalSet($setID); |
| 243 | $userRecord = $db->getUserSet($userID, $setID); |
485 | # $userRecord = $db->getUserSet($userID, $setID); |
| 244 | $mergedRecord = $db->getMergedSet($userID, $setID); |
486 | # #$mergedRecord = $db->getMergedSet($userID, $setID); # never user --sam |
| 245 | } |
487 | #} |
| 246 | |
488 | |
| 247 | return "No data exists for set $setID and problem $problemID" unless $globalRecord; |
489 | return "No data exists for set $setID and problem $problemID" unless $globalRecord; |
| 248 | return "No user specific data exists for user $userID" if $forOneUser and $globalRecord and not $userRecord; |
490 | return "No user specific data exists for user $userID" if $forOneUser and $globalRecord and not $userRecord; |
| 249 | |
491 | |
| 250 | my %properties = %{ FIELD_PROPERTIES()->{$field} }; |
492 | my %properties = %{ FIELD_PROPERTIES()->{$field} }; |
| … | |
… | |
| 256 | |
498 | |
| 257 | my $edit = ($properties{type} eq "edit") && ($properties{override} ne "none"); |
499 | my $edit = ($properties{type} eq "edit") && ($properties{override} ne "none"); |
| 258 | my $choose = ($properties{type} eq "choose") && ($properties{override} ne "none"); |
500 | my $choose = ($properties{type} eq "choose") && ($properties{override} ne "none"); |
| 259 | |
501 | |
| 260 | my $globalValue = $globalRecord->{$field}; |
502 | my $globalValue = $globalRecord->{$field}; |
|
|
503 | # use defined instead of value in order to allow 0 to printed, e.g. for the 'value' field |
| 261 | $globalValue = $globalValue ? ($labels{$globalValue || ""} || $globalValue) : ""; |
504 | $globalValue = (defined($globalValue)) ? ($labels{$globalValue || ""} || $globalValue) : ""; |
| 262 | my $userValue = $userRecord->{$field}; |
505 | my $userValue = $userRecord->{$field}; |
| 263 | $userValue = $userValue ? ($labels{$userValue || ""} || $userValue) : ""; |
506 | $userValue = (defined($userValue)) ? ($labels{$userValue || ""} || $userValue) : ""; |
| 264 | |
507 | |
| 265 | if ($field =~ /_date/) { |
508 | if ($field =~ /_date/) { |
| 266 | $globalValue = $self->formatDateTime($globalValue) if $globalValue; |
509 | $globalValue = $self->formatDateTime($globalValue) if defined $globalValue && $globalValue ne $labels{""}; |
| 267 | $userValue = $self->formatDateTime($userValue) if $userValue; |
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; |
| 268 | } |
516 | } |
| 269 | |
517 | |
| 270 | # check to make sure that a given value can be overridden |
518 | # check to make sure that a given value can be overridden |
| 271 | my %canOverride = map { $_ => 1 } (@{ PROBLEM_FIELDS() }, @{ SET_FIELDS() }); |
519 | my %canOverride = map { $_ => 1 } (@{ PROBLEM_FIELDS() }, @{ SET_FIELDS() }); |
| 272 | my $check = $canOverride{$field}; |
520 | my $check = $canOverride{$field}; |
| … | |
… | |
| 297 | # 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. |
| 298 | $inputType = CGI::popup_menu({ |
546 | $inputType = CGI::popup_menu({ |
| 299 | name => "$recordType.$recordID.$field", |
547 | name => "$recordType.$recordID.$field", |
| 300 | values => $properties{choices}, |
548 | values => $properties{choices}, |
| 301 | labels => \%labels, |
549 | labels => \%labels, |
| 302 | 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), |
| 303 | }); |
551 | }); |
| 304 | } |
552 | } |
| 305 | |
553 | |
|
|
554 | my $gDisplVal = defined($properties{labels}) && defined($properties{labels}->{$globalValue}) ? $properties{labels}->{$globalValue} : $globalValue; |
|
|
555 | |
| 306 | return (($forUsers && $edit && $check) ? CGI::checkbox({ |
556 | # return (($forUsers && $edit && $check) ? CGI::checkbox({ |
|
|
557 | return (($forUsers && $check) ? CGI::checkbox({ |
| 307 | type => "checkbox", |
558 | type => "checkbox", |
| 308 | name => "$recordType.$recordID.$field.override", |
559 | name => "$recordType.$recordID.$field.override", |
| 309 | label => "", |
560 | label => "", |
| 310 | value => $field, |
561 | value => $field, |
| 311 | checked => $r->param("$recordType.$recordID.$field.override") || ($userValue ne "" ? 1 : 0), |
562 | checked => $r->param("$recordType.$recordID.$field.override") || ($userValue ne ($labels{""} || "") ? 1 : 0), |
| 312 | }) : "", |
563 | }) : "", |
| 313 | $properties{name}, |
564 | $properties{name}, |
| 314 | $inputType, |
565 | $inputType, |
| 315 | $forUsers ? " $globalValue" : "", |
566 | $forUsers ? " $gDisplVal" : "", |
| 316 | ); |
567 | ); |
| 317 | } |
568 | } |
| 318 | |
569 | |
| 319 | # 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) |
| 320 | sub problem_number_popup { |
571 | sub problem_number_popup { |
| … | |
… | |
| 334 | my $setID = shift; |
585 | my $setID = shift; |
| 335 | my $force = shift || 0; |
586 | my $force = shift || 0; |
| 336 | my @sortme=(); |
587 | my @sortme=(); |
| 337 | my ($j, $val); |
588 | my ($j, $val); |
| 338 | |
589 | |
|
|
590 | # keys are current problem numbers, values are target problem numbers |
| 339 | foreach $j (keys %newProblemNumbers) { |
591 | foreach $j (keys %newProblemNumbers) { |
| 340 | # 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 |
| 341 | 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? |
| 342 | 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 |
| 343 | $force = 1; |
599 | $force = 1; |
| 344 | $val = 1000 * $newProblemNumbers{$j} - $j; |
600 | $val = 1000 * $newProblemNumbers{$j} - $j; |
| 345 | } else { |
601 | } else { |
| 346 | $val = 1000 * $newProblemNumbers{$j}; |
602 | $val = 1000 * $newProblemNumbers{$j}; |
| 347 | } |
603 | } |
|
|
604 | # store a mapping between current problem number and score (based on currnet and new problem number) |
| 348 | push @sortme, [$j, $val]; |
605 | push @sortme, [$j, $val]; |
|
|
606 | # replace new problem numbers in hash with the (global) problems themselves |
| 349 | $newProblemNumbers{$j} = $db->getGlobalProblem($setID, $j); |
607 | $newProblemNumbers{$j} = $db->getGlobalProblem($setID, $j); |
| 350 | die "global $j for set $setID not found." unless $newProblemNumbers{$j}; |
608 | die "global $j for set $setID not found." unless $newProblemNumbers{$j}; |
| 351 | } |
609 | } |
| 352 | |
610 | |
|
|
611 | # we don't have to do anything if we're not getting rid of holes |
| 353 | return "" unless $force; |
612 | return "" unless $force; |
| 354 | |
613 | |
|
|
614 | # sort the curr. prob. num./score pairs by score |
| 355 | @sortme = sort {$a->[1] <=> $b->[1]} @sortme; |
615 | @sortme = sort {$a->[1] <=> $b->[1]} @sortme; |
| 356 | # 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 |
| 357 | # get all of the problem records |
617 | # get all of the problem records |
| 358 | # assign new problem numbers |
618 | # assign new problem numbers |
| 359 | # loop - if number is new, put the problem record |
619 | # loop - if number is new, put the problem record |
| … | |
… | |
| 361 | |
621 | |
| 362 | |
622 | |
| 363 | # Now, three stages. First global values |
623 | # Now, three stages. First global values |
| 364 | |
624 | |
| 365 | for ($j = 0; $j < scalar @sortme; $j++) { |
625 | for ($j = 0; $j < scalar @sortme; $j++) { |
| 366 | 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) |
| 367 | # do nothing |
628 | # do nothing |
| 368 | } 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 |
| 369 | $newProblemNumbers{$sortme[$j]->[0]}->problem_id($j + 1); |
631 | $newProblemNumbers{$sortme[$j][0]}->problem_id($j + 1); |
| 370 | $db->addGlobalProblem($newProblemNumbers{$sortme[$j]->[0]}); |
632 | $db->addGlobalProblem($newProblemNumbers{$sortme[$j][0]}); |
| 371 | } 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) |
| 372 | $newProblemNumbers{$sortme[$j]->[0]}->problem_id($j + 1); |
635 | $newProblemNumbers{$sortme[$j][0]}->problem_id($j + 1); |
| 373 | $db->putGlobalProblem($newProblemNumbers{$sortme[$j]->[0]}); |
636 | $db->putGlobalProblem($newProblemNumbers{$sortme[$j][0]}); |
| 374 | } |
637 | } |
| 375 | } |
638 | } |
| 376 | |
639 | |
| 377 | my @setUsers = $db->listSetUsers($setID); |
640 | my @setUsers = $db->listSetUsers($setID); |
| 378 | my (@problist, $user); |
641 | my (@problist, $user); |
| 379 | my $globalUserID = $db->{set}->{params}->{globalUserID} || ''; |
|
|
| 380 | |
642 | |
| 381 | foreach $user (@setUsers) { |
643 | foreach $user (@setUsers) { |
| 382 | # 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) |
| 383 | # we can't do it again. This relies on the global user not having |
|
|
| 384 | # a blank name. |
|
|
| 385 | next if $globalUserID eq $user; |
|
|
| 386 | for $j (keys %newProblemNumbers) { |
645 | for $j (keys %newProblemNumbers) { |
| 387 | $problist[$j] = $db->getUserProblem($user, $setID, $j); |
646 | $problist[$j] = $db->getUserProblem($user, $setID, $j); |
| 388 | die " problem $j for set $setID and effective user $user not found" |
|
|
| 389 | unless $problist[$j]; |
|
|
| 390 | } |
647 | } |
| 391 | # ok, now we have all problem data for $user |
|
|
| 392 | for($j = 0; $j < scalar @sortme; $j++) { |
648 | for($j = 0; $j < scalar @sortme; $j++) { |
| 393 | 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 |
| 394 | # 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 |
| 395 | } 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 |
| 396 | $problist[$sortme[$j]->[0]]->problem_id($j + 1); |
659 | $problist[$sortme[$j][0]]->problem_id($j + 1); |
| 397 | $db->addUserProblem($problist[$sortme[$j]->[0]]); |
660 | $db->addUserProblem($problist[$sortme[$j][0]]); |
| 398 | } else { |
661 | } else { |
|
|
662 | # same as above -- there's a problem already there, so overwrite its data with the data from the jth problem |
| 399 | $problist[$sortme[$j]->[0]]->problem_id($j + 1); |
663 | $problist[$sortme[$j][0]]->problem_id($j + 1); |
| 400 | $db->putUserProblem($problist[$sortme[$j]->[0]]); |
664 | $db->putUserProblem($problist[$sortme[$j][0]]); |
| 401 | } |
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 | } |
| 402 | } |
683 | } |
| 403 | } |
684 | } |
| 404 | |
685 | |
| 405 | |
686 | # any problems with IDs above $maxNum get deleted -- presumably their data has been copied into problems with lower IDs |
| 406 | foreach ($j = scalar @sortme; $j < $maxNum; $j++) { |
687 | foreach ($j = scalar @sortme; $j < $maxNum; $j++) { |
| 407 | if (defined $newProblemNumbers{$j + 1}) { |
688 | if (defined $newProblemNumbers{$j + 1}) { |
| 408 | $db->deleteGlobalProblem($setID, $j+1); |
689 | $db->deleteGlobalProblem($setID, $j+1); |
| 409 | } |
690 | } |
| 410 | } |
691 | } |
| 411 | |
692 | |
|
|
693 | # return a string form of the old problem IDs in the new order (not used by caller, incidentally) |
| 412 | return join(', ', map {$_->[0]} @sortme); |
694 | return join(', ', map {$_->[0]} @sortme); |
| 413 | } |
695 | } |
| 414 | |
696 | |
| 415 | # swap index given with next bigger index |
697 | # swap index given with next bigger index |
| 416 | # leftover from when we had up/down buttons |
698 | # leftover from when we had up/down buttons |
| 417 | # maybe we will bring them back |
699 | # maybe we will bring them back |
| 418 | |
700 | |
| 419 | sub moveme { |
701 | #sub moveme { |
| 420 | my $index = shift; |
702 | # my $index = shift; |
| 421 | my $db = shift; |
703 | # my $db = shift; |
| 422 | my $setID = shift; |
704 | # my $setID = shift; |
| 423 | my (@problemIDList) = @_; |
705 | # my (@problemIDList) = @_; |
| 424 | my ($prob1, $prob2, $prob); |
706 | # my ($prob1, $prob2, $prob); |
| 425 | |
707 | # |
| 426 | foreach my $problemID (@problemIDList) { |
708 | # foreach my $problemID (@problemIDList) { |
| 427 | my $problemRecord = $db->getGlobalProblem($setID, $problemID); # checked |
709 | # my $problemRecord = $db->getGlobalProblem($setID, $problemID); # checked |
| 428 | die "global $problemID for set $setID not found." unless $problemRecord; |
710 | # die "global $problemID for set $setID not found." unless $problemRecord; |
| 429 | if ($problemRecord->problem_id == $index) { |
711 | # if ($problemRecord->problem_id == $index) { |
| 430 | $prob1 = $problemRecord; |
712 | # $prob1 = $problemRecord; |
| 431 | } elsif ($problemRecord->problem_id == $index + 1) { |
713 | # } elsif ($problemRecord->problem_id == $index + 1) { |
| 432 | $prob2 = $problemRecord; |
714 | # $prob2 = $problemRecord; |
| 433 | } |
715 | # } |
| 434 | } |
716 | # } |
| 435 | if (not defined $prob1 or not defined $prob2) { |
717 | # if (not defined $prob1 or not defined $prob2) { |
| 436 | die "cannot find problem $index or " . ($index + 1); |
718 | # die "cannot find problem $index or " . ($index + 1); |
| 437 | } |
719 | # } |
| 438 | |
720 | # |
| 439 | $prob1->problem_id($index + 1); |
721 | # $prob1->problem_id($index + 1); |
| 440 | $prob2->problem_id($index); |
722 | # $prob2->problem_id($index); |
| 441 | $db->putGlobalProblem($prob1); |
723 | # $db->putGlobalProblem($prob1); |
| 442 | $db->putGlobalProblem($prob2); |
724 | # $db->putGlobalProblem($prob2); |
| 443 | |
725 | # |
| 444 | my @setUsers = $db->listSetUsers($setID); |
726 | # my @setUsers = $db->listSetUsers($setID); |
| 445 | |
727 | # |
| 446 | my $user; |
728 | # my $user; |
| 447 | foreach $user (@setUsers) { |
729 | # foreach $user (@setUsers) { |
| 448 | $prob1 = $db->getUserProblem($user, $setID, $index); #checked |
730 | # $prob1 = $db->getUserProblem($user, $setID, $index); #checked |
| 449 | die " problem $index for set $setID and effective user $user not found" |
731 | # die " problem $index for set $setID and effective user $user not found" |
| 450 | unless $prob1; |
732 | # unless $prob1; |
| 451 | $prob2 = $db->getUserProblem($user, $setID, $index+1); #checked |
733 | # $prob2 = $db->getUserProblem($user, $setID, $index+1); #checked |
| 452 | die " problem $index for set $setID and effective user $user not found" |
734 | # die " problem $index for set $setID and effective user $user not found" |
| 453 | unless $prob2; |
735 | # unless $prob2; |
| 454 | $prob1->problem_id($index+1); |
736 | # $prob1->problem_id($index+1); |
| 455 | $prob2->problem_id($index); |
737 | # $prob2->problem_id($index); |
| 456 | $db->putUserProblem($prob1); |
738 | # $db->putUserProblem($prob1); |
| 457 | $db->putUserProblem($prob2); |
739 | # $db->putUserProblem($prob2); |
| 458 | } |
740 | # } |
| 459 | } |
741 | #} |
| 460 | |
742 | |
| 461 | # primarily saves any changes into the correct set or problem records (global vs user) |
743 | # primarily saves any changes into the correct set or problem records (global vs user) |
| 462 | # also deals with deleting or rearranging problems |
744 | # also deals with deleting or rearranging problems |
| 463 | sub initialize { |
745 | sub initialize { |
| 464 | my ($self) = @_; |
746 | my ($self) = @_; |
| … | |
… | |
| 498 | my @values = $r->param("set.$setID.$_"); |
780 | my @values = $r->param("set.$setID.$_"); |
| 499 | my $value = $values[0] || $values[1] || ""; |
781 | my $value = $values[0] || $values[1] || ""; |
| 500 | $r->param("set.$setID.$_", $value); |
782 | $r->param("set.$setID.$_", $value); |
| 501 | } |
783 | } |
| 502 | |
784 | |
|
|
785 | ##################################################################### |
|
|
786 | # Check date information |
|
|
787 | ##################################################################### |
|
|
788 | |
|
|
789 | my ($open_date, $due_date, $answer_date); |
|
|
790 | my $error = 0; |
| 503 | if (defined $r->param('submit_changes')) { |
791 | if (defined $r->param('submit_changes')) { |
|
|
792 | my @names = ("open_date", "due_date", "answer_date"); |
|
|
793 | |
|
|
794 | my %dates = map { $_ => $r->param("set.$setID.$_") } @names; |
|
|
795 | %dates = map { |
|
|
796 | my $unlabel = $undoLabels{$_}->{$dates{$_}}; |
|
|
797 | $_ => defined $unlabel ? $setRecord->$_ : $self->parseDateTime($dates{$_}) |
|
|
798 | } @names; |
|
|
799 | |
|
|
800 | ($open_date, $due_date, $answer_date) = map { $dates{$_} } @names; |
|
|
801 | |
|
|
802 | if ($answer_date < $due_date || $answer_date < $open_date) { |
|
|
803 | $self->addbadmessage("Answers cannot be made available until on or after the due date!"); |
|
|
804 | $error = $r->param('submit_changes'); |
|
|
805 | } |
|
|
806 | |
|
|
807 | if ($due_date < $open_date) { |
|
|
808 | $self->addbadmessage("Answers cannot be due until on or after the open date!"); |
|
|
809 | $error = $r->param('submit_changes'); |
|
|
810 | } |
|
|
811 | |
|
|
812 | # make sure the dates are not more than 10 years in the future |
|
|
813 | my $curr_time = time; |
|
|
814 | my $seconds_per_year = 31_556_926; |
|
|
815 | my $cutoff = $curr_time + $seconds_per_year*10; |
|
|
816 | if ($open_date > $cutoff) { |
|
|
817 | $self->addbadmessage("Error: open date cannot be more than 10 years from now in set $setID"); |
|
|
818 | $error = $r->param('submit_changes'); |
|
|
819 | } |
|
|
820 | if ($due_date > $cutoff) { |
|
|
821 | $self->addbadmessage("Error: due date cannot be more than 10 years from now in set $setID"); |
|
|
822 | $error = $r->param('submit_changes'); |
|
|
823 | } |
|
|
824 | if ($answer_date > $cutoff) { |
|
|
825 | $self->addbadmessage("Error: answer date cannot be more than 10 years from now in set $setID"); |
|
|
826 | $error = $r->param('submit_changes'); |
|
|
827 | } |
|
|
828 | } |
|
|
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 | |
|
|
855 | if ($error) { |
|
|
856 | $self->addbadmessage("No changes were saved!"); |
|
|
857 | } |
| 504 | |
858 | |
| 505 | my $setRecord = $db->getGlobalSet($setID); |
859 | if (defined $r->param('submit_changes') && !$error) { |
|
|
860 | |
|
|
861 | #my $setRecord = $db->getGlobalSet($setID); # already fetched above --sam |
| 506 | |
862 | |
| 507 | ##################################################################### |
863 | ##################################################################### |
| 508 | # Save general set information (including headers) |
864 | # Save general set information (including headers) |
| 509 | ##################################################################### |
865 | ##################################################################### |
| 510 | |
866 | |
| 511 | if ($forUsers) { |
867 | if ($forUsers) { |
|
|
868 | # DBFIXME use a WHERE clause, iterator |
| 512 | my @userRecords = $db->getUserSets(map { [$_, $setID] } @editForUser); |
869 | my @userRecords = $db->getUserSets(map { [$_, $setID] } @editForUser); |
| 513 | foreach my $record (@userRecords) { |
870 | foreach my $record (@userRecords) { |
| 514 | foreach my $field ( @{ SET_FIELDS() } ) { |
871 | foreach my $field ( @{ SET_FIELDS() } ) { |
| 515 | next unless canChange($forUsers, $field); |
872 | next unless canChange($forUsers, $field); |
| 516 | |
|
|
| 517 | my $override = $r->param("set.$setID.$field.override"); |
873 | my $override = $r->param("set.$setID.$field.override"); |
|
|
874 | |
| 518 | if (defined $override && $override eq $field) { |
875 | if (defined $override && $override eq $field) { |
| 519 | |
876 | |
| 520 | my $param = $r->param("set.$setID.$field"); |
877 | my $param = $r->param("set.$setID.$field"); |
| 521 | $param = $properties{$field}->{default} || "" unless defined $param && $param ne ""; |
878 | $param = $properties{$field}->{default} || "" unless defined $param && $param ne ""; |
|
|
879 | my $unlabel = $undoLabels{$field}->{$param}; |
|
|
880 | $param = $unlabel if defined $unlabel; |
| 522 | $param = $undoLabels{$field}->{$param} || $param; |
881 | # $param = $undoLabels{$field}->{$param} || $param; |
| 523 | if ($field =~ /_date/) { |
882 | if ($field =~ /_date/) { |
| 524 | $param = $self->parseDateTime($param); |
883 | $param = $self->parseDateTime($param) unless defined $unlabel; |
|
|
884 | } |
|
|
885 | if (defined($properties{$field}->{convertby}) && $properties{$field}->{convertby}) { |
|
|
886 | $param = $param*$properties{$field}->{convertby}; |
| 525 | } |
887 | } |
| 526 | $record->$field($param); |
888 | $record->$field($param); |
| 527 | } else { |
889 | } else { |
| 528 | $record->$field(undef); |
890 | $record->$field(undef); |
| 529 | } |
891 | } |
|
|
892 | |
| 530 | } |
893 | } |
| 531 | $db->putUserSet($record); |
894 | $db->putUserSet($record); |
| 532 | } |
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 | } |
| 533 | } else { |
940 | } else { |
| 534 | |
|
|
| 535 | foreach my $field ( @{ SET_FIELDS() } ) { |
941 | foreach my $field ( @{ SET_FIELDS() } ) { |
| 536 | next unless canChange($forUsers, $field); |
942 | next unless canChange($forUsers, $field); |
| 537 | |
943 | |
| 538 | my $param = $r->param("set.$setID.$field"); |
944 | my $param = $r->param("set.$setID.$field"); |
| 539 | $param = $properties{$field}->{default} || "" unless defined $param && $param ne ""; |
945 | $param = $properties{$field}->{default} || "" unless defined $param && $param ne ""; |
|
|
946 | |
| 540 | $param = $undoLabels{$field}->{$param} || $param; |
947 | my $unlabel = $undoLabels{$field}->{$param}; |
|
|
948 | $param = $unlabel if defined $unlabel; |
| 541 | if ($field =~ /_date/) { |
949 | if ($field =~ /_date/) { |
| 542 | $param = $self->parseDateTime($param); |
950 | $param = $self->parseDateTime($param) unless defined $unlabel; |
|
|
951 | } |
|
|
952 | if (defined($properties{$field}->{convertby}) && $properties{$field}->{convertby}) { |
|
|
953 | $param = $param*$properties{$field}->{convertby}; |
| 543 | } |
954 | } |
| 544 | $setRecord->$field($param); |
955 | $setRecord->$field($param); |
| 545 | } |
956 | } |
| 546 | $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 | } |
| 547 | } |
991 | } |
| 548 | |
992 | } |
| 549 | |
993 | |
| 550 | ##################################################################### |
994 | ##################################################################### |
| 551 | # Save problem information |
995 | # Save problem information |
| 552 | ##################################################################### |
996 | ##################################################################### |
| 553 | |
997 | |
|
|
998 | # DBFIXME use a WHERE clause, iterator? |
| 554 | my @problemIDs = $db->listGlobalProblems($setID); |
999 | my @problemIDs = sort { $a <=> $b } $db->listGlobalProblems($setID);; |
| 555 | my @problemRecords = $db->getGlobalProblems(map { [$setID, $_] } @problemIDs); |
1000 | my @problemRecords = $db->getGlobalProblems(map { [$setID, $_] } @problemIDs); |
| 556 | foreach my $problemRecord (@problemRecords) { |
1001 | foreach my $problemRecord (@problemRecords) { |
| 557 | my $problemID = $problemRecord->problem_id; |
1002 | my $problemID = $problemRecord->problem_id; |
| 558 | die "Global problem $problemID for set $setID not found." unless $problemRecord; |
1003 | die "Global problem $problemID for set $setID not found." unless $problemRecord; |
| 559 | |
1004 | |
| 560 | if ($forUsers) { |
1005 | if ($forUsers) { |
| 561 | |
|
|
| 562 | # Since we're editing for specific users, we don't allow the GlobalProblem record to be altered on that same page |
1006 | # Since we're editing for specific users, we don't allow the GlobalProblem record to be altered on that same page |
| 563 | # 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 |
| 564 | # 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. |
| 565 | |
1009 | |
| 566 | my @userIDs = @editForUser; |
1010 | my @userIDs = @editForUser; |
| 567 | my @userProblemIDs = map { [$_, $setID, $problemID] } @userIDs; |
1011 | my @userProblemIDs = map { [$_, $setID, $problemID] } @userIDs; |
|
|
1012 | # DBFIXME where clause? iterator? |
| 568 | my @userProblemRecords = $db->getUserProblems(@userProblemIDs); |
1013 | my @userProblemRecords = $db->getUserProblems(@userProblemIDs); |
| 569 | foreach my $record (@userProblemRecords) { |
1014 | foreach my $record (@userProblemRecords) { |
| 570 | |
1015 | |
| 571 | 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 |
| 572 | foreach my $field ( @{ PROBLEM_FIELDS() } ) { |
1017 | foreach my $field ( @{ PROBLEM_FIELDS() } ) { |
| … | |
… | |
| 575 | my $override = $r->param("problem.$problemID.$field.override"); |
1020 | my $override = $r->param("problem.$problemID.$field.override"); |
| 576 | if (defined $override && $override eq $field) { |
1021 | if (defined $override && $override eq $field) { |
| 577 | |
1022 | |
| 578 | my $param = $r->param("problem.$problemID.$field"); |
1023 | my $param = $r->param("problem.$problemID.$field"); |
| 579 | $param = $properties{$field}->{default} || "" unless defined $param && $param ne ""; |
1024 | $param = $properties{$field}->{default} || "" unless defined $param && $param ne ""; |
| 580 | $param = $undoLabels{$field}->{$param} || $param; |
1025 | my $unlabel = $undoLabels{$field}->{$param}; |
|
|
1026 | $param = $unlabel if defined $unlabel; |
| 581 | $changed ||= changed($record->$field, $param); |
1027 | $changed ||= changed($record->$field, $param); |
| 582 | $record->$field($param); |
1028 | $record->$field($param); |
| 583 | } else { |
1029 | } else { |
| 584 | $changed ||= changed($record->$field, undef); |
1030 | $changed ||= changed($record->$field, undef); |
| 585 | $record->$field(undef); |
1031 | $record->$field(undef); |
| … | |
… | |
| 590 | foreach my $field ( @{ USER_PROBLEM_FIELDS() } ) { |
1036 | foreach my $field ( @{ USER_PROBLEM_FIELDS() } ) { |
| 591 | next unless canChange($forUsers, $field); |
1037 | next unless canChange($forUsers, $field); |
| 592 | |
1038 | |
| 593 | my $param = $r->param("problem.$problemID.$field"); |
1039 | my $param = $r->param("problem.$problemID.$field"); |
| 594 | $param = $properties{$field}->{default} || "" unless defined $param && $param ne ""; |
1040 | $param = $properties{$field}->{default} || "" unless defined $param && $param ne ""; |
| 595 | $param = $undoLabels{$field}->{$param} || $param; |
1041 | my $unlabel = $undoLabels{$field}->{$param}; |
|
|
1042 | $param = $unlabel if defined $unlabel; |
| 596 | $changed ||= changed($record->$field, $param); |
1043 | $changed ||= changed($record->$field, $param); |
| 597 | $record->$field($param); |
1044 | $record->$field($param); |
| 598 | } |
1045 | } |
| 599 | $db->putUserProblem($record) if $changed; |
1046 | $db->putUserProblem($record) if $changed; |
| 600 | } |
1047 | } |
| 601 | } else { |
1048 | } else { |
| 602 | |
|
|
| 603 | # Since we're editing for ALL set users, we will make changes to the GlobalProblem record. |
1049 | # Since we're editing for ALL set users, we will make changes to the GlobalProblem record. |
| 604 | # We may also have instances where a field is unique to the UserProblem record but we want |
1050 | # We may also have instances where a field is unique to the UserProblem record but we want |
| 605 | # all users to (at least initially) have the same value |
1051 | # all users to (at least initially) have the same value |
| 606 | |
1052 | |
| 607 | # this only edits a globalProblem record |
1053 | # this only edits a globalProblem record |
| … | |
… | |
| 609 | foreach my $field ( @{ PROBLEM_FIELDS() } ) { |
1055 | foreach my $field ( @{ PROBLEM_FIELDS() } ) { |
| 610 | next unless canChange($forUsers, $field); |
1056 | next unless canChange($forUsers, $field); |
| 611 | |
1057 | |
| 612 | my $param = $r->param("problem.$problemID.$field"); |
1058 | my $param = $r->param("problem.$problemID.$field"); |
| 613 | $param = $properties{$field}->{default} || "" unless defined $param && $param ne ""; |
1059 | $param = $properties{$field}->{default} || "" unless defined $param && $param ne ""; |
| 614 | $param = $undoLabels{$field}->{$param} || $param; |
1060 | my $unlabel = $undoLabels{$field}->{$param}; |
|
|
1061 | $param = $unlabel if defined $unlabel; |
| 615 | $changed ||= changed($problemRecord->$field, $param); |
1062 | $changed ||= changed($problemRecord->$field, $param); |
| 616 | $problemRecord->$field($param); |
1063 | $problemRecord->$field($param); |
| 617 | } |
1064 | } |
| 618 | $db->putGlobalProblem($problemRecord) if $changed; |
1065 | $db->putGlobalProblem($problemRecord) if $changed; |
| 619 | |
1066 | |
| … | |
… | |
| 631 | my $param = $r->param("problem.$problemID.$field"); |
1078 | my $param = $r->param("problem.$problemID.$field"); |
| 632 | $useful{$field} = 1 if defined $param and $param ne ""; |
1079 | $useful{$field} = 1 if defined $param and $param ne ""; |
| 633 | } |
1080 | } |
| 634 | |
1081 | |
| 635 | if (keys %useful) { |
1082 | if (keys %useful) { |
|
|
1083 | # DBFIXME where clause, iterator |
| 636 | my @userIDs = $db->listProblemUsers($setID, $problemID); |
1084 | my @userIDs = $db->listProblemUsers($setID, $problemID); |
| 637 | my @userProblemIDs = map { [$_, $setID, $problemID] } @userIDs; |
1085 | my @userProblemIDs = map { [$_, $setID, $problemID] } @userIDs; |
| 638 | my @userProblemRecords = $db->getUserProblems(@userProblemIDs); |
1086 | my @userProblemRecords = $db->getUserProblems(@userProblemIDs); |
| 639 | foreach my $record (@userProblemRecords) { |
1087 | foreach my $record (@userProblemRecords) { |
| 640 | my $copy = $record; |
|
|
| 641 | 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 |
| 642 | foreach my $field ( @{ USER_PROBLEM_FIELDS() } ) { |
1089 | foreach my $field ( keys %useful ) { |
| 643 | next unless canChange($forUsers, $field); |
1090 | next unless canChange($forUsers, $field); |
| 644 | next unless $useful{$field}; |
1091 | |
| 645 | |
|
|
| 646 | my $param = $r->param("problem.$problemID.$field"); |
1092 | my $param = $r->param("problem.$problemID.$field"); |
| 647 | $param = $properties{$field}->{default} || "" unless defined $param && $param ne ""; |
1093 | $param = $properties{$field}->{default} || "" unless defined $param && $param ne ""; |
| 648 | $param = $undoLabels{$field}->{$param} || $param; |
1094 | my $unlabel = $undoLabels{$field}->{$param}; |
|
|
1095 | $param = $unlabel if defined $unlabel; |
| 649 | $changed ||= changed($record->$field, $param); |
1096 | $changed ||= changed($record->$field, $param); |
| 650 | $record->$field($param); |
1097 | $record->$field($param); |
| 651 | } |
1098 | } |
| 652 | $db->putUserProblem($record) if $changed; |
1099 | $db->putUserProblem($record) if $changed; |
| 653 | } |
1100 | } |
| 654 | } |
1101 | } |
| 655 | } |
1102 | } |
| 656 | } |
1103 | } |
| 657 | |
1104 | |
|
|
1105 | # Mark the specified problems as correct for all users |
|
|
1106 | foreach my $problemID ($r->param('markCorrect')) { |
|
|
1107 | # DBFIXME where clause, iterator |
|
|
1108 | my @userProblemIDs = map { [$_, $setID, $problemID] } ($forUsers ? @editForUser : $db->listProblemUsers($setID, $problemID)); |
|
|
1109 | my @userProblemRecords = $db->getUserProblems(@userProblemIDs); |
|
|
1110 | foreach my $record (@userProblemRecords) { |
|
|
1111 | if (defined $record && ($record->status eq "" || $record->status < 1)) { |
|
|
1112 | $record->status(1); |
|
|
1113 | $record->attempted(1); |
|
|
1114 | $db->putUserProblem($record); |
|
|
1115 | } |
|
|
1116 | } |
|
|
1117 | } |
|
|
1118 | |
| 658 | # Delete all problems marked for deletion |
1119 | # Delete all problems marked for deletion |
| 659 | foreach my $problemID ($r->param('deleteProblem')) { |
1120 | foreach my $problemID ($r->param('deleteProblem')) { |
| 660 | $db->deleteGlobalProblem($setID, $problemID); |
1121 | $db->deleteGlobalProblem($setID, $problemID); |
|
|
1122 | } |
|
|
1123 | |
|
|
1124 | ##################################################################### |
|
|
1125 | # Add blank problem if needed |
|
|
1126 | ##################################################################### |
|
|
1127 | if (defined($r->param("add_blank_problem") ) and $r->param("add_blank_problem") == 1) { |
|
|
1128 | my $targetProblemNumber = 1+ WeBWorK::Utils::max( $self->r->db->listGlobalProblems($setID)); |
|
|
1129 | ################################################## |
|
|
1130 | # make local copy of the blankProblem |
|
|
1131 | ################################################## |
|
|
1132 | my $blank_file_path = $ce->{webworkFiles}->{screenSnippets}->{blankProblem}; |
|
|
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 | |
|
|
1141 | ################################################# |
|
|
1142 | # Update problem record |
|
|
1143 | ################################################# |
|
|
1144 | my $problemRecord = $self->addProblemToSet( |
|
|
1145 | setName => $setID, |
|
|
1146 | sourceFile => $new_file_path, |
|
|
1147 | problemID => $targetProblemNumber, #added to end of set |
|
|
1148 | ); |
|
|
1149 | $self->assignProblemToAllSetUsers($problemRecord); |
|
|
1150 | $self->addgoodmessage("Added $new_file_path to ". $setID. " as problem $targetProblemNumber") ; |
| 661 | } |
1151 | } |
| 662 | |
1152 | |
| 663 | # 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. |
| 664 | foreach my $header ($r->param('defaultHeader')) { |
1154 | foreach my $header ($r->param('defaultHeader')) { |
| 665 | $setRecord->$header(""); |
1155 | $setRecord->$header(""); |
| 666 | } |
1156 | } |
| 667 | } elsif (defined $r->param('undo_changes')) { |
|
|
| 668 | |
|
|
| 669 | # reset all the parameters dealing with set/problem/header information |
|
|
| 670 | # if the current naming scheme is changed/broken, this could reek havoc |
|
|
| 671 | # on all kinds of things |
|
|
| 672 | foreach my $param ($r->param) { |
|
|
| 673 | $r->param($param, "") if $param =~ /^(set|problem|header)\./; |
|
|
| 674 | } |
|
|
| 675 | } |
1157 | } |
| 676 | |
1158 | |
| 677 | # Leftover code from when there were up/down buttons |
1159 | # Leftover code from when there were up/down buttons |
| 678 | |
1160 | |
| 679 | # } else { |
1161 | # } else { |
| 680 | # # Look for up and down buttons |
1162 | # # Look for up and down buttons |
| … | |
… | |
| 693 | # } |
1175 | # } |
| 694 | # $index++; |
1176 | # $index++; |
| 695 | # } |
1177 | # } |
| 696 | # } |
1178 | # } |
| 697 | |
1179 | |
| 698 | |
|
|
| 699 | |
1180 | |
| 700 | # handle renumbering of problems if necessary |
1181 | # This erases any sticky fields if the user saves changes, resets the form, or reorders problems |
| 701 | print CGI::a({name=>"problems"}); |
1182 | # It may not be obvious why this is necessary when saving changes or reordering problems |
| 702 | |
1183 | # but when the problems are reorder the param problem.1.source_file needs to be the source |
| 703 | my %newProblemNumbers = (); |
1184 | # file of the problem that is NOW #1 and not the problem that WAS #1. |
| 704 | my $maxProblemNumber = -1; |
1185 | unless (defined $r->param('refresh')) { |
| 705 | for my $jj ($db->listGlobalProblems($setID)) { |
1186 | |
| 706 | $newProblemNumbers{$jj} = $r->param('problem_num_' . $jj); |
1187 | # reset all the parameters dealing with set/problem/header information |
| 707 | $maxProblemNumber = $jj if $jj > $maxProblemNumber; |
1188 | # if the current naming scheme is changed/broken, this could reek havoc |
|
|
1189 | # on all kinds of things |
|
|
1190 | foreach my $param ($r->param) { |
|
|
1191 | $r->param($param, "") if $param =~ /^(set|problem|header)\./ && $param !~ /displaymode/; |
| 708 | } |
1192 | } |
| 709 | |
1193 | } |
| 710 | my $forceRenumber = $r->param('force_renumber') || 0; |
|
|
| 711 | handle_problem_numbers(\%newProblemNumbers, $maxProblemNumber, $db, $setID, $forceRenumber); |
|
|
| 712 | $self->{maxProblemNumber} = $maxProblemNumber; |
|
|
| 713 | } |
1194 | } |
| 714 | |
1195 | |
| 715 | # helper method for debugging |
1196 | # helper method for debugging |
| 716 | sub debug ($) { |
1197 | sub definedness ($) { |
| 717 | my ($variable) = @_; |
1198 | my ($variable) = @_; |
| 718 | |
1199 | |
| 719 | return "undefined" unless defined $variable; |
1200 | return "undefined" unless defined $variable; |
| 720 | return "empty" unless $variable ne ""; |
1201 | return "empty" unless $variable ne ""; |
| 721 | return $variable; |
1202 | return $variable; |
| … | |
… | |
| 770 | return $text . "is a directory!" if -d $file; |
1251 | return $text . "is a directory!" if -d $file; |
| 771 | return $text . "does not exist!" unless -e $file; |
1252 | return $text . "does not exist!" unless -e $file; |
| 772 | return $text . "is not a plain file!"; |
1253 | return $text . "is not a plain file!"; |
| 773 | } |
1254 | } |
| 774 | |
1255 | |
|
|
1256 | # don't show view options -- we provide display mode controls for headers/problems separately |
|
|
1257 | sub options { |
|
|
1258 | return ""; |
|
|
1259 | } |
|
|
1260 | |
| 775 | # Creates two separate tables, first of the headers, and the of the problems in a given set |
1261 | # Creates two separate tables, first of the headers, and the of the problems in a given set |
| 776 | # If one or more users are specified in the "editForUser" param, only the data for those users |
1262 | # If one or more users are specified in the "editForUser" param, only the data for those users |
| 777 | # becomes editable, not all the data |
1263 | # becomes editable, not all the data |
| 778 | sub body { |
1264 | sub body { |
| 779 | |
1265 | |
| … | |
… | |
| 782 | my $db = $r->db; |
1268 | my $db = $r->db; |
| 783 | my $ce = $r->ce; |
1269 | my $ce = $r->ce; |
| 784 | my $authz = $r->authz; |
1270 | my $authz = $r->authz; |
| 785 | my $userID = $r->param('user'); |
1271 | my $userID = $r->param('user'); |
| 786 | my $urlpath = $r->urlpath; |
1272 | my $urlpath = $r->urlpath; |
| 787 | my $courseID = $urlpath->arg("courseID"); |
1273 | my $courseID = $urlpath->arg("courseID"); |
| 788 | my $setID = $urlpath->arg("setID"); |
1274 | my $setID = $urlpath->arg("setID"); |
| 789 | my $setRecord = $db->getGlobalSet($setID); |
1275 | my $setRecord = $db->getGlobalSet($setID) or die "No record for global set $setID."; |
| 790 | die "Global set $setID not found." unless $setRecord; |
1276 | |
|
|
1277 | my $userRecord = $db->getUser($userID) or die "No record for user $userID."; |
|
|
1278 | # Check permissions |
|
|
1279 | return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.") |
|
|
1280 | unless $authz->hasPermissions($userRecord->user_id, "access_instructor_tools"); |
|
|
1281 | |
|
|
1282 | return CGI::div({class=>"ResultsWithError"}, "You are not authorized to modify problems.") |
|
|
1283 | unless $authz->hasPermissions($userRecord->user_id, "modify_problem_sets"); |
|
|
1284 | |
| 791 | my @editForUser = $r->param('editForUser'); |
1285 | my @editForUser = $r->param('editForUser'); |
| 792 | |
1286 | |
|
|
1287 | # Check that every user that we're editing for has a valid UserSet |
|
|
1288 | my @assignedUsers; |
|
|
1289 | my @unassignedUsers; |
|
|
1290 | if (scalar @editForUser) { |
|
|
1291 | foreach my $ID (@editForUser) { |
|
|
1292 | # DBFIXME iterator |
|
|
1293 | if ($db->getUserSet($ID, $setID)) { |
|
|
1294 | unshift @assignedUsers, $ID; |
|
|
1295 | } else { |
|
|
1296 | unshift @unassignedUsers, $ID; |
|
|
1297 | } |
|
|
1298 | } |
|
|
1299 | @editForUser = sort @assignedUsers; |
|
|
1300 | $r->param("editForUser", \@editForUser); |
|
|
1301 | |
|
|
1302 | if (scalar @editForUser && scalar @unassignedUsers) { |
|
|
1303 | print CGI::div({class=>"ResultsWithError"}, "The following users are NOT assigned to this set and will be ignored: " . CGI::b(join(", ", @unassignedUsers))); |
|
|
1304 | } elsif (scalar @editForUser == 0) { |
|
|
1305 | print CGI::div({class=>"ResultsWithError"}, "None of the selected users are assigned to this set: " . CGI::b(join(", ", @unassignedUsers))); |
|
|
1306 | print CGI::div({class=>"ResultsWithError"}, "Global set data will be shown instead of user specific data"); |
|
|
1307 | } |
|
|
1308 | } |
|
|
1309 | |
| 793 | # some useful booleans |
1310 | # some useful booleans |
| 794 | my $forUsers = scalar(@editForUser); |
1311 | my $forUsers = scalar(@editForUser); |
| 795 | my $forOneUser = $forUsers == 1; |
1312 | my $forOneUser = $forUsers == 1; |
| 796 | |
1313 | |
| 797 | # If you're editing for users, initially they're records will be different but |
1314 | # If you're editing for users, initially their records will be different but |
| 798 | # 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. |
| 799 | # 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 |
| 800 | my $userToShow = $forUsers ? $editForUser[0] : $userID; |
1317 | my $userToShow = $forUsers ? $editForUser[0] : $userID; |
| 801 | |
|
|
| 802 | # Check permissions |
|
|
| 803 | return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.") |
|
|
| 804 | unless $authz->hasPermissions($r->param("user"), "access_instructor_tools"); |
|
|
| 805 | |
1318 | |
| 806 | return CGI::div({class=>"ResultsWithError"}, "You are not authorized to modify problems.") |
1319 | # DBFIXME no need to get ID lists -- counts would be fine |
| 807 | unless $authz->hasPermissions($r->param("user"), "modify_problem_sets"); |
|
|
| 808 | |
|
|
| 809 | |
|
|
| 810 | |
|
|
| 811 | |
|
|
| 812 | my $userCount = $db->listUsers(); |
1320 | my $userCount = $db->listUsers(); |
| 813 | my $setCount = $db->listGlobalSets() if $forOneUser; |
1321 | my $setCount = $db->listGlobalSets(); # if $forOneUser; |
| 814 | my $setUserCount = $db->countSetUsers($setID); |
1322 | my $setUserCount = $db->countSetUsers($setID); |
| 815 | my $userSetCount = $db->countUserSets($editForUser[0]) if $forOneUser; |
1323 | my $userSetCount = $db->countUserSets($editForUser[0]) if $forOneUser; |
|
|
1324 | |
|
|
1325 | |
| 816 | my $editUsersAssignedToSetURL = $self->systemLink( |
1326 | my $editUsersAssignedToSetURL = $self->systemLink( |
| 817 | $urlpath->newFromModule( |
1327 | $urlpath->newFromModule( |
| 818 | "WeBWorK::ContentGenerator::Instructor::UsersAssignedToSet", |
1328 | "WeBWorK::ContentGenerator::Instructor::UsersAssignedToSet", |
| 819 | courseID => $courseID, setID => $setID)); |
1329 | courseID => $courseID, setID => $setID)); |
| 820 | my $editSetsAssignedToUserURL = $self->systemLink( |
1330 | my $editSetsAssignedToUserURL = $self->systemLink( |
| 821 | $urlpath->newFromModule( |
1331 | $urlpath->newFromModule( |
| 822 | "WeBWorK::ContentGenerator::Instructor::SetsAssignedToUser", |
1332 | "WeBWorK::ContentGenerator::Instructor::UserDetail", |
| 823 | courseID => $courseID, userID => $editForUser[0])) if $forOneUser; |
1333 | courseID => $courseID, userID => $editForUser[0])) if $forOneUser; |
| 824 | |
1334 | |
| 825 | |
1335 | |
| 826 | my $setDetailPage = $urlpath -> newFromModule($urlpath->module, courseID => $courseID, setID => $setID); |
1336 | my $setDetailPage = $urlpath -> newFromModule($urlpath->module, courseID => $courseID, setID => $setID); |
| 827 | my $setDetailURL = $self->systemLink($setDetailPage,authen=>0); |
1337 | my $setDetailURL = $self->systemLink($setDetailPage, authen=>0); |
| 828 | |
1338 | |
| 829 | |
1339 | |
| 830 | my $userCountMessage = CGI::a({href=>$editUsersAssignedToSetURL}, $self->userCountMessage($setUserCount, $userCount)); |
1340 | my $userCountMessage = CGI::a({href=>$editUsersAssignedToSetURL}, $self->userCountMessage($setUserCount, $userCount)); |
| 831 | 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; |
| 832 | |
1342 | |
| 833 | $userCountMessage = "The set $setID is assigned to " . $userCountMessage . "."; |
1343 | $userCountMessage = "The set $setID is assigned to " . $userCountMessage . "."; |
| 834 | $setCountMessage = "The user $editForUser[0] has been assigned " . $setCountMessage . "." if $forOneUser; |
1344 | $setCountMessage = "The user $editForUser[0] has been assigned " . $setCountMessage . "." if $forOneUser; |
| 835 | |
1345 | |
| 836 | if ($forUsers) { |
1346 | if ($forUsers) { |
| 837 | print CGI::p("$userCountMessage Editing user-specific overrides for ". CGI::b(join ", ", @editForUser)); |
1347 | ############################################## |
| 838 | if ($forOneUser) { |
1348 | # calculate links for the users being edited: |
| 839 | 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; |
| 840 | } |
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 | ); |
| 841 | } else { |
1375 | } else { |
| 842 | 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 | ); |
| 843 | } |
1385 | } |
| 844 | |
1386 | |
| 845 | |
1387 | # handle renumbering of problems if necessary |
|
|
1388 | print CGI::a({name=>"problems"}); |
|
|
1389 | |
|
|
1390 | my %newProblemNumbers = (); |
|
|
1391 | my $maxProblemNumber = -1; |
|
|
1392 | for my $jj (sort { $a <=> $b } $db->listGlobalProblems($setID)) { |
|
|
1393 | $newProblemNumbers{$jj} = $r->param('problem_num_' . $jj); |
|
|
1394 | $maxProblemNumber = $jj if $jj > $maxProblemNumber; |
|
|
1395 | } |
|
|
1396 | |
|
|
1397 | my $forceRenumber = $r->param('force_renumber') || 0; |
|
|
1398 | handle_problem_numbers(\%newProblemNumbers, $maxProblemNumber, $db, $setID, $forceRenumber) unless defined $r->param('undo_changes'); |
| 846 | |
1399 | |
| 847 | my %properties = %{ FIELD_PROPERTIES() }; |
1400 | my %properties = %{ FIELD_PROPERTIES() }; |
| 848 | |
1401 | |
| 849 | my %display_modes = %{WeBWorK::PG::DISPLAY_MODES()}; |
1402 | my %display_modes = %{WeBWorK::PG::DISPLAY_MODES()}; |
| 850 | my @active_modes = grep { exists $display_modes{$_} } @{$r->ce->{pg}->{displayModes}}; |
1403 | my @active_modes = grep { exists $display_modes{$_} } @{$r->ce->{pg}->{displayModes}}; |
| … | |
… | |
| 855 | ##################################################################### |
1408 | ##################################################################### |
| 856 | # Browse available header/problem files |
1409 | # Browse available header/problem files |
| 857 | ##################################################################### |
1410 | ##################################################################### |
| 858 | |
1411 | |
| 859 | my $templates = $r->ce->{courseDirs}->{templates}; |
1412 | my $templates = $r->ce->{courseDirs}->{templates}; |
| 860 | my %probLibs = %{ $r->ce->{courseFiles}->{problibs} }; |
1413 | my $skip = join("|", keys %{ $r->ce->{courseFiles}->{problibs} }); |
| 861 | my $skip = join("|", keys %probLibs); |
|
|
| 862 | |
1414 | |
| 863 | my @headerFileList = listFilesRecursive( |
1415 | my @headerFileList = listFilesRecursive( |
| 864 | $templates, |
1416 | $templates, |
| 865 | qr/header.*\.pg$/i, # match these files |
1417 | qr/header.*\.pg$/i, # match these files |
| 866 | qr/^(?:$skip|CVS)$/, # prune these directories |
1418 | qr/^(?:$skip|CVS)$/, # prune these directories |
| … | |
… | |
| 900 | |
1452 | |
| 901 | print CGI::start_table({border=>1, cellpadding=>4}); |
1453 | print CGI::start_table({border=>1, cellpadding=>4}); |
| 902 | print CGI::Tr({}, CGI::th({}, [ |
1454 | print CGI::Tr({}, CGI::th({}, [ |
| 903 | "General Information", |
1455 | "General Information", |
| 904 | ])); |
1456 | ])); |
| 905 | |
1457 | |
|
|
1458 | # this is kind of a hack -- we need to get a user record here, so we can |
|
|
1459 | # pass it to FieldTable, so FieldTable can pass it to FieldHTML, so |
|
|
1460 | # FieldHTML doesn't have to fetch it itself. |
|
|
1461 | my $userSetRecord = $db->getUserSet($userToShow, $setID); |
|
|
1462 | |
| 906 | print CGI::Tr({}, CGI::td({}, [ |
1463 | print CGI::Tr({}, CGI::td({}, [ |
| 907 | $self->FieldTable($userToShow, $setID), |
1464 | $self->FieldTable($userToShow, $setID, undef, $setRecord, $userSetRecord), |
| 908 | ])); |
1465 | ])); |
| 909 | print CGI::end_table(); |
1466 | print CGI::end_table(); |
| 910 | |
1467 | |
| 911 | # spacing |
1468 | # spacing |
| 912 | print CGI::p(); |
1469 | print CGI::p(); |
| … | |
… | |
| 925 | print CGI::Tr({}, CGI::th({}, [ |
1482 | print CGI::Tr({}, CGI::th({}, [ |
| 926 | "Headers", |
1483 | "Headers", |
| 927 | # "Data", |
1484 | # "Data", |
| 928 | "Display Mode: " . |
1485 | "Display Mode: " . |
| 929 | CGI::popup_menu(-name => "header.displaymode", -values => \@active_modes, -default => $default_header_mode) . ' '. |
1486 | CGI::popup_menu(-name => "header.displaymode", -values => \@active_modes, -default => $default_header_mode) . ' '. |
| 930 | CGI::input({type => "submit", name => "refresh", value => "Refresh"}), |
1487 | CGI::input({type => "submit", name => "refresh", value => "Refresh Display"}), |
| 931 | ])); |
1488 | ])); |
| 932 | |
1489 | |
| 933 | my %header_html; |
1490 | my %header_html; |
| 934 | |
1491 | |
| 935 | my %error; |
1492 | my %error; |
| 936 | foreach my $header (@headers) { |
1493 | foreach my $header (@headers) { |
| 937 | my $headerFile = $r->param("set.$setID.$header") || $setRecord->{$header} || $headerDefaults{$header}; |
1494 | my $headerFile = $r->param("set.$setID.$header") || $setRecord->{$header} || $headerDefaults{$header}; |
| 938 | |
1495 | |
| 939 | $error{$header} = $self->checkFile($headerFile); |
1496 | $error{$header} = $self->checkFile($headerFile); |
|
|
1497 | my $this_set = $db->getMergedSet($userToShow, $setID); |
| 940 | unless ($error{$header}) { |
1498 | unless ($error{$header}) { |
| 941 | my @temp = renderProblems( r=> $r, |
1499 | my @temp = renderProblems( |
|
|
1500 | r=> $r, |
| 942 | user => $db->getUser($userToShow), |
1501 | user => $db->getUser($userToShow), |
| 943 | displayMode=> $default_header_mode, |
1502 | displayMode=> $default_header_mode, |
| 944 | problem_number=> 0, |
1503 | problem_number=> 0, |
| 945 | this_set => $db->getMergedSet($userToShow, $setID), |
1504 | this_set => $this_set, |
| 946 | problem_list => [$headerFile], |
1505 | problem_list => [$headerFile], |
| 947 | ); |
1506 | ); |
| 948 | $header_html{$header} = $temp[0]; |
1507 | $header_html{$header} = $temp[0]; |
| 949 | } |
1508 | } |
| 950 | } |
1509 | } |
| 951 | |
1510 | |
| … | |
… | |
| 958 | my $viewHeaderLink = $self->systemLink($viewHeaderPage); |
1517 | my $viewHeaderLink = $self->systemLink($viewHeaderPage); |
| 959 | |
1518 | |
| 960 | print CGI::Tr({}, CGI::td({}, [ |
1519 | print CGI::Tr({}, CGI::td({}, [ |
| 961 | CGI::start_table({border => 0, cellpadding => 0}) . |
1520 | CGI::start_table({border => 0, cellpadding => 0}) . |
| 962 | CGI::Tr({}, CGI::td({}, $properties{$header}->{name})) . |
1521 | CGI::Tr({}, CGI::td({}, $properties{$header}->{name})) . |
| 963 | CGI::Tr({}, CGI::td({}, CGI::a({href => $editHeaderLink}, "Edit it"))) . |
1522 | CGI::Tr({}, CGI::td({}, CGI::a({href => $editHeaderLink, target=>"WW_Editor"}, "Edit it"))) . |
| 964 | CGI::Tr({}, CGI::td({}, CGI::a({href => $viewHeaderLink}, "View it"))) . |
1523 | CGI::Tr({}, CGI::td({}, CGI::a({href => $viewHeaderLink, target=>"WW_View"}, "View it"))) . |
| 965 | # 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"}))) . |
| 966 | CGI::end_table(), |
1525 | CGI::end_table(), |
| 967 | # "", |
1526 | # "", |
| 968 | # CGI::input({ name => "set.$setID.$header", value => $setRecord->{$header}, size => 50}) . |
1527 | # CGI::input({ name => "set.$setID.$header", value => $setRecord->{$header}, size => 50}) . |
| 969 | # join ("\n", $self->FieldHTML($userToShow, $setID, $problemID, "source_file")) . |
1528 | # join ("\n", $self->FieldHTML($userToShow, $setID, $problemID, "source_file")) . |
| … | |
… | |
| 995 | |
1554 | |
| 996 | ##################################################################### |
1555 | ##################################################################### |
| 997 | # Display problem information |
1556 | # Display problem information |
| 998 | ##################################################################### |
1557 | ##################################################################### |
| 999 | |
1558 | |
| 1000 | my @problemIDList = $db->listGlobalProblems($setID); |
1559 | my @problemIDList = sort { $a <=> $b } $db->listGlobalProblems($setID); |
|
|
1560 | |
|
|
1561 | # DBFIXME use iterators instead of getting all at once |
|
|
1562 | |
|
|
1563 | # get global problem records for all problems in one go |
|
|
1564 | my %GlobalProblems; |
|
|
1565 | my @globalKeypartsRef = map { [$setID, $_] } @problemIDList; |
|
|
1566 | # DBFIXME shouldn't need to get key list here |
|
|
1567 | @GlobalProblems{@problemIDList} = $db->getGlobalProblems(@globalKeypartsRef); |
|
|
1568 | |
|
|
1569 | # if needed, get user problem records for all problems in one go |
|
|
1570 | my (%UserProblems, %MergedProblems); |
|
|
1571 | if ($forOneUser) { |
|
|
1572 | my @userKeypartsRef = map { [$editForUser[0], $setID, $_] } @problemIDList; |
|
|
1573 | # DBFIXME shouldn't need to get key list here |
|
|
1574 | @UserProblems{@problemIDList} = $db->getUserProblems(@userKeypartsRef); |
|
|
1575 | @MergedProblems{@problemIDList} = $db->getMergedProblems(@userKeypartsRef); |
|
|
1576 | } |
|
|
1577 | |
| 1001 | if (scalar @problemIDList) { |
1578 | if (scalar @problemIDList) { |
| 1002 | |
|
|
| 1003 | my $maxProblemNumber = $self->{maxProblemNumber}; |
|
|
| 1004 | |
1579 | |
| 1005 | print CGI::start_table({border=>1, cellpadding=>4}); |
1580 | print CGI::start_table({border=>1, cellpadding=>4}); |
| 1006 | print CGI::Tr({}, CGI::th({}, [ |
1581 | print CGI::Tr({}, CGI::th({}, [ |
| 1007 | "Problems", |
1582 | "Problems", |
| 1008 | "Data", |
1583 | "Data", |
| 1009 | "Display Mode: " . |
1584 | "Display Mode: " . |
| 1010 | CGI::popup_menu(-name => "problem.displaymode", -values => \@active_modes, -default => $default_problem_mode) . ' '. |
1585 | CGI::popup_menu(-name => "problem.displaymode", -values => \@active_modes, -default => $default_problem_mode) . ' '. |
| 1011 | CGI::input({type => "submit", name => "refresh", value => "Refresh"}), |
1586 | CGI::input({type => "submit", name => "refresh", value => "Refresh Display"}), |
| 1012 | ])); |
1587 | ])); |
| 1013 | |
1588 | |
| 1014 | my %shownYet; |
1589 | my %shownYet; |
| 1015 | my $repeatFile; |
1590 | my $repeatFile; |
| 1016 | foreach my $problemID (@problemIDList) { |
1591 | foreach my $problemID (@problemIDList) { |
| 1017 | |
1592 | |
| 1018 | my $problemRecord; |
1593 | my $problemRecord; |
| 1019 | if ($forOneUser) { |
1594 | if ($forOneUser) { |
| 1020 | $problemRecord = $db->getMergedProblem($editForUser[0], $setID, $problemID); |
1595 | #$problemRecord = $db->getMergedProblem($editForUser[0], $setID, $problemID); |
|
|
1596 | $problemRecord = $MergedProblems{$problemID}; # already fetched above --sam |
| 1021 | } else { |
1597 | } else { |
| 1022 | $problemRecord = $db->getGlobalProblem($setID, $problemID); |
1598 | #$problemRecord = $db->getGlobalProblem($setID, $problemID); |
|
|
1599 | $problemRecord = $GlobalProblems{$problemID}; # already fetched above --sam |
| 1023 | } |
1600 | } |
|
|
1601 | |
|
|
1602 | #$self->addgoodmessage(""); |
|
|
1603 | #$self->addbadmessage($problemRecord->toString()); |
|
|
1604 | |
| 1024 | |
1605 | |
| 1025 | my $editProblemPage = $urlpath->new(type => 'instructor_problem_editor_withset_withproblem', args => { courseID => $courseID, setID => $setID, problemID => $problemID }); |
1606 | my $editProblemPage = $urlpath->new(type => 'instructor_problem_editor_withset_withproblem', args => { courseID => $courseID, setID => $setID, problemID => $problemID }); |
| 1026 | my $editProblemLink = $self->systemLink($editProblemPage, params => { make_local_copy => 0 }); |
1607 | my $editProblemLink = $self->systemLink($editProblemPage, params => { make_local_copy => 0 }); |
| 1027 | |
1608 | |
|
|
1609 | |
| 1028 | # FIXME: should we have an "act as" type link here when editing for multiple users? |
1610 | # FIXME: should we have an "act as" type link here when editing for multiple users? |
| 1029 | my $viewProblemPage = $urlpath->new(type => 'problem_detail', args => { courseID => $courseID, setID => $setID, problemID => $problemID }); |
1611 | my $viewProblemPage = $urlpath->new(type => 'problem_detail', args => { courseID => $courseID, setID => $setID, problemID => $problemID }); |
| 1030 | my $viewProblemLink = $self->systemLink($viewProblemPage, params => { effectiveUser => ($forOneUser ? $editForUser[0] : $userID)}); |
1612 | my $viewProblemLink = $self->systemLink($viewProblemPage, params => { effectiveUser => ($forOneUser ? $editForUser[0] : $userID)}); |
| 1031 | |
1613 | |
| 1032 | my @fields = @{ PROBLEM_FIELDS() }; |
1614 | my @fields = @{ PROBLEM_FIELDS() }; |
| … | |
… | |
| 1037 | # warn of repeat problems |
1619 | # warn of repeat problems |
| 1038 | if (defined $shownYet{$problemFile}) { |
1620 | if (defined $shownYet{$problemFile}) { |
| 1039 | $repeatFile = "This problem uses the same source file as number " . $shownYet{$problemFile} . "."; |
1621 | $repeatFile = "This problem uses the same source file as number " . $shownYet{$problemFile} . "."; |
| 1040 | } else { |
1622 | } else { |
| 1041 | $shownYet{$problemFile} = $problemID; |
1623 | $shownYet{$problemFile} = $problemID; |
|
|
1624 | $repeatFile = ""; |
| 1042 | } |
1625 | } |
| 1043 | |
1626 | |
| 1044 | my $error = $self->checkFile($problemFile); |
1627 | my $error = $self->checkFile($problemFile); |
|
|
1628 | my $this_set = $db->getMergedSet($userToShow, $setID); |
| 1045 | my @problem_html; |
1629 | my @problem_html; |
| 1046 | unless ($error) { |
1630 | unless ($error) { |
| 1047 | @problem_html = renderProblems( r=> $r, |
1631 | @problem_html = renderProblems( |
|
|
1632 | r=> $r, |
| 1048 | user => $db->getUser($userToShow), |
1633 | user => $db->getUser($userToShow), |
| 1049 | displayMode=> $default_problem_mode, |
1634 | displayMode=> $default_problem_mode, |
| 1050 | problem_number=> $problemID, |
1635 | problem_number=> $problemID, |
| 1051 | this_set => $db->getMergedSet($userToShow, $setID), |
1636 | this_set => $this_set, |
| 1052 | problem_seed => $forOneUser ? $problemRecord->problem_seed : 0, |
1637 | problem_seed => $forOneUser ? $problemRecord->problem_seed : 0, |
| 1053 | problem_list => [$problemRecord->source_file], |
1638 | problem_list => [$problemRecord->source_file], |
| 1054 | ); |
1639 | ); |
| 1055 | } |
1640 | } |
| 1056 | |
1641 | |
| 1057 | print CGI::Tr({}, CGI::td({}, [ |
1642 | print CGI::Tr({}, CGI::td({}, [ |
| 1058 | CGI::start_table({border => 0, cellpadding => 1}) . |
1643 | CGI::start_table({border => 0, cellpadding => 1}) . |
| 1059 | CGI::Tr({}, CGI::td({}, problem_number_popup($problemID, $maxProblemNumber))) . |
1644 | CGI::Tr({}, CGI::td({}, problem_number_popup($problemID, $maxProblemNumber))) . |
| 1060 | CGI::Tr({}, CGI::td({}, CGI::a({href => $editProblemLink}, "Edit it"))) . |
1645 | CGI::Tr({}, CGI::td({}, CGI::a({href => $editProblemLink, target=>"WW_Editor"}, "Edit it"))) . |
| 1061 | 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])" : "")))) . |
| 1062 | ($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?"})))) . |
| 1063 | # 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}))) . |
|
|
1649 | ($forOneUser ? "" : CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "markCorrect", value => $problemID, label => "Mark Correct?"})))) . |
| 1064 | CGI::end_table(), |
1650 | CGI::end_table(), |
| 1065 | $self->FieldTable($userToShow, $setID, $problemID), |
1651 | $self->FieldTable($userToShow, $setID, $problemID, $GlobalProblems{$problemID}, $UserProblems{$problemID}), |
| 1066 | # A comprehensive list of problems is just TOO big to be handled well |
1652 | # A comprehensive list of problems is just TOO big to be handled well |
| 1067 | # comboBox({ |
1653 | # comboBox({ |
| 1068 | # name => "set.$setID.$problemID", |
1654 | # name => "set.$setID.$problemID", |
| 1069 | # request => $r, |
1655 | # request => $r, |
| 1070 | # default => $problemRecord->{problem_id}, |
1656 | # default => $problemRecord->{problem_id}, |
| 1071 | # multiple => 0, |
1657 | # multiple => 0, |
| 1072 | # values => \@problemFileList, |
1658 | # values => \@problemFileList, |
| 1073 | # }) . |
1659 | # }) . |
| 1074 | |
1660 | |
| 1075 | join ("\n", $self->FieldHTML($userToShow, $setID, $problemID, "source_file")) . |
1661 | join ("\n", $self->FieldHTML( |
|
|
1662 | $userToShow, |
|
|
1663 | $setID, |
|
|
1664 | $problemID, |
|
|
1665 | $GlobalProblems{$problemID}, # pass previously fetched global record to FieldHTML --sam |
|
|
1666 | $UserProblems{$problemID}, # pass previously fetched user record to FieldHTML --sam |
|
|
1667 | "source_file" |
|
|
1668 | )) . |
| 1076 | CGI::br() . |
1669 | CGI::br() . |
| 1077 | ($error ? |
1670 | ($error ? |
| 1078 | CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $error) |
1671 | CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $error) |
| 1079 | : CGI::div({class=> "RenderSolo"}, $problem_html[0]->{body_text}) |
1672 | : CGI::div({class=> "RenderSolo"}, $problem_html[0]->{body_text}) |
| 1080 | ) . |
1673 | ) . |
| 1081 | ($repeatFile ? CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $repeatFile) : ''), |
1674 | ($repeatFile ? CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $repeatFile) : ''), |
| 1082 | ])); |
1675 | ])); |
| 1083 | } |
1676 | } |
| 1084 | |
1677 | |
|
|
1678 | |
|
|
1679 | # print final lines |
| 1085 | print CGI::end_table(); |
1680 | print CGI::end_table(); |
| 1086 | print CGI::checkbox({ |
1681 | print CGI::checkbox({ |
| 1087 | label=> "Force problems to be numbered consecutively from one", |
1682 | label=> "Force problems to be numbered consecutively from one (always done when reordering problems)", |
| 1088 | name=>"force_renumber", value=>"1"}), |
1683 | name=>"force_renumber", value=>"1"}); |
| 1089 | |
|
|
| 1090 | CGI::br(); |
|
|
| 1091 | print CGI::input({type=>"submit", name=>"submit_changes", value=>"Save Changes"}); |
|
|
| 1092 | print CGI::p(<<HERE); |
1684 | print CGI::p(<<EOF); |
| 1093 | Any time problem numbers are intentionally changed, the problems will |
1685 | Any time problem numbers are intentionally changed, the problems will |
| 1094 | always be renumbered consecutively, starting from one. When deleting |
1686 | always be renumbered consecutively, starting from one. When deleting |
| 1095 | 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 |
| 1096 | checked. |
1688 | checked. |
| 1097 | HERE |
1689 | EOF |
| 1098 | 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()); |
| 1099 | 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."); |
| 1100 | the problem to be ", CGI::em("before"), " the chosen number."); |
|
|
| 1101 | |
1692 | |
| 1102 | } else { |
1693 | } else { |
| 1103 | 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.")); |
| 1104 | } |
1695 | } |
|
|
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 | |
|
|
1708 | #my $editNewProblemPage = $urlpath->new(type => 'instructor_problem_editor_withset_withproblem', args => { courseID => $courseID, setID => $setID, problemID =>'new_problem' }); |
|
|
1709 | #my $editNewProblemLink = $self->systemLink($editNewProblemPage, params => { make_local_copy => 1, file_type => 'blank_problem' }); |
|
|
1710 | # This next feature isn't fully supported and is causing problems. Remove for now. #FIXME |
|
|
1711 | #print CGI::p( CGI::a({href=>$editNewProblemLink},'Edit'). ' a new blank problem'); |
| 1105 | |
1712 | |
| 1106 | print CGI::end_form(); |
1713 | print CGI::end_form(); |
| 1107 | |
1714 | |
| 1108 | return ""; |
1715 | return ""; |
| 1109 | } |
1716 | } |