[system] / branches / rel-2-1-a1 / webwork2 / lib / WeBWorK / ContentGenerator / Instructor / ProblemSetList.pm Repository:
ViewVC logotype

Annotation of /branches/rel-2-1-a1/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList.pm

Parent Directory Parent Directory | Revision Log Revision Log


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

1 : sh002i 1663 ################################################################################
2 :     # WeBWorK Online Homework Delivery System
3 :     # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4 : toenail 2246 # $CVSHeader:
5 : sh002i 1663 #
6 :     # This program is free software; you can redistribute it and/or modify it under
7 :     # the terms of either: (a) the GNU General Public License as published by the
8 :     # Free Software Foundation; either version 2, or (at your option) any later
9 :     # version, or (b) the "Artistic License" which comes with this package.
10 :     #
11 :     # This program is distributed in the hope that it will be useful, but WITHOUT
12 :     # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 :     # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
14 :     # Artistic License for more details.
15 :     ################################################################################
16 :    
17 : malsyned 832 package WeBWorK::ContentGenerator::Instructor::ProblemSetList;
18 :     use base qw(WeBWorK::ContentGenerator::Instructor);
19 :    
20 :     =head1 NAME
21 :    
22 : toenail 2246 WeBWorK::ContentGenerator::Instructor::ProblemSetList - Entry point for Set-specific
23 :     data editing/viewing
24 : malsyned 832
25 :     =cut
26 :    
27 : toenail 2246 =for comment
28 :    
29 :     What do we want to be able to do here?
30 :    
31 :     filter sort edit publish import create delete
32 :    
33 :     Filter what sets are shown:
34 :     - none, all, selected
35 : toenail 2278 - matching set_id, visible to students, hidden from students
36 : toenail 2246
37 :     Sort sets by:
38 :     - set name
39 :     - open date
40 :     - due date
41 :     - answer date
42 :     - header files
43 :     - visibility to students
44 :    
45 :     Switch from view mode to edit mode:
46 :     - showing visible sets
47 :     - showing selected sets
48 :     Switch from edit mode to view and save changes
49 :     Switch from edit mode to view and abandon changes
50 :    
51 :     Make sets visible to or hidden from students:
52 :     - all, selected
53 :    
54 :     Import sets:
55 :     - replace:
56 :     - any users
57 :     - visible users
58 :     - selected users
59 :     - no users
60 :     - add:
61 :     - any users
62 :     - no users
63 :    
64 : toenail 2272 Score sets:
65 :     - all
66 :     - visible
67 :     - selected
68 :    
69 : toenail 2246 Create a set with a given name
70 :    
71 :     Delete sets:
72 :     - visible
73 :     - selected
74 :    
75 :     =cut
76 :    
77 : gage 2536 # FIXME: rather than having two types of boolean modes $editMode and $exportMode
78 :     # make one $mode variable that contains a string like "edit", "view", or "export"
79 :    
80 : malsyned 832 use strict;
81 :     use warnings;
82 :     use CGI qw();
83 : glarose 2343 use WeBWorK::Utils qw(formatDateTime parseDateTime timeToSec readFile readDirectory cryptPassword);
84 : malsyned 832
85 : toenail 2280 use constant HIDE_SETS_THRESHOLD => 50;
86 :     use constant DEFAULT_PUBLISHED_STATE => 1;
87 : gage 1428
88 : toenail 2246 use constant EDIT_FORMS => [qw(cancelEdit saveEdit)];
89 : gage 2536 use constant VIEW_FORMS => [qw(filter sort edit publish import export score create delete)];
90 :     use constant EXPORT_FORMS => [qw(cancelExport saveExport)];
91 : toenail 2246
92 : gage 2536 use constant VIEW_FIELD_ORDER => [ qw( select set_id problems users published open_date due_date answer_date set_header hardcopy_header) ];
93 :     use constant EDIT_FIELD_ORDER => [ qw( set_id published open_date due_date answer_date set_header hardcopy_header) ];
94 :     use constant EXPORT_FIELD_ORDER => [ qw( select set_id filename) ];
95 : toenail 2246
96 : gage 2536 # permissions needed to perform a given action
97 :     use constant FORM_PERMS => {
98 :     saveEdit => "modify_problem_sets",
99 :     edit => "modify_problem_sets",
100 :     publish => "modify_problem_sets",
101 :     import => "create_and_delete_problem_sets",
102 :     export => "modify_set_def_files",
103 :     saveExport => "modify_set_def_files",
104 :     score => "score_sets",
105 :     create => "create_and_delete_problem_sets",
106 :     delete => "create_and_delete_problem_sets",
107 :     };
108 : toenail 2246
109 : gage 2536 # permissions needed to view a given field
110 :     use constant FIELD_PERMS => {
111 :     problems => "modify_problem_sets",
112 :     users => "assign_problem_sets",
113 :     };
114 :    
115 :     use constant STATE_PARAMS => [qw(user effectiveUser key visible_sets no_visible_sets prev_visible_sets no_prev_visible_set editMode exportMode primarySortField secondarySortField)];
116 :    
117 : toenail 2246 use constant SORT_SUBS => {
118 :     set_id => \&bySetID,
119 :     set_header => \&bySetHeader,
120 : gage 2536 hardcopy_header => \&byHardcopyHeader,
121 : toenail 2246 open_date => \&byOpenDate,
122 :     due_date => \&byDueDate,
123 :     answer_date => \&byAnswerDate,
124 :     published => \&byPublished,
125 :    
126 :     };
127 :    
128 :     use constant FIELD_PROPERTIES => {
129 :     set_id => {
130 :     type => "text",
131 :     size => 8,
132 :     access => "readonly",
133 :     },
134 :     set_header => {
135 :     type => "filelist",
136 :     size => 10,
137 :     access => "readwrite",
138 :     },
139 : gage 2536 hardcopy_header => {
140 : toenail 2246 type => "filelist",
141 :     size => 10,
142 :     access => "readwrite",
143 :     },
144 :     open_date => {
145 :     type => "text",
146 :     size => 20,
147 :     access => "readwrite",
148 :     },
149 :     due_date => {
150 :     type => "text",
151 :     size => 20,
152 :     access => "readwrite",
153 :     },
154 :     answer_date => {
155 :     type => "text",
156 :     size => 20,
157 :     access => "readwrite",
158 :     },
159 :     published => {
160 :     type => "checked",
161 :     size => 4,
162 :     access => "readwrite",
163 : glarose 2343 },
164 : glarose 2337 assignment_type => {
165 : glarose 2343 type => "text",
166 :     size => 20,
167 :     access => "readwrite",
168 :     },
169 : glarose 2337 attempts_per_version => {
170 : glarose 2343 type => "text",
171 :     size => 4,
172 :     access => "readwrite",
173 :     },
174 : glarose 2337 time_interval => {
175 : glarose 2343 type => "text",
176 :     size => 10,
177 :     access => "readwrite",
178 :     },
179 : glarose 2337 versions_per_interval => {
180 : glarose 2343 type => "text",
181 :     size => 4,
182 :     access => "readwrite",
183 :     },
184 : glarose 2337 version_time_limit => {
185 : glarose 2343 type => "text",
186 :     size => 10,
187 :     access => "readwrite",
188 :     },
189 :     problem_randorder => {
190 :     type => "text",
191 :     size => 4,
192 :     access => "readwrite",
193 :     },
194 : glarose 2337 version_creation_time => {
195 : glarose 2343 type => "text",
196 :     size => 10,
197 :     access => "readonly",
198 :     },
199 : glarose 3186 version_last_attempt_time => {
200 :     type => "text",
201 :     size => 10,
202 :     access => "readonly",
203 :     },
204 : toenail 2246 };
205 :    
206 : toenail 2272 sub pre_header_initialize {
207 : toenail 2246 my ($self) = @_;
208 :     my $r = $self->r;
209 :     my $db = $r->db;
210 :     my $ce = $r->ce;
211 :     my $authz = $r->authz;
212 : toenail 2272 my $urlpath = $r->urlpath;
213 : toenail 2246 my $user = $r->param('user');
214 : toenail 2272 my $courseName = $urlpath->arg("courseID");
215 : toenail 2246
216 : toenail 2272
217 : gage 2536 # Check permissions
218 :     return unless $authz->hasPermissions($user, "access_instructor_tools");
219 :    
220 :     if (defined $r->param("action") and $r->param("action") eq "score" and $authz->hasPermissions($user, "score_sets")) {
221 : toenail 2272 my $scope = $r->param("action.score.scope");
222 :     my @setsToScore = ();
223 :    
224 :     if ($scope eq "none") {
225 :     return "No sets selected for scoring.";
226 :     } elsif ($scope eq "all") {
227 :     @setsToScore = @{ $r->param("allSetIDs") };
228 :     } elsif ($scope eq "visible") {
229 :     @setsToScore = @{ $r->param("visibleSetIDs") };
230 :     } elsif ($scope eq "selected") {
231 :     @setsToScore = $r->param("selected_sets");
232 :     }
233 :    
234 :     my $uri = $self->systemLink( $urlpath->newFromModule('WeBWorK::ContentGenerator::Instructor::Scoring', courseID=>$courseName),
235 :     params=>{
236 :     scoreSelected=>"ScoreSelected",
237 :     selectedSet=>\@setsToScore,
238 :     # recordSingleSetScores=>''
239 :     }
240 :     );
241 :    
242 :     $self->reply_with_redirect($uri);
243 : malsyned 1410 }
244 : toenail 2272
245 :     }
246 :    
247 : toenail 2246 sub body {
248 :     my ($self) = @_;
249 :     my $r = $self->r;
250 :     my $urlpath = $r->urlpath;
251 :     my $db = $r->db;
252 :     my $ce = $r->ce;
253 :     my $authz = $r->authz;
254 :     my $courseName = $urlpath->arg("courseID");
255 :     my $setID = $urlpath->arg("setID");
256 :     my $user = $r->param('user');
257 : malsyned 963
258 : toenail 2246 my $root = $ce->{webworkURLs}->{root};
259 :    
260 :     # templates for getting field names
261 :     my $setTemplate = $self->{setTemplate} = $db->newGlobalSet;
262 :    
263 : gage 2536 return CGI::div({class => "ResultsWithError"}, "You are not authorized to access the Instructor tools.")
264 : toenail 2246 unless $authz->hasPermissions($user, "access_instructor_tools");
265 :    
266 :     # This table can be consulted when display-ready forms of field names are needed.
267 :     my %prettyFieldNames = map { $_ => $_ }
268 :     $setTemplate->FIELDS();
269 :    
270 :     @prettyFieldNames{qw(
271 :     select
272 :     problems
273 :     users
274 : gage 2536 filename
275 : toenail 2246 set_id
276 :     set_header
277 : gage 2536 hardcopy_header
278 : toenail 2246 open_date
279 :     due_date
280 :     answer_date
281 :     published
282 :     )} = (
283 :     "Select",
284 :     "Problems",
285 :     "Assigned Users",
286 : gage 2536 "Set Definition Filename",
287 : toenail 2246 "Set Name",
288 :     "Set Header",
289 : gage 2536 "Hardcopy Header",
290 : toenail 2246 "Open Date",
291 :     "Due Date",
292 :     "Answer Date",
293 :     "Visible",
294 :     );
295 :    
296 :     ########## set initial values for state fields
297 :    
298 :     my @allSetIDs = $db->listGlobalSets;
299 :     my @users = $db->listUsers;
300 :     $self->{allSetIDs} = \@allSetIDs;
301 :     $self->{totalUsers} = scalar @users;
302 :    
303 :     if (defined $r->param("visible_sets")) {
304 :     $self->{visibleSetIDs} = [ $r->param("visible_sets") ];
305 :     } elsif (defined $r->param("no_visible_sets")) {
306 :     $self->{visibleSetIDs} = [];
307 :     } else {
308 :     if (@allSetIDs > HIDE_SETS_THRESHOLD) {
309 :     $self->{visibleSetIDs} = [];
310 :     } else {
311 :     $self->{visibleSetIDs} = [ @allSetIDs ];
312 :     }
313 : malsyned 1017 }
314 : toenail 2246
315 :     $self->{prevVisibleSetIDs} = $self->{visibleSetIDs};
316 :    
317 :     if (defined $r->param("selected_sets")) {
318 :     $self->{selectedSetIDs} = [ $r->param("selected_sets") ];
319 :     } else {
320 :     $self->{selectedSetIDs} = [];
321 : gage 1843 }
322 : toenail 2246
323 :     $self->{editMode} = $r->param("editMode") || 0;
324 :    
325 : gage 2536 return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to modify problem sets."))
326 :     if $self->{editMode} and not $authz->hasPermissions($user, "modify_problem_sets");
327 :    
328 :     $self->{exportMode} = $r->param("exportMode") || 0;
329 :    
330 :     return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to modify set definition files."))
331 :     if $self->{exportMode} and not $authz->hasPermissions($user, "modify_set_def_files");
332 :    
333 : toenail 2246 $self->{primarySortField} = $r->param("primarySortField") || "due_date";
334 :     $self->{secondarySortField} = $r->param("secondarySortField") || "open_date";
335 :    
336 :     my @allSets = $db->getGlobalSets(@allSetIDs);
337 :    
338 :     my (%open_dates, %due_dates, %answer_dates);
339 :     foreach my $Set (@allSets) {
340 :     push @{$open_dates{defined $Set->open_date ? $Set->open_date : ""}}, $Set->set_id;
341 :     push @{$due_dates{defined $Set->due_date ? $Set->due_date : ""}}, $Set->set_id;
342 :     push @{$answer_dates{defined $Set->answer_date ? $Set->answer_date : ""}}, $Set->set_id;
343 :     }
344 :     $self->{open_dates} = \%open_dates;
345 :     $self->{due_dates} = \%due_dates;
346 :     $self->{answer_dates} = \%answer_dates;
347 :    
348 :     ########## call action handler
349 :    
350 :     my $actionID = $r->param("action");
351 :     if ($actionID) {
352 : gage 2536 unless (grep { $_ eq $actionID } @{ VIEW_FORMS() }, @{ EDIT_FORMS() }, @{ EXPORT_FORMS() }) {
353 : toenail 2246 die "Action $actionID not found";
354 : malsyned 963 }
355 : gage 2536 # Check permissions
356 :     if (not FORM_PERMS()->{$actionID} or $authz->hasPermissions($user, FORM_PERMS()->{$actionID})) {
357 :     my $actionHandler = "${actionID}_handler";
358 :     my %genericParams;
359 :     foreach my $param (qw(selected_sets)) {
360 :     $genericParams{$param} = [ $r->param($param) ];
361 :     }
362 :     my %actionParams = $self->getActionParams($actionID);
363 :     my %tableParams = $self->getTableParams();
364 :     print CGI::div({class=>"Message"}, CGI::p("Results of last action performed: ", $self->$actionHandler(\%genericParams, \%actionParams, \%tableParams))), CGI::hr();
365 :     } else {
366 :     return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to perform this action."));
367 : toenail 2210 }
368 : gage 2536
369 : toenail 2246 }
370 : gage 1428
371 : toenail 2246 ########## retrieve possibly changed values for member fields
372 :    
373 :     @allSetIDs = @{ $self->{allSetIDs} }; # do we need this one? YES, deleting or importing a set will change this.
374 :     my @visibleSetIDs = @{ $self->{visibleSetIDs} };
375 :     my @prevVisibleSetIDs = @{ $self->{prevVisibleSetIDs} };
376 :     my @selectedSetIDs = @{ $self->{selectedSetIDs} };
377 :     my $editMode = $self->{editMode};
378 : gage 2536 my $exportMode = $self->{exportMode};
379 : toenail 2246 my $primarySortField = $self->{primarySortField};
380 :     my $secondarySortField = $self->{secondarySortField};
381 :    
382 :     #warn "visibleSetIDs=@visibleSetIDs\n";
383 :     #warn "prevVisibleSetIDs=@prevVisibleSetIDs\n";
384 :     #warn "selectedSetIDs=@selectedSetIDs\n";
385 :     #warn "editMode=$editMode\n";
386 :    
387 :     ########## get required users
388 :    
389 :     my @Sets = grep { defined $_ } @visibleSetIDs ? $db->getGlobalSets(@visibleSetIDs) : ();
390 :    
391 :     # presort users
392 :     my %sortSubs = %{ SORT_SUBS() };
393 :     my $primarySortSub = $sortSubs{$primarySortField};
394 :     my $secondarySortSub = $sortSubs{$secondarySortField};
395 :    
396 :     # don't forget to sort in opposite order of importance
397 :     @Sets = sort $secondarySortSub @Sets;
398 :     @Sets = sort $primarySortSub @Sets;
399 : sh002i 1660
400 : toenail 2246 ########## print beginning of form
401 :    
402 :     print CGI::start_form({method=>"post", action=>$self->systemLink($urlpath,authen=>0), name=>"problemsetlist"});
403 :     print $self->hidden_authen_fields();
404 :    
405 :     ########## print state data
406 :    
407 :     print "\n<!-- state data here -->\n";
408 :    
409 :     if (@visibleSetIDs) {
410 :     print CGI::hidden(-name=>"visible_sets", -value=>\@visibleSetIDs);
411 :     } else {
412 :     print CGI::hidden(-name=>"no_visible_sets", -value=>"1");
413 :     }
414 :    
415 :     if (@prevVisibleSetIDs) {
416 :     print CGI::hidden(-name=>"prev_visible_sets", -value=>\@prevVisibleSetIDs);
417 :     } else {
418 :     print CGI::hidden(-name=>"no_prev_visible_sets", -value=>"1");
419 :     }
420 :    
421 :     print CGI::hidden(-name=>"editMode", -value=>$editMode);
422 : gage 2536 print CGI::hidden(-name=>"exportMode", -value=>$exportMode);
423 : toenail 2246
424 :     print CGI::hidden(-name=>"primarySortField", -value=>$primarySortField);
425 :     print CGI::hidden(-name=>"secondarySortField", -value=>$secondarySortField);
426 :    
427 :     print "\n<!-- state data here -->\n";
428 :    
429 :     ########## print action forms
430 :    
431 :     print CGI::start_table({});
432 :     print CGI::Tr({}, CGI::td({-colspan=>2}, "Select an action to perform:"));
433 :    
434 :     my @formsToShow;
435 :     if ($editMode) {
436 :     @formsToShow = @{ EDIT_FORMS() };
437 :     } else {
438 :     @formsToShow = @{ VIEW_FORMS() };
439 :     }
440 :    
441 : gage 2536 if ($exportMode) {
442 :     @formsToShow = @{ EXPORT_FORMS() };
443 :     }
444 :    
445 : toenail 2246 my $i = 0;
446 :     foreach my $actionID (@formsToShow) {
447 : gage 2536 # Check permissions
448 :     next if FORM_PERMS()->{$actionID} and not $authz->hasPermissions($user, FORM_PERMS()->{$actionID});
449 : toenail 2246 my $actionForm = "${actionID}_form";
450 :     my $onChange = "document.problemsetlist.action[$i].checked=true";
451 :     my %actionParams = $self->getActionParams($actionID);
452 :    
453 :     print CGI::Tr({-valign=>"top"},
454 :     CGI::td({}, CGI::input({-type=>"radio", -name=>"action", -value=>$actionID})),
455 :     CGI::td({}, $self->$actionForm($onChange, %actionParams))
456 :     );
457 :    
458 :     $i++;
459 :     }
460 :    
461 :     print CGI::Tr({}, CGI::td({-colspan=>2, -align=>"center"},
462 :     CGI::submit(-value=>"Take Action!"))
463 :     );
464 :     print CGI::end_table();
465 :    
466 :     ########## print table
467 :    
468 :     print CGI::p("Showing ", scalar @visibleSetIDs, " out of ", scalar @allSetIDs, " sets.");
469 :    
470 :     $self->printTableHTML(\@Sets, \%prettyFieldNames,
471 :     editMode => $editMode,
472 : gage 2536 exportMode => $exportMode,
473 : toenail 2246 selectedSetIDs => \@selectedSetIDs,
474 :     );
475 :    
476 :    
477 :     ########## print end of form
478 :    
479 :     print CGI::end_form();
480 : sh002i 1660
481 : toenail 2246 return "";
482 :     }
483 : sh002i 1660
484 : toenail 2246 ################################################################################
485 :     # extract particular params and put them in a hash (values are ARRAYREFs!)
486 :     ################################################################################
487 :    
488 :     sub getActionParams {
489 :     my ($self, $actionID) = @_;
490 :     my $r = $self->{r};
491 :    
492 :     my %actionParams;
493 :     foreach my $param ($r->param) {
494 :     next unless $param =~ m/^action\.$actionID\./;
495 :     $actionParams{$param} = [ $r->param($param) ];
496 :     }
497 :     return %actionParams;
498 :     }
499 :    
500 :     sub getTableParams {
501 :     my ($self) = @_;
502 :     my $r = $self->{r};
503 :    
504 :     my %tableParams;
505 :     foreach my $param ($r->param) {
506 :     next unless $param =~ m/^(?:set)\./;
507 :     $tableParams{$param} = [ $r->param($param) ];
508 :     }
509 :     return %tableParams;
510 :     }
511 :    
512 :     ################################################################################
513 :     # actions and action triggers
514 :     ################################################################################
515 :    
516 :     # filter, edit, cancelEdit, and saveEdit should stay with the display module and
517 :     # not be real "actions". that way, all actions are shown in view mode and no
518 :     # actions are shown in edit mode.
519 :    
520 :     sub filter_form {
521 :     my ($self, $onChange, %actionParams) = @_;
522 :     #return CGI::table({}, CGI::Tr({-valign=>"top"},
523 :     # CGI::td({},
524 :     return join("",
525 :     "Show ",
526 :     CGI::popup_menu(
527 :     -name => "action.filter.scope",
528 :     -values => [qw(all none selected match_ids published unpublished)],
529 :     -default => $actionParams{"action.filter.scope"}->[0] || "match_ids",
530 :     -labels => {
531 :     all => "all sets",
532 :     none => "no sets",
533 :     selected => "sets checked below",
534 :     published => "sets visible to students",
535 :     unpublished => "sets hidden from students",
536 :     match_ids => "sets with matching set IDs:",
537 :     },
538 :     -onchange => $onChange,
539 :     ),
540 :     " ",
541 :     CGI::textfield(
542 :     -name => "action.filter.set_ids",
543 :     -value => $actionParams{"action.filter.set_ids"}->[0] || "",,
544 :     -width => "50",
545 :     -onchange => $onChange,
546 :     ),
547 :     " (separate multiple IDs with commas)",
548 :     CGI::br(),
549 :     # "Open dates: ",
550 :     # CGI::popup_menu(
551 :     # -name => "action.filter.open_date",
552 :     # -values => [ keys %{ $self->{open_dates} } ],
553 :     # -default => $actionParams{"action.filter.open_date"}->[0] || "",
554 :     # -labels => { $self->menuLabels($self->{open_dates}) },
555 :     # -onchange => $onChange,
556 :     # ),
557 :     # " Due dates: ",
558 :     # CGI::popup_menu(
559 :     # -name => "action.filter.due_date",
560 :     # -values => [ keys %{ $self->{due_dates} } ],
561 :     # -default => $actionParams{"action.filter.due_date"}->[0] || "",
562 :     # -labels => { $self->menuLabels($self->{due_dates}) },
563 :     # -onchange => $onChange,
564 :     # ),
565 :     # " Answer dates: ",
566 :     # CGI::popup_menu(
567 :     # -name => "action.filter.answer_date",
568 :     # -values => [ keys %{ $self->{answer_dates} } ],
569 :     # -default => $actionParams{"action.filter.answer_date"}->[0] || "",
570 :     # -labels => { $self->menuLabels($self->{answer_dates}) },
571 :     # -onchange => $onChange,
572 :     # ),
573 : sh002i 1660
574 : toenail 2246 );
575 : malsyned 963 }
576 :    
577 : toenail 2246 # this action handler modifies the "visibleUserIDs" field based on the contents
578 :     # of the "action.filter.scope" parameter and the "selected_users"
579 :     sub filter_handler {
580 :     my ($self, $genericParams, $actionParams, $tableParams) = @_;
581 :    
582 :     my $r = $self->r ;
583 :     my $db = $r->db;
584 :    
585 :     my $result;
586 :    
587 :     my $scope = $actionParams->{"action.filter.scope"}->[0];
588 :     if ($scope eq "all") {
589 :     $result = "showing all sets";
590 :     $self->{visibleSetIDs} = $self->{allSetIDs};
591 :     } elsif ($scope eq "none") {
592 :     $result = "showing no sets";
593 :     $self->{visibleSetIDs} = [];
594 :     } elsif ($scope eq "selected") {
595 :     $result = "showing selected sets";
596 :     $self->{visibleSetIDs} = $genericParams->{selected_sets}; # an arrayref
597 :     } elsif ($scope eq "match_ids") {
598 :     my @setIDs = split /\s*,\s*/, $actionParams->{"action.filter.set_ids"}->[0];
599 :     $self->{visibleSetIDs} = \@setIDs;
600 :     } elsif ($scope eq "match_open_date") {
601 :     my $open_date = $actionParams->{"action.filter.open_date"}->[0];
602 :     $self->{visibleSetIDs} = $self->{open_dates}->{$open_date}; # an arrayref
603 :     } elsif ($scope eq "match_due_date") {
604 :     my $due_date = $actionParams->{"action.filter.due_date"}->[0];
605 :     $self->{visibleSetIDs} = $self->{due_date}->{$due_date}; # an arrayref
606 :     } elsif ($scope eq "match_answer_date") {
607 :     my $answer_date = $actionParams->{"action.filter.answer_date"}->[0];
608 :     $self->{visibleSetIDs} = $self->{answer_dates}->{$answer_date}; # an arrayref
609 :     } elsif ($scope eq "published") {
610 :     my @setRecords = $db->getGlobalSets(@{$self->{allSetIDs}});
611 :     my @publishedSetIDs = map { $_->published ? $_->set_id : ""} @setRecords;
612 :     $self->{visibleSetIDs} = \@publishedSetIDs;
613 :     } elsif ($scope eq "unpublished") {
614 :     my @setRecords = $db->getGlobalSets(@{$self->{allSetIDs}});
615 :     my @unpublishedSetIDs = map { (not $_->published) ? $_->set_id : ""} @setRecords;
616 :     $self->{visibleSetIDs} = \@unpublishedSetIDs;
617 :     }
618 :    
619 :     return $result;
620 :     }
621 : gage 1295
622 : toenail 2246 sub sort_form {
623 :     my ($self, $onChange, %actionParams) = @_;
624 :     return join ("",
625 :     "Primary sort: ",
626 :     CGI::popup_menu(
627 :     -name => "action.sort.primary",
628 : gage 2536 -values => [qw(set_id set_header hardcopy_header open_date due_date answer_date published)],
629 : toenail 2246 -default => $actionParams{"action.sort.primary"}->[0] || "due_date",
630 :     -labels => {
631 : toenail 2290 set_id => "Set Name",
632 :     set_header => "Set Header",
633 : gage 2536 hardcopy_header => "Hardcopy Header",
634 : toenail 2290 open_date => "Open Date",
635 :     due_date => "Due Date",
636 :     answer_date => "Answer Date",
637 :     published => "Visibility",
638 : toenail 2246 },
639 :     -onchange => $onChange,
640 :     ),
641 :     " Secondary sort: ",
642 :     CGI::popup_menu(
643 :     -name => "action.sort.secondary",
644 : gage 2536 -values => [qw(set_id set_header hardcopy_header open_date due_date answer_date published)],
645 : toenail 2246 -default => $actionParams{"action.sort.secondary"}->[0] || "open_date",
646 :     -labels => {
647 : toenail 2290 set_id => "Set Name",
648 :     set_header => "Set Header",
649 : gage 2536 hardcopy_header => "Hardcopy Header",
650 : toenail 2290 open_date => "Open Date",
651 :     due_date => "Due Date",
652 :     answer_date => "Answer Date",
653 :     published => "Visibility",
654 : toenail 2246 },
655 :     -onchange => $onChange,
656 :     ),
657 :     ".",
658 :     );
659 :     }
660 : malsyned 836
661 : toenail 2246 sub sort_handler {
662 :     my ($self, $genericParams, $actionParams, $tableParams) = @_;
663 :    
664 :     my $primary = $actionParams->{"action.sort.primary"}->[0];
665 :     my $secondary = $actionParams->{"action.sort.secondary"}->[0];
666 :    
667 :     $self->{primarySortField} = $primary;
668 :     $self->{secondarySortField} = $secondary;
669 : gage 1928
670 : toenail 2246 my %names = (
671 : toenail 2290 set_id => "Set Name",
672 :     set_header => "Set Header",
673 : gage 2536 hardcopy_header => "Hardcopy Header",
674 : toenail 2290 open_date => "Open Date",
675 :     due_date => "Due Date",
676 :     answer_date => "Answer Date",
677 :     published => "Visibility",
678 : toenail 2246 );
679 : gage 1934
680 : toenail 2246 return "sort by $names{$primary} and then by $names{$secondary}.";
681 :     }
682 :    
683 :    
684 :     sub edit_form {
685 :     my ($self, $onChange, %actionParams) = @_;
686 : gage 2536
687 : toenail 2246 return join("",
688 :     "Edit ",
689 :     CGI::popup_menu(
690 :     -name => "action.edit.scope",
691 :     -values => [qw(all visible selected)],
692 :     -default => $actionParams{"action.edit.scope"}->[0] || "selected",
693 :     -labels => {
694 :     all => "all sets",
695 :     visible => "visible sets",
696 :     selected => "selected sets",
697 :     },
698 :     -onchange => $onChange,
699 :     ),
700 :     );
701 :     }
702 :    
703 :     sub edit_handler {
704 :     my ($self, $genericParams, $actionParams, $tableParams) = @_;
705 : toenail 2272
706 : toenail 2246 my $result;
707 :    
708 :     my $scope = $actionParams->{"action.edit.scope"}->[0];
709 :     if ($scope eq "all") {
710 :     $result = "editing all sets";
711 :     $self->{visibleSetIDs} = $self->{allSetIDs};
712 :     } elsif ($scope eq "visible") {
713 :     $result = "editing visible sets";
714 :     # leave visibleUserIDs alone
715 :     } elsif ($scope eq "selected") {
716 :     $result = "editing selected sets";
717 :     $self->{visibleSetIDs} = $genericParams->{selected_sets}; # an arrayref
718 :     }
719 :     $self->{editMode} = 1;
720 :    
721 :     return $result;
722 :     }
723 :    
724 :     sub publish_form {
725 :     my ($self, $onChange, %actionParams) = @_;
726 :    
727 :     return join ("",
728 :     "Make ",
729 :     CGI::popup_menu(
730 :     -name => "action.publish.scope",
731 :     -values => [ qw(none all selected) ],
732 : toenail 2249 -default => $actionParams{"action.publish.scope"}->[0] || "selected",
733 : toenail 2246 -labels => {
734 :     none => "",
735 :     all => "all sets",
736 :     # visible => "visible sets",
737 :     selected => "selected sets",
738 :     },
739 :     -onchange => $onChange,
740 :     ),
741 :     CGI::popup_menu(
742 :     -name => "action.publish.value",
743 :     -values => [ 0, 1 ],
744 :     -default => $actionParams{"action.publish.value"}->[0] || "1",
745 :     -labels => {
746 :     0 => "hidden",
747 :     1 => "visible",
748 :     },
749 :     -onchange => $onChange,
750 :     ),
751 :     " for students.",
752 : gage 1934 );
753 : toenail 2246 }
754 :    
755 :     sub publish_handler {
756 :     my ($self, $genericParams, $actionParams, $tableParams) = @_;
757 : toenail 2272
758 : gage 2536 my $r = $self->r;
759 :     my $db = $r->db;
760 : toenail 2272
761 : toenail 2246 my $result = "";
762 : gage 1606
763 : toenail 2246 my $scope = $actionParams->{"action.publish.scope"}->[0];
764 :     my $value = $actionParams->{"action.publish.value"}->[0];
765 :    
766 :     my $verb = $value ? "made visible for" : "hidden from";
767 : gage 1843
768 : toenail 2246 my @setIDs;
769 :    
770 :     if ($scope eq "none") { # FIXME: double negative "Make no sets hidden" might make professor expect all sets to be made visible.
771 :     @setIDs = ();
772 :     $result = "No change made to any set.";
773 :     } elsif ($scope eq "all") {
774 :     @setIDs = @{ $self->{allSetIDs} };
775 :     $result = "All sets $verb all students.";
776 :     } elsif ($scope eq "visible") {
777 :     @setIDs = @{ $self->{visibleSetIDs} };
778 :     $result = "All visible sets $verb all students.";
779 :     } elsif ($scope eq "selected") {
780 :     @setIDs = @{ $genericParams->{selected_sets} };
781 :     $result = "All selected sets $verb all students.";
782 : malsyned 883 }
783 :    
784 : toenail 2246 my @sets = $db->getGlobalSets(@setIDs);
785 :    
786 :     map { $_->published("$value") if $_; $db->putGlobalSet($_); } @sets;
787 :    
788 :     return $result
789 :    
790 :     }
791 : gage 1606
792 : toenail 2272 sub score_form {
793 :     my ($self, $onChange, %actionParams) = @_;
794 : toenail 2246
795 : toenail 2272 return join ("",
796 :     "Score ",
797 : toenail 2246 CGI::popup_menu(
798 : toenail 2272 -name => "action.score.scope",
799 :     -values => [qw(none all selected)],
800 :     -default => $actionParams{"action.score.scope"}->[0] || "none",
801 : toenail 2246 -labels => {
802 :     none => "no sets.",
803 : toenail 2272 all => "all sets.",
804 : toenail 2246 selected => "selected sets.",
805 :     },
806 :     -onchange => $onChange,
807 :     ),
808 : gage 1934 );
809 : toenail 2272
810 :    
811 :    
812 : toenail 2246 }
813 : gage 1934
814 : toenail 2272 sub score_handler {
815 :     my ($self, $genericParams, $actionParams, $tableParams) = @_;
816 :    
817 :     my $r = $self->r;
818 :     my $urlpath = $r->urlpath;
819 :     my $courseName = $urlpath->arg("courseID");
820 :    
821 :     my $scope = $actionParams->{"action.score.scope"}->[0];
822 :     my @setsToScore;
823 :    
824 :     if ($scope eq "none") {
825 :     @setsToScore = ();
826 :     return "No sets selected for scoring.";
827 :     } elsif ($scope eq "all") {
828 :     @setsToScore = @{ $self->{allSetIDs} };
829 :     } elsif ($scope eq "visible") {
830 :     @setsToScore = @{ $self->{visibleSetIDs} };
831 :     } elsif ($scope eq "selected") {
832 :     @setsToScore = @{ $genericParams->{selected_sets} };
833 :     }
834 :    
835 :     my $uri = $self->systemLink( $urlpath->newFromModule('WeBWorK::ContentGenerator::Instructor::Scoring', courseID=>$courseName),
836 :     params=>{
837 :     scoreSelected=>"Score Selected",
838 :     selectedSet=>\@setsToScore,
839 :     # recordSingleSetScores=>''
840 :     }
841 :     );
842 :    
843 :    
844 :     return $uri;
845 :     }
846 :    
847 :    
848 :     sub delete_form {
849 :     my ($self, $onChange, %actionParams) = @_;
850 :    
851 :     return join("",
852 :     CGI::div({class=>"ResultsWithError"},
853 :     "Delete ",
854 :     CGI::popup_menu(
855 :     -name => "action.delete.scope",
856 :     -values => [qw(none selected)],
857 :     -default => $actionParams{"action.delete.scope"}->[0] || "none",
858 :     -labels => {
859 :     none => "no sets.",
860 :     #visble => "visible sets.",
861 :     selected => "selected sets.",
862 :     },
863 :     -onchange => $onChange,
864 :     ),
865 :     CGI::em(" Deletion destroys all set-related data and is not undoable!"),
866 :     )
867 :     );
868 :     }
869 :    
870 : toenail 2246 sub delete_handler {
871 :     my ($self, $genericParams, $actionParams, $tableParams) = @_;
872 : toenail 2272
873 :     my $r = $self->r;
874 :     my $db = $r->db;
875 :    
876 : toenail 2246 my $scope = $actionParams->{"action.delete.scope"}->[0];
877 : toenail 2272
878 : toenail 2097
879 : toenail 2246 my @setIDsToDelete = ();
880 :    
881 :     if ($scope eq "selected") {
882 :     @setIDsToDelete = @{ $self->{selectedSetIDs} };
883 : malsyned 877 }
884 : sh002i 1804
885 : toenail 2246 my %allSetIDs = map { $_ => 1 } @{ $self->{allSetIDs} };
886 :     my %visibleSetIDs = map { $_ => 1 } @{ $self->{visibleSetIDs} };
887 :     my %selectedSetIDs = map { $_ => 1 } @{ $self->{selectedSetIDs} };
888 :    
889 :     foreach my $setID (@setIDsToDelete) {
890 :     delete $allSetIDs{$setID};
891 :     delete $visibleSetIDs{$setID};
892 :     delete $selectedSetIDs{$setID};
893 :     $db->deleteGlobalSet($setID);
894 : sh002i 1804 }
895 : toenail 2246
896 :     $self->{allSetIDs} = [ keys %allSetIDs ];
897 :     $self->{visibleSetIDs} = [ keys %visibleSetIDs ];
898 :     $self->{selectedSetIDs} = [ keys %selectedSetIDs ];
899 :    
900 :     my $num = @setIDsToDelete;
901 :     return "deleted $num set" . ($num == 1 ? "" : "s");
902 :     }
903 : malsyned 883
904 : toenail 2246 sub create_form {
905 :     my ($self, $onChange, %actionParams) = @_;
906 : toenail 2272
907 :     my $r = $self->r;
908 : toenail 2246
909 :     return "Create a new set named: ",
910 :     CGI::textfield(
911 :     -name => "action.create.name",
912 :     -value => $actionParams{"action.create.name"}->[0] || "",
913 :     -width => "50",
914 :     -onchange => $onChange,
915 :     );
916 :     }
917 : gage 1843
918 : toenail 2246 sub create_handler {
919 :     my ($self, $genericParams, $actionParams, $tableParams) = @_;
920 : toenail 2272
921 :     my $r = $self->r;
922 :     my $db = $r->db;
923 : toenail 2246
924 :     my $newSetRecord = $db->newGlobalSet;
925 :     my $newSetName = $actionParams->{"action.create.name"}->[0];
926 :     return CGI::div({class => "ResultsWithError"}, "Failed to create new set: no set name specified!") unless $newSetName =~ /\S/;
927 :     $newSetRecord->set_id($newSetName);
928 :     $newSetRecord->set_header("");
929 : gage 2536 $newSetRecord->hardcopy_header("");
930 : toenail 2246 $newSetRecord->open_date("0");
931 :     $newSetRecord->due_date("0");
932 :     $newSetRecord->answer_date("0");
933 : toenail 2280 $newSetRecord->published(DEFAULT_PUBLISHED_STATE); # don't want students to see an empty set
934 : toenail 2246 eval {$db->addGlobalSet($newSetRecord)};
935 :    
936 :     return CGI::div({class => "ResultsWithError"}, "Failed to create new set: $@") if $@;
937 :    
938 :     return "Successfully created new set $newSetName";
939 :    
940 :     }
941 :    
942 :     sub import_form {
943 :     my ($self, $onChange, %actionParams) = @_;
944 : gage 2536
945 :     my $r = $self->r;
946 :     my $authz = $r->authz;
947 :     my $user = $r->param('user');
948 : toenail 2272
949 : toenail 2246 # this will make the popup menu alternate between a single selection and a multiple selection menu
950 :     # Note: search by name is required since document.problemsetlist.action.import.number is not seen as
951 :     # a valid reference to the object named 'action.import.number'
952 :     my $importScript = join (" ",
953 :     "var number = document.getElementsByName('action.import.number')[0].value;",
954 :     "document.getElementsByName('action.import.source')[0].size = number;",
955 :     "document.getElementsByName('action.import.source')[0].multiple = (number > 1 ? true : false);",
956 :     "document.getElementsByName('action.import.name')[0].value = (number > 1 ? '(taken from filenames)' : '');",
957 :     );
958 :    
959 :     return join(" ",
960 :     "Import ",
961 :     CGI::popup_menu(
962 :     -name => "action.import.number",
963 :     -values => [ 1, 8 ],
964 :     -default => $actionParams{"action.import.number"}->[0] || "1",
965 :     -labels => {
966 :     1 => "a single set",
967 :     8 => "multiple sets",
968 :     },
969 :     -onchange => "$onChange;$importScript",
970 : gage 1723 ),
971 : toenail 2249 " from ", # set definition file(s) ",
972 : toenail 2246 CGI::popup_menu(
973 :     -name => "action.import.source",
974 :     -values => [ "", $self->getDefList() ],
975 : toenail 2249 -labels => { "" => "the following file(s)" },
976 : toenail 2246 -default => $actionParams{"action.import.source"}->[0] || "",
977 :     -size => $actionParams{"action.import.number"}->[0] || "1",
978 :     -onchange => $onChange,
979 :     ),
980 : toenail 2249 " with set name(s): ",
981 : toenail 2246 CGI::textfield(
982 :     -name => "action.import.name",
983 :     -value => $actionParams{"action.import.name"}->[0] || "",
984 :     -width => "50",
985 :     -onchange => $onChange,
986 :     ),
987 : toenail 2272 ($authz->hasPermissions($user, "assign_problem_sets"))
988 :     ?
989 :     "assigning this set to " .
990 :     CGI::popup_menu(
991 :     -name => "action.import.assign",
992 :     -value => [qw(all none)],
993 :     -default => $actionParams{"action.import.assign"}->[0] || "none",
994 :     -labels => {
995 :     all => "all current users.",
996 :     none => "no users.",
997 :     },
998 :     -onchange => $onChange,
999 :     )
1000 :     :
1001 :     "" #user does not have permissions to assign problem sets
1002 : gage 1400 );
1003 : toenail 2246 }
1004 :    
1005 :     sub import_handler {
1006 :     my ($self, $genericParams, $actionParams, $tableParams) = @_;
1007 : toenail 2272
1008 : toenail 2246 my @fileNames = @{ $actionParams->{"action.import.source"} };
1009 :     my $newSetName = $actionParams->{"action.import.name"}->[0];
1010 : toenail 2290 $newSetName = "" if @fileNames > 1; # cannot assign set names to multiple imports
1011 : toenail 2246 my $assign = $actionParams->{"action.import.assign"}->[0];
1012 :    
1013 :     my ($added, $skipped) = $self->importSetsFromDef($newSetName, $assign, @fileNames);
1014 :    
1015 :     # make new sets visible... do we really want to do this? probably.
1016 :     push @{ $self->{visibleSetIDs} }, @$added;
1017 :     push @{ $self->{allSetIDs} }, @$added;
1018 :    
1019 :     my $numAdded = @$added;
1020 :     my $numSkipped = @$skipped;
1021 :    
1022 :     return $numAdded . " set" . ($numAdded == 1 ? "" : "s") . " added, "
1023 :     . $numSkipped . " set" . ($numSkipped == 1 ? "" : "s") . " skipped.";
1024 : malsyned 836 }
1025 : malsyned 924
1026 : toenail 2246 sub export_form {
1027 :     my ($self, $onChange, %actionParams) = @_;
1028 : gage 2536
1029 : toenail 2246 return join("",
1030 :     "Export ",
1031 :     CGI::popup_menu(
1032 :     -name => "action.export.scope",
1033 :     -values => [qw(all visible selected)],
1034 :     -default => $actionParams{"action.export.scope"}->[0] || "visible",
1035 :     -labels => {
1036 :     all => "all sets",
1037 :     visible => "visible sets",
1038 :     selected => "selected sets",
1039 :     },
1040 :     -onchange => $onChange,
1041 :     ),
1042 :     );
1043 :     }
1044 : gage 1428
1045 : gage 2536 # this does not actually export any files, rather it sends us to a new page in order to export the files
1046 : toenail 2246 sub export_handler {
1047 :     my ($self, $genericParams, $actionParams, $tableParams) = @_;
1048 : gage 2536
1049 :     my $result;
1050 : toenail 2246
1051 :     my $scope = $actionParams->{"action.export.scope"}->[0];
1052 : gage 2536 if ($scope eq "all") {
1053 :     $result = "exporting all sets";
1054 :     $self->{selectedSetIDs} = $self->{visibleSetIDs} = $self->{allSetIDs};
1055 :    
1056 :     } elsif ($scope eq "visible") {
1057 :     $result = "exporting visible sets";
1058 :     $self->{selectedSetIDs} = $self->{visibleSetIDs};
1059 :     } elsif ($scope eq "selected") {
1060 :     $result = "exporting selected sets";
1061 :     $self->{selectedSetIDs} = $self->{visibleSetIDs} = $genericParams->{selected_sets}; # an arrayref
1062 :     }
1063 :     $self->{exportMode} = 1;
1064 : toenail 2246
1065 : gage 2536 return $result;
1066 :     }
1067 :    
1068 :     sub cancelExport_form {
1069 :     my ($self, $onChange, %actionParams) = @_;
1070 :     return "Abandon export";
1071 :     }
1072 :    
1073 :     sub cancelExport_handler {
1074 :     my ($self, $genericParams, $actionParams, $tableParams) = @_;
1075 :     my $r = $self->r;
1076 :    
1077 :     #$self->{selectedSetIDs) = $self->{visibleSetIDs};
1078 :     # only do the above if we arrived here via "edit selected users"
1079 :     if (defined $r->param("prev_visible_sets")) {
1080 :     $self->{visibleSetIDs} = [ $r->param("prev_visible_sets") ];
1081 :     } elsif (defined $r->param("no_prev_visible_sets")) {
1082 :     $self->{visibleSetIDs} = [];
1083 : toenail 2246 } else {
1084 : gage 2536 # leave it alone
1085 : toenail 2246 }
1086 : gage 2536 $self->{exportMode} = 0;
1087 : toenail 2246
1088 : gage 2536 return "export abandoned";
1089 :     }
1090 :    
1091 :     sub saveExport_form {
1092 :     my ($self, $onChange, %actionParams) = @_;
1093 :     return "Export selected sets (This may take a long time. Even if your browser times out, all the files will be exported).";
1094 :     }
1095 :    
1096 :     sub saveExport_handler {
1097 :     my ($self, $genericParams, $actionParams, $tableParams) = @_;
1098 :     my $r = $self->r;
1099 :     my $db = $r->db;
1100 : toenail 2246
1101 : gage 2536 my @setIDsToExport = @{ $self->{selectedSetIDs} };
1102 : gage 1428
1103 : gage 2536 my %filenames = map { $_ => (@{ $tableParams->{"set.$_"} }[0] || $_) } @setIDsToExport;
1104 :    
1105 :     my ($exported, $skipped, $reason) = $self->exportSetsToDef(%filenames);
1106 :    
1107 :     if (defined $r->param("prev_visible_sets")) {
1108 :     $self->{visibleSetIDs} = [ $r->param("prev_visible_sets") ];
1109 :     } elsif (defined $r->param("no_prev_visble_sets")) {
1110 :     $self->{visibleSetIDs} = [];
1111 :     } else {
1112 :     # leave it alone
1113 : toenail 2246 }
1114 :    
1115 : gage 2536 $self->{exportMode} = 0;
1116 :    
1117 :     my $numExported = @$exported;
1118 :     my $numSkipped = @$skipped;
1119 :    
1120 :     my @reasons = map { "set $_ - " . $reason->{$_} } keys %$reason;
1121 :    
1122 :     return $numExported . " set" . ($numExported == 1 ? "" : "s") . " exported, "
1123 :     . $numSkipped . " set" . ($numSkipped == 1 ? "" : "s") . " skipped."
1124 :     . (($numSkipped) ? CGI::ul(CGI::li(\@reasons)) : "");
1125 :    
1126 : toenail 2246 }
1127 :    
1128 :     sub cancelEdit_form {
1129 :     my ($self, $onChange, %actionParams) = @_;
1130 :     return "Abandon changes";
1131 :     }
1132 :    
1133 :     sub cancelEdit_handler {
1134 :     my ($self, $genericParams, $actionParams, $tableParams) = @_;
1135 :     my $r = $self->r;
1136 :    
1137 :     #$self->{selectedSetIDs) = $self->{visibleSetIDs};
1138 :     # only do the above if we arrived here via "edit selected users"
1139 :     if (defined $r->param("prev_visible_sets")) {
1140 :     $self->{visibleSetIDs} = [ $r->param("prev_visible_sets") ];
1141 :     } elsif (defined $r->param("no_prev_visible_sets")) {
1142 :     $self->{visibleSetIDs} = [];
1143 :     } else {
1144 :     # leave it alone
1145 :     }
1146 :     $self->{editMode} = 0;
1147 :    
1148 :     return "changes abandoned";
1149 :     }
1150 :    
1151 :     sub saveEdit_form {
1152 :     my ($self, $onChange, %actionParams) = @_;
1153 :     return "Save changes";
1154 :     }
1155 :    
1156 :     sub saveEdit_handler {
1157 :     my ($self, $genericParams, $actionParams, $tableParams) = @_;
1158 :     my $r = $self->r;
1159 :     my $db = $r->db;
1160 :    
1161 :     my @visibleSetIDs = @{ $self->{visibleSetIDs} };
1162 :     foreach my $setID (@visibleSetIDs) {
1163 :     my $Set = $db->getGlobalSet($setID); # checked
1164 :     # FIXME: we may not want to die on bad sets, they're not as bad as bad users
1165 :     die "record for visible set $setID not found" unless $Set;
1166 :    
1167 :     foreach my $field ($Set->NONKEYFIELDS()) {
1168 :     my $param = "set.${setID}.${field}";
1169 :     if (defined $tableParams->{$param}->[0]) {
1170 :     if ($field =~ /_date/) {
1171 :     $Set->$field(parseDateTime($tableParams->{$param}->[0]));
1172 :     } else {
1173 :     $Set->$field($tableParams->{$param}->[0]);
1174 :     }
1175 :     }
1176 :     }
1177 :    
1178 :     ###################################################
1179 :     # Check that the open, due and answer dates are in increasing order.
1180 :     # Bail if this is not correct.
1181 :     ###################################################
1182 :     if ($Set->open_date > $Set->due_date) {
1183 :     return CGI::div({class=>'ResultsWithError'}, "Error: Due date must come after open date in set $setID");
1184 :     }
1185 :     if ($Set->due_date > $Set->answer_date) {
1186 :     return CGI::div({class=>'ResultsWithError'}, "Error: Answer date must come after due date in set $setID");
1187 :     }
1188 :     ###################################################
1189 :     # End date check section.
1190 :     ###################################################
1191 :     $db->putGlobalSet($Set);
1192 :     }
1193 :    
1194 :     if (defined $r->param("prev_visible_sets")) {
1195 :     $self->{visibleSetIDs} = [ $r->param("prev_visible_sets") ];
1196 :     } elsif (defined $r->param("no_prev_visble_sets")) {
1197 :     $self->{visibleSetIDs} = [];
1198 :     } else {
1199 :     # leave it alone
1200 :     }
1201 :    
1202 :     $self->{editMode} = 0;
1203 :    
1204 :     return "changes saved";
1205 :     }
1206 :    
1207 :     ################################################################################
1208 :     # sorts
1209 :     ################################################################################
1210 :    
1211 :     sub bySetID { $a->set_id cmp $b->set_id }
1212 :     sub bySetHeader { $a->set_header cmp $b->set_header }
1213 : gage 2536 sub byHardcopyHeader { $a->hardcopy_header cmp $b->hardcopy_header }
1214 : toenail 2246 sub byOpenDate { $a->open_date <=> $b->open_date }
1215 :     sub byDueDate { $a->due_date <=> $b->due_date }
1216 :     sub byAnswerDate { $a->answer_date <=> $b->answer_date }
1217 :     sub byPublished { $a->published cmp $b->published }
1218 :    
1219 :     sub byOpenDue { &byOpenDate || &byDueDate }
1220 :    
1221 :     ################################################################################
1222 :     # utilities
1223 :     ################################################################################
1224 :    
1225 :     # generate labels for open_date/due_date/answer_date popup menus
1226 :     sub menuLabels {
1227 :     my ($self, $hashRef) = @_;
1228 :     my %hash = %$hashRef;
1229 :    
1230 :     my %result;
1231 :     foreach my $key (keys %hash) {
1232 :     my $count = @{ $hash{$key} };
1233 :     my $displayKey = formatDateTime($key) || "<none>";
1234 :     $result{$key} = "$displayKey ($count sets)";
1235 :     }
1236 :     return %result;
1237 :     }
1238 :    
1239 :     sub importSetsFromDef {
1240 :     my ($self, $newSetName, $assign, @setDefFiles) = @_;
1241 :     my $r = $self->r;
1242 :     my $ce = $r->ce;
1243 :     my $db = $r->db;
1244 :     my $dir = $ce->{courseDirs}->{templates};
1245 :    
1246 :     # FIXME: do we really want everything to fail on one bad file name?
1247 :     foreach my $fileName (@setDefFiles) {
1248 :     die "won't be able to read from file $dir/$fileName: does it exist? is it readable?"
1249 :     unless -r "$dir/$fileName";
1250 :     }
1251 :    
1252 :     my @allSetIDs = $db->listGlobalSets();
1253 :     # FIXME: getGlobalSets takes a lot of time just for checking to see if a set already exists
1254 :     # this could be avoided by waiting until the call to addGlobalSet below
1255 :     # and checking to see if the error message says that the set already exists
1256 :     # but if the error message is ever changed the code here might be broken
1257 :     # then again, one call to getGlobalSets and skipping unnecessary calls to addGlobalSet
1258 :     # could be faster than no call to getGlobalSets and lots of unnecessary calls to addGlobalSet
1259 :     my %allSets = map { $_->set_id => 1 if $_} $db->getGlobalSets(@allSetIDs); # checked
1260 :    
1261 :     my (@added, @skipped);
1262 :    
1263 :     foreach my $set_definition_file (@setDefFiles) {
1264 :    
1265 :     $WeBWorK::timer->continue("$set_definition_file: reading set definition file") if defined $WeBWorK::timer;
1266 :     # read data in set definition file
1267 : glarose 2343 my ($setName, $paperHeaderFile, $screenHeaderFile, $openDate,
1268 :     $dueDate, $answerDate, $ra_problemData,
1269 :     $assignmentType, $attemptsPerVersion, $timeInterval,
1270 :     $versionsPerInterval, $versionTimeLimit,
1271 :     $problemRandOrder) = $self->readSetDef($set_definition_file);
1272 : toenail 2246 my @problemList = @{$ra_problemData};
1273 :    
1274 :     # Use the original name if form doesn't specify a new one.
1275 :     # The set acquires the new name specified by the form. A blank
1276 :     # entry on the form indicates that the imported set name will be used.
1277 :     $setName = $newSetName if $newSetName;
1278 :    
1279 :     if ($allSets{$setName}) {
1280 :     # this set already exists!!
1281 :     push @skipped, $setName;
1282 :     next;
1283 :     } else {
1284 :     push @added, $setName;
1285 :     }
1286 :    
1287 :     $WeBWorK::timer->continue("$set_definition_file: adding set") if defined $WeBWorK::timer;
1288 :     # add the data to the set record
1289 :     my $newSetRecord = $db->newGlobalSet;
1290 :     $newSetRecord->set_id($setName);
1291 :     $newSetRecord->set_header($screenHeaderFile);
1292 : gage 2536 $newSetRecord->hardcopy_header($paperHeaderFile);
1293 : toenail 2246 $newSetRecord->open_date($openDate);
1294 :     $newSetRecord->due_date($dueDate);
1295 :     $newSetRecord->answer_date($answerDate);
1296 : toenail 2280 $newSetRecord->published(DEFAULT_PUBLISHED_STATE);
1297 : toenail 2246
1298 : glarose 2343 # gateway/version data. I'm not sure why I'm bothering to put these in a conditional.
1299 :     # in that we return '' for missing gateway data, it should just keep all of these
1300 :     # values null for non-versioned/non-gateway sets
1301 :     $newSetRecord->assignment_type($assignmentType) if ( $assignmentType );
1302 :     $newSetRecord->attempts_per_version($attemptsPerVersion) if ( $attemptsPerVersion );
1303 :     $newSetRecord->time_interval($timeInterval) if ( $timeInterval );
1304 :     $newSetRecord->versions_per_interval($versionsPerInterval) if ( $versionsPerInterval );
1305 :     $newSetRecord->version_time_limit($versionTimeLimit) if ( $versionTimeLimit );
1306 :     $newSetRecord->problem_randorder($problemRandOrder) if ( $problemRandOrder );
1307 :    
1308 : toenail 2246 #create the set
1309 :     eval {$db->addGlobalSet($newSetRecord)};
1310 :     die "addGlobalSet $setName in ProblemSetList: $@" if $@;
1311 :    
1312 :     $WeBWorK::timer->continue("$set_definition_file: adding problems to database") if defined $WeBWorK::timer;
1313 :     # add problems
1314 :     my $freeProblemID = WeBWorK::Utils::max($db->listGlobalProblems($setName)) + 1;
1315 :     foreach my $rh_problem (@problemList) {
1316 : jj 2284 $self->addProblemToSet(
1317 :     setName => $setName,
1318 :     sourceFile => $rh_problem->{source_file},
1319 :     problemID => $freeProblemID++,
1320 :     value => $rh_problem->{value},
1321 :     maxAttempts => $rh_problem->{max_attempts});
1322 : toenail 2246 }
1323 :    
1324 :    
1325 :     if ($assign eq "all") {
1326 :     $self->assignSetToAllUsers($setName);
1327 :     }
1328 :     }
1329 :    
1330 :    
1331 :     return \@added, \@skipped;
1332 :     }
1333 :    
1334 : gage 1428 sub readSetDef {
1335 : toenail 2246 my ($self, $fileName) = @_;
1336 : gage 1428 my $templateDir = $self->{ce}->{courseDirs}->{templates};
1337 :     my $filePath = "$templateDir/$fileName";
1338 :    
1339 : toenail 2246 my $setName = '';
1340 : gage 1428
1341 : toenail 2246 if ($fileName =~ m|^set([\w-]+)\.def$|) {
1342 :     $setName = $1;
1343 :     } else {
1344 :     warn qq{The setDefinition file name must begin with <CODE>set</CODE>},
1345 :     qq{and must end with <CODE>.def</CODE> . Every thing in between becomes the name of the set. },
1346 :     qq{For example <CODE>set1.def</CODE>, <CODE>setExam.def</CODE>, and <CODE>setsample7.def</CODE> },
1347 :     qq{define sets named <CODE>1</CODE>, <CODE>Exam</CODE>, and <CODE>sample7</CODE> respectively. },
1348 :     qq{The filename, $fileName, you entered is not legal\n };
1349 :    
1350 :     }
1351 :    
1352 :     my ($line, $name, $value, $attemptLimit, $continueFlag);
1353 : gage 1428 my $paperHeaderFile = '';
1354 :     my $screenHeaderFile = '';
1355 : toenail 2246 my ($dueDate, $openDate, $answerDate);
1356 : gage 1428 my @problemData;
1357 : toenail 2246
1358 : glarose 2343 # added fields for gateway test/versioned set definitions:
1359 :     my ( $assignmentType, $attemptsPerVersion, $timeInterval,
1360 :     $versionsPerInterval, $versionTimeLimit, $problemRandOrder ) =
1361 :     ('')x6; # initialize these to ''
1362 : toenail 2246
1363 : glarose 2343
1364 : toenail 2246 my %setInfo;
1365 :     if ( open (SETFILENAME, "$filePath") ) {
1366 : gage 1428 #####################################################################
1367 :     # Read and check set data
1368 :     #####################################################################
1369 :     while (<SETFILENAME>) {
1370 : toenail 2246
1371 : gage 1428 chomp($line = $_);
1372 :     $line =~ s|(#.*)||; ## don't read past comments
1373 :     unless ($line =~ /\S/) {next;} ## skip blank lines
1374 :     $line =~ s|\s*$||; ## trim trailing spaces
1375 :     $line =~ m|^\s*(\w+)\s*=\s*(.*)|;
1376 : gage 1726
1377 :     ######################
1378 :     # sanity check entries
1379 :     ######################
1380 :     my $item = $1;
1381 :     $item = '' unless defined $item;
1382 :     my $value = $2;
1383 :     $value = '' unless defined $value;
1384 :    
1385 :     if ($item eq 'setNumber') {
1386 : gage 1428 next;
1387 : gage 1726 } elsif ($item eq 'paperHeaderFile') {
1388 :     $paperHeaderFile = $value;
1389 :     } elsif ($item eq 'screenHeaderFile') {
1390 :     $screenHeaderFile = $value;
1391 :     } elsif ($item eq 'dueDate') {
1392 :     $dueDate = $value;
1393 :     } elsif ($item eq 'openDate') {
1394 :     $openDate = $value;
1395 : toenail 2246 } elsif ($item eq 'answerDate') {
1396 : gage 1726 $answerDate = $value;
1397 : glarose 2343 } elsif ($item eq 'assignmentType') {
1398 :     $assignmentType = $value;
1399 :     } elsif ($item eq 'attemptsPerVersion') {
1400 :     $attemptsPerVersion = $value;
1401 :     } elsif ($item eq 'timeInterval') {
1402 :     $timeInterval = $value;
1403 :     } elsif ($item eq 'versionsPerInterval') {
1404 :     $versionsPerInterval = $value;
1405 :     } elsif ($item eq 'versionTimeLimit') {
1406 :     $versionTimeLimit = $value;
1407 :     } elsif ($item eq 'problemRandOrder') {
1408 :     $problemRandOrder = $value;
1409 : gage 1726 } elsif ($item eq 'problemList') {
1410 : gage 1428 last;
1411 :     } else {
1412 : gage 1726 warn "readSetDef error, can't read the line: ||$line||";
1413 : gage 1428 }
1414 :     }
1415 : toenail 2246
1416 :     #####################################################################
1417 :     # Check and format dates
1418 :     #####################################################################
1419 :     my ($time1, $time2, $time3) = map { $_ =~ s/\s*at\s*/ /; WeBWorK::Utils::parseDateTime($_); } ($openDate, $dueDate, $answerDate);
1420 : gage 1428
1421 :     unless ($time1 <= $time2 and $time2 <= $time3) {
1422 : toenail 2246 warn "The open date: $openDate, due date: $dueDate, and answer date: $answerDate must be defined and in chronological order.";
1423 : gage 1428 }
1424 : toenail 2246
1425 :     # Check header file names
1426 : gage 1428 $paperHeaderFile =~ s/(.*?)\s*$/$1/; #remove trailing white space
1427 :     $screenHeaderFile =~ s/(.*?)\s*$/$1/; #remove trailing white space
1428 : glarose 2343
1429 :     #####################################################################
1430 :     # Gateway/version variable cleanup
1431 :    
1432 :     $assignmentType =~ s/(.*?)\s*/$1/; # remove trailing
1433 :     $attemptsPerVersion =~ s/(.*?)\s*/$1/; # white space
1434 :     $timeInterval =~ s/(.*?)\s*/$1/;
1435 :     $versionsPerInterval =~ s/(.*?)\s*/$1/;
1436 :     $versionTimeLimit =~ s/(.*?)\s*/$1/;
1437 :     $problemRandOrder =~ s/(.*?)\s*/$1/;
1438 :    
1439 :     # convert times into seconds
1440 : glarose 3275 $timeInterval = WeBWorK::Utils::timeToSec( $timeInterval )
1441 :     if ( $timeInterval );
1442 :     $versionTimeLimit = WeBWorK::Utils::timeToSec( $versionTimeLimit )
1443 :     if ( $versionTimeLimit );
1444 : gage 1428
1445 : toenail 2246 #####################################################################
1446 :     # Read and check list of problems for the set
1447 :     #####################################################################
1448 : gage 1428 while(<SETFILENAME>) {
1449 :     chomp($line=$_);
1450 :     $line =~ s/(#.*)//; ## don't read past comments
1451 :     unless ($line =~ /\S/) {next;} ## skip blank lines
1452 :    
1453 :     ($name, $value, $attemptLimit, $continueFlag) = split (/\s*,\s*/,$line);
1454 :     #####################
1455 :     # clean up problem values
1456 :     ###########################
1457 :     $name =~ s/\s*//g;
1458 :     $value = "" unless defined($value);
1459 :     $value =~ s/[^\d\.]*//g;
1460 :     unless ($value =~ /\d+/) {$value = 1;}
1461 :     $attemptLimit = "" unless defined($attemptLimit);
1462 :     $attemptLimit =~ s/[^\d-]*//g;
1463 :     unless ($attemptLimit =~ /\d+/) {$attemptLimit = -1;}
1464 :     $continueFlag = "0" unless( defined($continueFlag) && @problemData );
1465 : toenail 2246 # can't put continuation flag onto the first problem
1466 : gage 1428 push(@problemData, {source_file => $name,
1467 :     value => $value,
1468 :     max_attempts =>, $attemptLimit,
1469 :     continuation => $continueFlag
1470 :     });
1471 :     }
1472 :     close(SETFILENAME);
1473 : toenail 2246 ($setName,
1474 : gage 1428 $paperHeaderFile,
1475 :     $screenHeaderFile,
1476 :     $time1,
1477 :     $time2,
1478 :     $time3,
1479 :     \@problemData,
1480 : glarose 2343 $assignmentType, $attemptsPerVersion, $timeInterval,
1481 :     $versionsPerInterval, $versionTimeLimit, $problemRandOrder,
1482 : gage 1428 );
1483 :     } else {
1484 :     warn "Can't open file $filePath\n";
1485 :     }
1486 :     }
1487 :    
1488 : gage 2536 sub exportSetsToDef {
1489 :     my ($self, %filenames) = @_;
1490 :    
1491 :     my $r = $self->r;
1492 :     my $ce = $r->ce;
1493 :     my $db = $r->db;
1494 :    
1495 :     my (@exported, @skipped, %reason);
1496 :    
1497 :     SET: foreach my $set (keys %filenames) {
1498 :    
1499 :     my $fileName = $filenames{$set};
1500 :     $fileName .= ".def" unless $fileName =~ m/\.def$/;
1501 :     $fileName = "set" . $fileName unless $fileName =~ m/^set/;
1502 :     # files can be exported to sub directories but not parent directories
1503 :     if ($fileName =~ /\.\./) {
1504 :     push @skipped, $set;
1505 :     $reason{$set} = "Illegal filename contains '..'";
1506 :     next SET;
1507 :     }
1508 :    
1509 :     my $setRecord = $db->getGlobalSet($set);
1510 :     unless (defined $setRecord) {
1511 :     push @skipped, $set;
1512 :     $reason{$set} = "No record found.";
1513 :     next SET;
1514 :     }
1515 :     my $filePath = $ce->{courseDirs}->{templates} . '/' . $fileName;
1516 :    
1517 :     # back up existing file
1518 :     if(-e $filePath) {
1519 :     rename($filePath, "$filePath.bak") or
1520 :     $reason{$set} = "Existing file $filePath could not be backed up and was lost.";
1521 :     }
1522 :    
1523 :     my $openDate = formatDateTime($setRecord->open_date);
1524 :     my $dueDate = formatDateTime($setRecord->due_date);
1525 :     my $answerDate = formatDateTime($setRecord->answer_date);
1526 :     my $setHeader = $setRecord->set_header;
1527 :     my @problemList = $db->listGlobalProblems($set);
1528 :    
1529 :     my $problemList = '';
1530 :     foreach my $prob (sort {$a <=> $b} @problemList) {
1531 :     my $problemRecord = $db->getGlobalProblem($set, $prob); # checked
1532 :     unless (defined $problemRecord) {
1533 :     push @skipped, $set;
1534 :     $reason{$set} = "No record found for problem $prob.";
1535 :     next SET;
1536 :     }
1537 :     my $source_file = $problemRecord->source_file();
1538 :     my $value = $problemRecord->value();
1539 :     my $max_attempts = $problemRecord->max_attempts();
1540 :     $problemList .= "$source_file, $value, $max_attempts \n";
1541 :     }
1542 :     my $fileContents = <<EOF;
1543 :    
1544 :     openDate = $openDate
1545 :     dueDate = $dueDate
1546 :     answerDate = $answerDate
1547 :     paperHeaderFile = $setHeader
1548 :     screenHeaderFile = $setHeader
1549 :     problemList =
1550 :    
1551 :     $problemList
1552 :    
1553 :    
1554 :    
1555 :     EOF
1556 :    
1557 :     $filePath = WeBWorK::Utils::surePathToFile($ce->{courseDirs}->{templates}, $filePath);
1558 :     eval {
1559 :     local *SETDEF;
1560 :     open SETDEF, ">$filePath" or die "Failed to open $filePath";
1561 :     print SETDEF $fileContents;
1562 :     close SETDEF;
1563 :     };
1564 :    
1565 :     if ($@) {
1566 :     push @skipped, $set;
1567 :     $reason{$set} = $@;
1568 :     } else {
1569 :     push @exported, $set;
1570 :     }
1571 :    
1572 :     }
1573 :    
1574 :     return \@exported, \@skipped, \%reason;
1575 :    
1576 :     }
1577 :    
1578 :    
1579 : toenail 2246 # search recursively through a directory looking for all filenames matching a given pattern
1580 :     sub recurseDirectory {
1581 :    
1582 :     my ($self, $dir, $pattern) = @_;
1583 :    
1584 :     my @dirs = grep {$_ ne "." and $_ ne ".." and $_ ne "Library" and $_ ne "CVS" and -d "$dir/$_"} readDirectory($dir);
1585 :    
1586 :     my @files = map { "$dir/$_" } $self->read_dir($dir, $pattern);
1587 :    
1588 :     foreach (@dirs) {
1589 :     push (@files, $self->recurseDirectory("$dir/$_", $pattern));
1590 :     }
1591 :    
1592 :     return @files;
1593 :     }
1594 :    
1595 :     ################################################################################
1596 :     # "display" methods
1597 :     ################################################################################
1598 :    
1599 :     sub fieldEditHTML {
1600 :     my ($self, $fieldName, $value, $properties) = @_;
1601 :     my $size = $properties->{size};
1602 :     my $type = $properties->{type};
1603 :     my $access = $properties->{access};
1604 :     my $items = $properties->{items};
1605 :     my $synonyms = $properties->{synonyms};
1606 :     my $headerFiles = $self->{headerFiles};
1607 :    
1608 :     if ($access eq "readonly") {
1609 :     return $value;
1610 :     }
1611 :    
1612 :     if ($type eq "number" or $type eq "text") {
1613 :     return CGI::input({type=>"text", name=>$fieldName, value=>$value, size=>$size});
1614 :     }
1615 :    
1616 :     if ($type eq "filelist") {
1617 :     return CGI::popup_menu({
1618 :     name => $fieldName,
1619 :     value => [ sort keys %$headerFiles ],
1620 :     labels => $headerFiles,
1621 :     default => $value || 0,
1622 :     });
1623 :     }
1624 :    
1625 :     if ($type eq "enumerable") {
1626 :     my $matched = undef; # Whether a synonym match has occurred
1627 :    
1628 :     # Process synonyms for enumerable objects
1629 :     foreach my $synonym (keys %$synonyms) {
1630 :     if ($synonym ne "*" and $value =~ m/$synonym/) {
1631 :     $value = $synonyms->{$synonym};
1632 :     $matched = 1;
1633 :     }
1634 :     }
1635 :    
1636 :     if (!$matched and exists $synonyms->{"*"}) {
1637 :     $value = $synonyms->{"*"};
1638 :     }
1639 :    
1640 :     return CGI::popup_menu({
1641 :     name => $fieldName,
1642 :     values => [keys %$items],
1643 :     default => $value,
1644 :     labels => $items,
1645 :     });
1646 :     }
1647 :    
1648 :     if ($type eq "checked") {
1649 :    
1650 :     # FIXME: kludge (R)
1651 :     # if the checkbox is checked it returns a 1, if it is unchecked it returns nothing
1652 :     # in which case the hidden field overrides the parameter with a 0
1653 :     return CGI::checkbox(
1654 :     -name => $fieldName,
1655 :     -checked => $value,
1656 :     -label => "",
1657 :     -value => 1
1658 :     ) . CGI::hidden(
1659 :     -name => $fieldName,
1660 :     -value => 0
1661 :     );
1662 :     }
1663 :     }
1664 :    
1665 :     sub recordEditHTML {
1666 :     my ($self, $Set, %options) = @_;
1667 :     my $r = $self->r;
1668 :     my $urlpath = $r->urlpath;
1669 :     my $ce = $r->ce;
1670 :     my $db = $r->db;
1671 : gage 2536 my $authz = $r->authz;
1672 :     my $user = $r->param('user');
1673 : toenail 2246 my $root = $ce->{webworkURLs}->{root};
1674 :     my $courseName = $urlpath->arg("courseID");
1675 :    
1676 :     my $editMode = $options{editMode};
1677 : gage 2536 my $exportMode = $options{exportMode};
1678 : toenail 2246 my $setSelected = $options{setSelected};
1679 :    
1680 :     my $publishedClass = $Set->published ? "Published" : "Unpublished";
1681 :    
1682 :     my $users = $db->countSetUsers($Set->set_id);
1683 :     my $totalUsers = $self->{totalUsers};
1684 :     my $problems = $db->listGlobalProblems($Set->set_id);
1685 :    
1686 :     my $usersAssignedToSetURL = $self->systemLink($urlpath->new(type=>'instructor_users_assigned_to_set', args=>{courseID => $courseName, setID => $Set->set_id} ));
1687 :     my $problemListURL = $self->systemLink($urlpath->new(type=>'instructor_problem_list', args=>{courseID => $courseName, setID => $Set->set_id} ));
1688 : toenail 2285 my $problemSetListURL = $self->systemLink($urlpath->new(type=>'instructor_set_list', args=>{courseID => $courseName, setID => $Set->set_id})) . "&editMode=1&visible_sets=" . $Set->set_id;
1689 :     my $imageURL = $ce->{webworkURLs}->{htdocs}."/images/edit.gif";
1690 :     my $imageLink = CGI::a({href => $problemSetListURL}, CGI::img({src=>$imageURL, border=>0}));
1691 : toenail 2246
1692 :     my @tableCells;
1693 :     my %fakeRecord;
1694 : gage 2536 my $set_id = $Set->set_id;
1695 :     $fakeRecord{select} = CGI::checkbox(-name => "selected_sets", -value => $set_id, -checked => $setSelected, -label => "", );
1696 :     $fakeRecord{set_id} = CGI::font({class=>$publishedClass}, $set_id) . ($editMode ? "" : $imageLink);
1697 :     $fakeRecord{problems} = (FIELD_PERMS()->{problems} and not $authz->hasPermissions($user, FIELD_PERMS()->{problems}))
1698 :     ? "$problems"
1699 :     : CGI::a({href=>$problemListURL}, "$problems");
1700 :     $fakeRecord{users} = (FIELD_PERMS()->{users} and not $authz->hasPermissions($user, FIELD_PERMS()->{users}))
1701 :     ? "$users/$totalUsers"
1702 :     : CGI::a({href=>$usersAssignedToSetURL}, "$users/$totalUsers");
1703 :     $fakeRecord{filename} = CGI::input({-name => "set.$set_id", -value=>"set$set_id.def", -size=>60});
1704 :    
1705 : toenail 2246
1706 :     # Select
1707 :     if ($editMode) {
1708 :     # column not there
1709 :     } else {
1710 :     # selection checkbox
1711 :     push @tableCells, CGI::checkbox(
1712 :     -name => "selected_sets",
1713 : gage 2536 -value => $set_id,
1714 : toenail 2246 -checked => $setSelected,
1715 :     -label => "",
1716 :     );
1717 :     }
1718 :    
1719 :     # Set ID
1720 : gage 2536 push @tableCells, CGI::font({class=>$publishedClass}, $set_id . $imageLink);
1721 : toenail 2246
1722 :     # Problems link
1723 :     if ($editMode) {
1724 :     # column not there
1725 :     } else {
1726 :     # "problem list" link
1727 :     push @tableCells, CGI::a({href=>$problemListURL}, "$problems");
1728 :     }
1729 :    
1730 :     # Users link
1731 :     if ($editMode) {
1732 :     # column not there
1733 :     } else {
1734 :     # "edit users assigned to set" link
1735 :     push @tableCells, CGI::a({href=>$usersAssignedToSetURL}, "$users/$totalUsers");
1736 :     }
1737 :    
1738 :     # Set Fields
1739 :     foreach my $field ($Set->NONKEYFIELDS) {
1740 : gage 2536 my $fieldName = "set." . $set_id . "." . $field,
1741 : toenail 2246 my $fieldValue = $Set->$field;
1742 :     my %properties = %{ FIELD_PROPERTIES()->{$field} };
1743 :     $properties{access} = "readonly" unless $editMode;
1744 :     $fieldValue = formatDateTime($fieldValue) if $field =~ /_date/;
1745 : glarose 2343 $fieldValue = '' if ( ! defined( $fieldValue ) );
1746 : toenail 2246 $fieldValue =~ s/ /&nbsp;/g unless $editMode;
1747 :     $fieldValue = ($fieldValue) ? "Yes" : "No" if $field =~ /published/ and not $editMode;
1748 :     push @tableCells, CGI::font({class=>$publishedClass}, $self->fieldEditHTML($fieldName, $fieldValue, \%properties));
1749 :     $fakeRecord{$field} = CGI::font({class=>$publishedClass}, $self->fieldEditHTML($fieldName, $fieldValue, \%properties));
1750 :     }
1751 :    
1752 :     my @fieldsToShow;
1753 :     if ($editMode) {
1754 :     @fieldsToShow = @{ EDIT_FIELD_ORDER() };
1755 :     } else {
1756 :     @fieldsToShow = @{ VIEW_FIELD_ORDER() };
1757 :     }
1758 : gage 2536
1759 :     if ($exportMode) {
1760 :     @fieldsToShow = @{ EXPORT_FIELD_ORDER() };
1761 :     }
1762 : toenail 2246
1763 :     @tableCells = map { $fakeRecord{$_} } @fieldsToShow;
1764 :    
1765 :     return CGI::Tr({}, CGI::td({}, \@tableCells));
1766 :     }
1767 :    
1768 :     sub printTableHTML {
1769 :     my ($self, $SetsRef, $fieldNamesRef, %options) = @_;
1770 :     my $r = $self->r;
1771 : gage 2536 my $authz = $r->authz;
1772 :     my $user = $r->param('user');
1773 : toenail 2246 my $setTemplate = $self->{setTemplate};
1774 :     my @Sets = @$SetsRef;
1775 :     my %fieldNames = %$fieldNamesRef;
1776 :    
1777 :     my $editMode = $options{editMode};
1778 : gage 2536 my $exportMode = $options{exportMode};
1779 : toenail 2246 my %selectedSetIDs = map { $_ => 1 } @{ $options{selectedSetIDs} };
1780 :     my $currentSort = $options{currentSort};
1781 :    
1782 :     # names of headings:
1783 :     my @realFieldNames = (
1784 :     $setTemplate->KEYFIELDS,
1785 :     $setTemplate->NONKEYFIELDS,
1786 :     );
1787 :    
1788 :     if ($editMode) {
1789 :     @realFieldNames = @{ EDIT_FIELD_ORDER() };
1790 :     } else {
1791 :     @realFieldNames = @{ VIEW_FIELD_ORDER() };
1792 :     }
1793 : gage 2536
1794 :     if ($exportMode) {
1795 :     @realFieldNames = @{ EXPORT_FIELD_ORDER() };
1796 :     }
1797 : toenail 2246
1798 :    
1799 :     my %sortSubs = %{ SORT_SUBS() };
1800 :    
1801 :     # FIXME: should this always presume to use the templates directory?
1802 :     my @headers = $self->recurseDirectory($self->{ce}->{courseDirs}->{templates}, '(?i)header.*?\\.pg$');
1803 :     map { s|^$self->{ce}->{courseDirs}->{templates}/?|| } @headers;
1804 :     @headers = sort @headers;
1805 :     my %headers = map { $_ => $_ } @headers;
1806 :     $headers{""} = "Use System Default";
1807 :     $self->{headerFiles} = \%headers; # store these header files so we don't have to look for them later.
1808 :    
1809 : gage 2536
1810 :     my @tableHeadings = map { $fieldNames{$_} } @realFieldNames;
1811 : toenail 2246
1812 :     # prepend selection checkbox? only if we're NOT editing!
1813 :     # unshift @tableHeadings, "Select", "Set", "Problems" unless $editMode;
1814 :    
1815 :     # print the table
1816 : gage 2536 if ($editMode or $exportMode) {
1817 : toenail 2246 print CGI::start_table({});
1818 :     } else {
1819 :     print CGI::start_table({-border=>1});
1820 :     }
1821 :    
1822 :     print CGI::Tr({}, CGI::th({}, \@tableHeadings));
1823 :    
1824 :    
1825 :     for (my $i = 0; $i < @Sets; $i++) {
1826 :     my $Set = $Sets[$i];
1827 :    
1828 :     print $self->recordEditHTML($Set,
1829 :     editMode => $editMode,
1830 : gage 2536 exportMode => $exportMode,
1831 : toenail 2246 setSelected => exists $selectedSetIDs{$Set->set_id}
1832 :     );
1833 :     }
1834 :    
1835 :     print CGI::end_table();
1836 :     #########################################
1837 :     # if there are no users shown print message
1838 :     #
1839 :     ##########################################
1840 :    
1841 :     print CGI::p(
1842 :     CGI::i("No sets shown. Choose one of the options above to list the sets in the course.")
1843 :     ) unless @Sets;
1844 :     }
1845 :    
1846 : malsyned 832 1;
1847 : toenail 2246

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9