[system] / trunk / webwork-modperl / lib / WeBWorK / ContentGenerator / Instructor / UserList.pm Repository:
ViewVC logotype

Diff of /trunk/webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm

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

Revision 1473 Revision 2109
1################################################################################
2# WeBWorK Online Homework Delivery System
3# Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
4# $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm,v 1.48 2004/05/11 20:13:22 toenail Exp $
5#
6# This program is free software; you can redistribute it and/or modify it under
7# the terms of either: (a) the GNU General Public License as published by the
8# Free Software Foundation; either version 2, or (at your option) any later
9# version, or (b) the "Artistic License" which comes with this package.
10#
11# This program is distributed in the hope that it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
14# Artistic License for more details.
15################################################################################
16
1package WeBWorK::ContentGenerator::Instructor::UserList; 17package WeBWorK::ContentGenerator::Instructor::UserList;
2use base qw(WeBWorK::ContentGenerator::Instructor); 18use base qw(WeBWorK::ContentGenerator::Instructor);
3 19
4=head1 NAME 20=head1 NAME
5 21
6WeBWorK::ContentGenerator::Instructor::UserList - Entry point for User-specific data editing 22WeBWorK::ContentGenerator::Instructor::UserList - Entry point for User-specific
23data editing
24
25=cut
26
27=for comment
28
29What do we want to be able to do here?
30
31Filter what users are shown:
32 - none, all, selected
33 - matching user_id, matching section, matching recitation
34Switch from view mode to edit mode:
35 - showing visible users
36 - showing selected users
37Switch from edit mode to view and save changes
38Switch from edit mode to view and abandon changes
39Delete users:
40 - visible
41 - selected
42Import users:
43 - replace:
44 - any users
45 - visible users
46 - selected users
47 - no users
48 - add:
49 - any users
50 - no users
51Export users:
52 - export:
53 - all
54 - visible
55 - selected
56 - to:
57 - existing file on server (overwrite): [ list of files ]
58 - new file on server (create): [ filename ]
7 59
8=cut 60=cut
9 61
10use strict; 62use strict;
11use warnings; 63use warnings;
12use CGI qw(); 64use CGI qw();
65use WeBWorK::Utils qw(readFile readDirectory);
66use Apache::Constants qw(:common REDIRECT DONE); #FIXME -- this should be called higher up in the object tree.
67use constant HIDE_USERS_THRESHHOLD => 20;
68use constant EDIT_FORMS => [qw(cancelEdit saveEdit)];
69use constant VIEW_FORMS => [qw(filter edit import export add delete)];
70use constant STATE_PARAMS => [qw(user effectiveUser key visible_users no_visible_users prev_visible_users no_prev_visible_users editMode sortField)];
71
72use constant SORT_SUBS => {
73 user_id => \&byUserID,
74 first_name => \&byFirstName,
75 last_name => \&byLastName,
76 email_address => \&byEmailAddress,
77 student_id => \&byStudentID,
78 status => \&byStatus,
79 section => \&bySection,
80 recitation => \&byRecitation,
81 comment => \&byComment,
82};
83
84use constant FIELD_PROPERTIES => {
85 user_id => {
86 type => "text",
87 size => 8,
88 access => "readonly",
89 },
90 first_name => {
91 type => "text",
92 size => 10,
93 access => "readwrite",
94 },
95 last_name => {
96 type => "text",
97 size => 10,
98 access => "readwrite",
99 },
100 email_address => {
101 type => "text",
102 size => 20,
103 access => "readwrite",
104 },
105 student_id => {
106 type => "text",
107 size => 11,
108 access => "readwrite",
109 },
110 status => {
111 type => "enumerable",
112 size => 4,
113 access => "readwrite",
114 items => {
115 "C" => "Enrolled",
116 "D" => "Drop",
117 "A" => "Audit",
118 },
119 synonyms => {
120 qr/^[ce]/i => "C",
121 qr/^[dw]/i => "D",
122 qr/^a/i => "A",
123 "*" => "C",
124 }
125 },
126 section => {
127 type => "text",
128 size => 4,
129 access => "readwrite",
130 },
131 recitation => {
132 type => "text",
133 size => 4,
134 access => "readwrite",
135 },
136 comment => {
137 type => "text",
138 size => 20,
139 access => "readwrite",
140 },
141 permission => {
142 type => "number",
143 size => 2,
144 access => "readwrite",
145 }
146};
147sub pre_header_initialize {
148 my $self = shift;
149 my $r = $self->r;
150 my $urlpath = $r->urlpath;
151 my $ce = $r->ce;
152 my $courseName = $urlpath->arg("courseID");
153 # Handle redirects, if any.
154 ##############################
155 # Redirect to the addUser page
156 ##################################
157
158 defined($r->param('action')) && $r->param('action') eq 'add' && do {
159 # fix url and redirect
160 my $root = $ce->{webworkURLs}->{root};
161
162 my $numberOfStudents = $r->param('number_of_students');
163 warn "number of students not defined " unless defined $numberOfStudents;
164
165 my $uri=$self->systemLink( $urlpath->newFromModule('WeBWorK::ContentGenerator::Instructor::AddUsers',courseID=>$courseName),
166 params=>{
167 number_of_students=>$numberOfStudents,
168 }
169 );
170 #FIXME does the display mode need to be defined?
171 #FIXME url_authen_args also includes an effective user, so the new one must come first.
172 # even that might not work with every browser since there are two effective User assignments.
173 $r->header_out(Location => $uri);
174 $self->{noContent} = 1; # forces redirect
175 return;
176 };
177}
178# FIXME -- this should be moved up to instructor or contentgenerator
179sub header {
180 my $self = shift;
181 return REDIRECT if $self->{noContent};
182 my $r = $self->r;
183 $r->content_type('text/html');
184 $r->send_http_header();
185 return OK;
186}
187
188#FIXME -- this should probably be moved up to instructor or contentgenerator as well
189#sub nbsp {
190# my $str = shift;
191# ($str =~/\S/) ? $str : ' ' ; # returns non-breaking space for empty strings
192# # tricky cases: $str =0;
193# # $str is a complex number
194#}
195# moved to ContentGenerator.pm
13 196
14sub initialize { 197sub initialize {
15 my ($self) = @_; 198 my ($self) = @_;
16 my $r = $self->{r}; 199 my $r = $self->r;
17 my $db = $self->{db}; 200 my $db = $r->db;
18 my $ce = $self->{ce}; 201 my $ce = $r->ce;
19 my $authz = $self->{authz}; 202 my $authz = $r->authz;
20 my $user = $r->param('user'); 203 my $user = $r->param('user');
21 204
22 unless ($authz->hasPermissions($user, "modify_student_data")) { 205 unless ($authz->hasPermissions($user, "modify_student_data")) {
23 $self->{submitError} = "You are not authorized to modify student data"; 206 $self->addmessage(CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to modify student data")));
24 return; 207 return;
25 } 208 }
26 209
27 if (defined($r->param('save_classlist'))) {
28 my @userList = $db->listUsers;
29 foreach my $user (@userList) {
30 my $userRecord = $db->getUser($user);
31 my $permissionLevelRecord = $db->getPermissionLevel($user);
32 foreach my $field ($userRecord->NONKEYFIELDS()) {
33 my $paramName = "user.${user}.${field}";
34 if (defined($r->param($paramName))) {
35 $userRecord->$field($r->param($paramName));
36 }
37 }
38 foreach my $field ($permissionLevelRecord->NONKEYFIELDS()) {
39 my $paramName = "permission.${user}.${field}";
40 if (defined($r->param($paramName))) {
41 $permissionLevelRecord->$field($r->param($paramName));
42 }
43 }
44 $db->putUser($userRecord);
45 $db->putPermissionLevel($permissionLevelRecord);
46 }
47 foreach my $userID ($r->param('deleteUser')) {
48 $db->deleteUser($userID);
49 }
50 } elsif (defined($r->param('addStudent'))) { 210 #if (defined($r->param('addStudent'))) {
51 my $newUser = $db->newUser; 211 # my $newUser = $db->newUser;
52 my $newPermissionLevel = $db->newPermissionLevel; 212 # my $newPermissionLevel = $db->newPermissionLevel;
53 my $newPassword = $db->newPassword; 213 # my $newPassword = $db->newPassword;
54 $newUser->user_id($r->param('newUserID')); 214 # $newUser->user_id($r->param('newUserID'));
55 $newPermissionLevel->user_id($r->param('newUserID')); 215 # $newPermissionLevel->user_id($r->param('newUserID'));
56 $newPassword->user_id($r->param('newUserID')); 216 # $newPassword->user_id($r->param('newUserID'));
57 $newUser->status('C'); 217 # $newUser->status('C');
58 $newPermissionLevel->permission(0); 218 # $newPermissionLevel->permission(0);
59 $db->addUser($newUser); 219 # $db->addUser($newUser);
60 $db->addPermissionLevel($newPermissionLevel); 220 # $db->addPermissionLevel($newPermissionLevel);
61 $db->addPassword($newPassword); 221 # $db->addPassword($newPassword);
62 } 222 #}
63} 223}
64 224
65sub fieldEditHTML {
66 my ($self, $fieldName, $value, $properties) = @_;
67 my $size = $properties->{size};
68 my $type = $properties->{type};
69 my $access = $properties->{access};
70 my $items = $properties->{items};
71 my $synonyms = $properties->{synonyms};
72
73
74 if ($access eq "readonly") {
75 return $value;
76 }
77 if ($type eq "number" or $type eq "text") {
78 return CGI::input({type=>"text", name=>$fieldName, value=>$value, size=>$size});
79 }
80 if ($type eq "enumerable") {
81 my $matched = undef; # Whether a synonym match has occurred
82 225
83 # Process synonyms for enumerable objects
84 foreach my $synonym (keys %$synonyms) {
85 if ($synonym ne "*" and $value =~ m/$synonym/) {
86 $value = $synonyms->{$synonym};
87 $matched = 1;
88 }
89 }
90 if (!$matched and exists $synonyms->{"*"}) {
91 $value = $synonyms->{"*"};
92 }
93 return CGI::popup_menu({
94 name => $fieldName,
95 values => [keys %$items],
96 default => $value,
97 labels => $items,
98 });
99 }
100}
101 226
102sub title { 227sub body {
103 my $self = shift;
104 return $self->{ce}->{courseName}. ' class list';
105}
106
107sub path {
108 my $self = shift; 228 my ($self) = @_;
109 my $args = $_[-1]; 229 my $r = $self->r;
230 my $urlpath = $r->urlpath;
231 my $db = $r->db;
232 my $ce = $r->ce;
233 my $authz = $r->authz;
234 my $courseName = $urlpath->arg("courseID");
235 my $setID = $urlpath->arg("setID");
236 my $user = $r->param('user');
110 237
111 my $ce = $self->{ce};
112 my $root = $ce->{webworkURLs}->{root}; 238 my $root = $ce->{webworkURLs}->{root};
113 my $courseName = $ce->{courseName};
114 return $self->pathMacro($args,
115 "Home" => "$root",
116 $courseName => "$root/$courseName",
117 'instructor' => "$root/$courseName/instructor",
118 "class:$courseName" => ''
119 );
120}
121 239
122sub body { 240 # templates for getting field names
123 my ($self, $setID) = @_; 241 my $userTemplate = $self->{userTemplate} = $db->newUser;
124 my $r = $self->{r};
125 my $authz = $self->{authz};
126 my $user = $r->param('user');
127 my $db = $self->{db};
128 my $ce = $self->{ce};
129 my $root = $ce->{webworkURLs}->{root};
130 my $courseName = $ce->{courseName};
131
132 return CGI::em("You are not authorized to access the Instructor tools.") unless $authz->hasPermissions($user, "access_instructor_tools");
133
134 my $userTemplate = $db->newUser;
135 my $permissionLevelTemplate = $db->newPermissionLevel; 242 my $permissionLevelTemplate = $self->{permissionLevelTemplate} = $db->newPermissionLevel;
136 243
137 # This code will require changing if the permission and user tables ever have different keys. 244 return CGI::em("You are not authorized to access the Instructor tools.")
138 my @users = $db->listUsers; 245 unless $authz->hasPermissions($user, "access_instructor_tools");
139 246
140 # This table can be consulted when display-ready forms of field names are needed. 247 # This table can be consulted when display-ready forms of field names are needed.
141 my %prettyFieldNames = map {$_ => $_} ($userTemplate->FIELDS(), $permissionLevelTemplate->FIELDS()); 248 my %prettyFieldNames = map { $_ => $_ }
249 $userTemplate->FIELDS(),
250 $permissionLevelTemplate->FIELDS();
251
142 @prettyFieldNames{qw( 252 @prettyFieldNames{qw(
143 user_id 253 user_id
144 first_name 254 first_name
145 last_name 255 last_name
146 email_address 256 email_address
149 section 259 section
150 recitation 260 recitation
151 comment 261 comment
152 permission 262 permission
153 )} = ( 263 )} = (
154 "User ID", 264 "Assigned sets",
155 "First Name", 265 "First Name",
156 "Last Name", 266 "Last Name",
157 "E-mail", 267 "E-mail",
158 "Student ID", 268 "Student ID",
159 "Status", 269 "Status",
160 "Section", 270 "Section",
161 "Recitation", 271 "Recitation",
162 "Comment", 272 "Comment",
163 "Perm. Level" 273 "Perm. Level"
164 ); 274 );
165 275
166 my %fieldProperties = ( 276 ########## set initial values for state fields
167 user_id => { 277
168 type => "text", 278 my @allUserIDs = $db->listUsers;
169 size => 8, 279 $self->{allUserIDs} = \@allUserIDs;
170 access => "readonly", 280
281 if (defined $r->param("visible_users")) {
282 $self->{visibleUserIDs} = [ $r->param("visible_users") ];
283 } elsif (defined $r->param("no_visible_users")) {
284 $self->{visibleUserIDs} = [];
285 } else {
286 if (@allUserIDs > HIDE_USERS_THRESHHOLD) {
287 $self->{visibleUserIDs} = [];
288 } else {
289 $self->{visibleUserIDs} = [ @allUserIDs ];
171 }, 290 }
172 first_name => { 291 }
173 type => "text", 292
174 size => 10, 293 $self->{prevVisibleUserIDs} = $self->{visibleUserIDs};
175 access => "readwrite", 294
295 if (defined $r->param("selected_users")) {
296 $self->{selectedUserIDs} = [ $r->param("selected_users") ];
297 } else {
298 $self->{selectedUserIDs} = [];
299 }
300
301 $self->{editMode} = $r->param("editMode") || 0;
302
303 $self->{sortField} = $r->param("sortField") || "last_name";
304
305 my @allUsers = $db->getUsers(@allUserIDs);
306 my (%sections, %recitations);
307 foreach my $User (@allUsers) {
308 push @{$sections{defined $User->section ? $User->section : ""}}, $User->user_id;
309 push @{$recitations{defined $User->recitation ? $User->recitation : ""}}, $User->user_id;
310 }
311 $self->{sections} = \%sections;
312 $self->{recitations} = \%recitations;
313
314 ########## call action handler
315
316 my $actionID = $r->param("action");
317 if ($actionID) {
318 unless (grep { $_ eq $actionID } @{ VIEW_FORMS() }, @{ EDIT_FORMS() }) {
319 die "Action $actionID not found";
176 }, 320 }
177 last_name => { 321 my $actionHandler = "${actionID}_handler";
178 type => "text", 322 my %genericParams;
179 size => 10, 323 foreach my $param (qw(selected_users)) {
180 access => "readwrite", 324 $genericParams{$param} = [ $r->param($param) ];
181 }, 325 }
182 email_address => { 326 my %actionParams = $self->getActionParams($actionID);
183 type => "text", 327 my %tableParams = $self->getTableParams();
184 size => 20, 328 print CGI::p(
185 access => "readwrite", 329 '<div style="color:green">',
330 "Result of last action performed: ",
331 CGI::i($self->$actionHandler(\%genericParams, \%actionParams, \%tableParams)),
332 '</div>',
333 CGI::hr()
334
335 );
336 }
337
338 ########## retrieve possibly changed values for member fields
339
340 #@allUserIDs = @{ $self->{allUserIDs} }; # do we need this one?
341 my @visibleUserIDs = @{ $self->{visibleUserIDs} };
342 my @prevVisibleUserIDs = @{ $self->{prevVisibleUserIDs} };
343 my @selectedUserIDs = @{ $self->{selectedUserIDs} };
344 my $editMode = $self->{editMode};
345 my $sortField = $self->{sortField};
346
347 #warn "visibleUserIDs=@visibleUserIDs\n";
348 #warn "prevVisibleUserIDs=@prevVisibleUserIDs\n";
349 #warn "selectedUserIDs=@selectedUserIDs\n";
350 #warn "editMode=$editMode\n";
351
352 ########## get required users
353
354 my @Users = grep { defined $_ } @visibleUserIDs ? $db->getUsers(@visibleUserIDs) : ();
355
356 # presort users
357 my %sortSubs = %{ SORT_SUBS() };
358 my $sortSub = $sortSubs{$sortField};
359 #@Users = sort $sortSub @Users;
360 @Users = sort byLnFnUid @Users;
361
362 my @PermissionLevels;
363
364 for (my $i = 0; $i < @Users; $i++) {
365 my $User = $Users[$i];
366 my $PermissionLevel = $db->getPermissionLevel($User->user_id); # checked
367
368 unless ($PermissionLevel) {
369 # uh oh! no permission level record found!
370 warn "added missing permission level for user ", $User->user_id, "\n";
371
372 # create a new permission level record
373 $PermissionLevel = $db->newPermissionLevel;
374 $PermissionLevel->user_id($User->user_id);
375 $PermissionLevel->permission(0);
376
377 # add it to the database
378 $db->addPermissionLevel($PermissionLevel);
186 }, 379 }
187 student_id => { 380
188 type => "text", 381 $PermissionLevels[$i] = $PermissionLevel;
189 size => 11, 382 }
190 access => "readwrite", 383
384 ########## print beginning of form
385
386 print CGI::start_form({method=>"post", action=>$self->systemLink($urlpath,authen=>0), name=>"userlist"});
387 print $self->hidden_authen_fields();
388
389 ########## print state data
390
391 print "\n<!-- state data here -->\n";
392
393 if (@visibleUserIDs) {
394 print CGI::hidden(-name=>"visible_users", -value=>\@visibleUserIDs);
395 } else {
396 print CGI::hidden(-name=>"no_visible_users", -value=>"1");
397 }
398
399 if (@prevVisibleUserIDs) {
400 print CGI::hidden(-name=>"prev_visible_users", -value=>\@prevVisibleUserIDs);
401 } else {
402 print CGI::hidden(-name=>"no_prev_visible_users", -value=>"1");
403 }
404
405 print CGI::hidden(-name=>"editMode", -value=>$editMode);
406
407 print CGI::hidden(-name=>"sortField", -value=>$sortField);
408
409 print "\n<!-- state data here -->\n";
410
411 ########## print action forms
412
413 print CGI::start_table({});
414 print CGI::Tr({}, CGI::td({-colspan=>2}, "Select an action to perform:"));
415
416 my @formsToShow;
417 if ($editMode) {
418 @formsToShow = @{ EDIT_FORMS() };
419 } else {
420 @formsToShow = @{ VIEW_FORMS() };
421 }
422
423 my $i = 0;
424 foreach my $actionID (@formsToShow) {
425 my $actionForm = "${actionID}_form";
426 my $onChange = "document.userlist.action[$i].checked=true";
427 my %actionParams = $self->getActionParams($actionID);
428
429 print CGI::Tr({-valign=>"top"},
430 CGI::td({}, CGI::input({-type=>"radio", -name=>"action", -value=>$actionID})),
431 CGI::td({}, $self->$actionForm($onChange, %actionParams))
432 );
433
434 $i++;
435 }
436
437 print CGI::Tr({}, CGI::td({-colspan=>2, -align=>"center"},
438 CGI::submit(-value=>"Take Action!"))
439 );
440 print CGI::end_table();
441
442 ########## print table
443
444 print CGI::p("Showing ", scalar @visibleUserIDs, " out of ", scalar @allUserIDs, " users.");
445
446 $self->printTableHTML(\@Users, \@PermissionLevels, \%prettyFieldNames,
447 editMode => $editMode,
448 selectedUserIDs => \@selectedUserIDs,
449 );
450
451
452 ########## print end of form
453
454 print CGI::end_form();
455
456 return "";
457}
458
459################################################################################
460# extract particular params and put them in a hash (values are ARRAYREFs!)
461################################################################################
462
463sub getActionParams {
464 my ($self, $actionID) = @_;
465 my $r = $self->{r};
466
467 my %actionParams;
468 foreach my $param ($r->param) {
469 next unless $param =~ m/^action\.$actionID\./;
470 $actionParams{$param} = [ $r->param($param) ];
471 }
472 return %actionParams;
473}
474
475sub getTableParams {
476 my ($self) = @_;
477 my $r = $self->{r};
478
479 my %tableParams;
480 foreach my $param ($r->param) {
481 next unless $param =~ m/^(?:user|permission)\./;
482 $tableParams{$param} = [ $r->param($param) ];
483 }
484 return %tableParams;
485}
486
487################################################################################
488# actions and action triggers
489################################################################################
490
491# filter, edit, cancelEdit, and saveEdit should stay with the display module and
492# not be real "actions". that way, all actions are shown in view mode and no
493# actions are shown in edit mode.
494
495sub filter_form {
496 my ($self, $onChange, %actionParams) = @_;
497 #return CGI::table({}, CGI::Tr({-valign=>"top"},
498 # CGI::td({},
499 return join("",
500 "Show ",
501 CGI::popup_menu(
502 -name => "action.filter.scope",
503 -values => [qw(all none selected match_ids match_section match_recitation)],
504 -default => $actionParams{"action.filter.scope"}->[0] || "match_ids",
505 -labels => {
506 all => "all users",
507 none => "no users",
508 selected => "users checked below",
509 match_ids => "users with matching user IDs:",
510 match_section => "users in selected section",
511 match_recitation => "users in selected recitation",
191 }, 512 },
192 status => { 513 -onchange => $onChange,
193 type => "enumerable", 514 ),
194 size => 4, 515 " ",
195 access => "readwrite", 516 CGI::textfield(
196 items => { 517 -name => "action.filter.user_ids",
197 "C" => "Enrolled", 518 -value => $actionParams{"action.filter.user_ids"}->[0] || "",,
198 "D" => "Drop", 519 -width => "50",
199 "A" => "Audit", 520 -onchange => $onChange,
521 ),
522 " (separate multiple IDs with commas)",
523 CGI::br(),
524 "sections: ",
525 CGI::popup_menu(
526 -name => "action.filter.section",
527 -values => [ keys %{ $self->{sections} } ],
528 -default => $actionParams{"action.filter.section"}->[0] || "",
529 -labels => { $self->menuLabels($self->{sections}) },
530 -onchange => $onChange,
531 ),
532 " recitations: ",
533 CGI::popup_menu(
534 -name => "action.filter.recitation",
535 -values => [ keys %{ $self->{recitations} } ],
536 -default => $actionParams{"action.filter.recitation"}->[0] || "",
537 -labels => { $self->menuLabels($self->{recitations}) },
538 -onchange => $onChange,
539 ),
540 );
541 # ),
542 #));
543}
544
545# this action handler modifies the "visibleUserIDs" field based on the contents
546# of the "action.filter.scope" parameter and the "selected_users"
547sub filter_handler {
548 my ($self, $genericParams, $actionParams, $tableParams) = @_;
549
550 my $result;
551
552 my $scope = $actionParams->{"action.filter.scope"}->[0];
553 if ($scope eq "all") {
554 $result = "showing all users";
555 $self->{visibleUserIDs} = $self->{allUserIDs};
556 } elsif ($scope eq "none") {
557 $result = "showing no users";
558 $self->{visibleUserIDs} = [];
559 } elsif ($scope eq "selected") {
560 $result = "showing selected users";
561 $self->{visibleUserIDs} = $genericParams->{selected_users}; # an arrayref
562 } elsif ($scope eq "match_ids") {
563 my @userIDs = split /\s*,\s*/, $actionParams->{"action.filter.user_ids"}->[0];
564 $self->{visibleUserIDs} = \@userIDs;
565 } elsif ($scope eq "match_section") {
566 my $section = $actionParams->{"action.filter.section"}->[0];
567 $self->{visibleUserIDs} = $self->{sections}->{$section}; # an arrayref
568 } elsif ($scope eq "match_recitation") {
569 my $recitation = $actionParams->{"action.filter.recitation"}->[0];
570 $self->{visibleUserIDs} = $self->{recitations}->{$recitation}; # an arrayref
571 }
572
573 return $result;
574}
575
576sub edit_form {
577 my ($self, $onChange, %actionParams) = @_;
578 return join("",
579 "Edit ",
580 CGI::popup_menu(
581 -name => "action.edit.scope",
582 -values => [qw(all visible selected)],
583 -default => $actionParams{"action.edit.scope"}->[0] || "selected",
584 -labels => {
585 all => "all users",
586 visible => "visible users",
587 selected => "selected users"
200 }, 588 },
201 synonyms => { 589 -onchange => $onChange,
202 qr/^[ce]/i => "C", 590 ),
203 qr/^[dw]/i => "D", 591 );
204 qr/^a/i => "A", 592}
205 "*" => "C", 593
594sub edit_handler {
595 my ($self, $genericParams, $actionParams, $tableParams) = @_;
596
597 my $result;
598
599 my $scope = $actionParams->{"action.edit.scope"}->[0];
600 if ($scope eq "all") {
601 $result = "editing all users";
602 $self->{visibleUserIDs} = $self->{allUserIDs};
603 } elsif ($scope eq "visible") {
604 $result = "editing visible users";
605 # leave visibleUserIDs alone
606 } elsif ($scope eq "selected") {
607 $result = "editing selected users";
608 $self->{visibleUserIDs} = $genericParams->{selected_users}; # an arrayref
609 }
610 $self->{editMode} = 1;
611
612 return $result;
613}
614
615sub delete_form {
616 my ($self, $onChange, %actionParams) = @_;
617 return join("",
618 qq!\n<div style="background-color:red">!,
619 "Delete ",
620 CGI::popup_menu(
621 -name => "action.delete.scope",
622 -values => [qw(none visible selected)],
623 -default => $actionParams{"action.delete.scope"}->[0] || "none",
624 -labels => {
625 none => "no users.",
626 #visible => "visible users.",
627 selected => "selected users."
628 },
629 -onchange => $onChange,
630 ),
631 CGI::em(" Deletion destroys all user-related data and is not undoable!"),
632 "</div>\n",
633 );
634}
635
636sub delete_handler {
637 my ($self, $genericParams, $actionParams, $tableParams) = @_;
638 my $r = $self->r;
639 my $db = $r->db;
640 my $scope = $actionParams->{"action.delete.scope"}->[0];
641
642 my @userIDsToDelete = ();
643 #if ($scope eq "visible") {
644 # @userIDsToDelete = @{ $self->{visibleUserIDs} };
645 #} elsif ($scope eq "selected") {
646 if ($scope eq "selected") {
647 @userIDsToDelete = @{ $self->{selectedUserIDs} };
648 }
649
650 my %allUserIDs = map { $_ => 1 } @{ $self->{allUserIDs} };
651 my %visibleUserIDs = map { $_ => 1 } @{ $self->{visibleUserIDs} };
652 my %selectedUserIDs = map { $_ => 1 } @{ $self->{selectedUserIDs} };
653
654 foreach my $userID (@userIDsToDelete) {
655 delete $allUserIDs{$userID};
656 delete $visibleUserIDs{$userID};
657 delete $selectedUserIDs{$userID};
658 $db->deleteUser($userID);
659 }
660
661 $self->{allUserIDs} = [ keys %allUserIDs ];
662 $self->{visibleUserIDs} = [ keys %visibleUserIDs ];
663 $self->{selectedUserIDs} = [ keys %selectedUserIDs ];
664
665 my $num = @userIDsToDelete;
666 return "deleted $num user" . ($num == 1 ? "" : "s");
667}
668sub add_form {
669 my ($self, $onChange, %actionParams) = @_;
670
671 return "Add ", CGI::input({name=>'number_of_students', value=>1,size => 3}), " student(s). ";
672}
673
674sub add_handler {
675 my ($self, $genericParams, $actionParams, $tableParams) = @_;
676 # This action is redirected to the addUser.pm module using ../instructor/add_user/...
677 return "Nothing done by add student handler";
678}
679sub import_form {
680 my ($self, $onChange, %actionParams) = @_;
681 return join(" ",
682 "Import users from file",
683 CGI::popup_menu(
684 -name => "action.import.source",
685 -values => [ "", $self->getCSVList() ],
686 -default => $actionParams{"action.import.source"}->[0] || "",
687 -onchange => $onChange,
688 ),
689 "replacing",
690 CGI::popup_menu(
691 -name => "action.import.replace",
692 -values => [qw(any visible selected none)],
693 -default => $actionParams{"action.import.replace"}->[0] || "none",
694 -labels => {
695 any => "any",
696 visible => "visible",
697 selected => "selected",
698 none => "no",
699 },
700 -onchange => $onChange,
701 ),
702 "existing users and adding",
703 CGI::popup_menu(
704 -name => "action.import.add",
705 -values => [qw(any none)],
706 -default => $actionParams{"action.import.add"}->[0] || "any",
707 -labels => {
708 any => "any",
709 none => "no",
710 },
711 -onchange => $onChange,
712 ),
713 "new users",
714 );
715}
716
717sub import_handler {
718 my ($self, $genericParams, $actionParams, $tableParams) = @_;
719
720 my $source = $actionParams->{"action.import.source"}->[0];
721 my $add = $actionParams->{"action.import.add"}->[0];
722 my $replace = $actionParams->{"action.import.replace"}->[0];
723
724 my $fileName = $source;
725 my $createNew = $add eq "any";
726 my $replaceExisting;
727 my @replaceList;
728 if ($replace eq "any") {
729 $replaceExisting = "any";
730 } elsif ($replace eq "none") {
731 $replaceExisting = "none";
732 } elsif ($replace eq "visible") {
733 $replaceExisting = "listed";
734 @replaceList = @{ $self->{visibleUserIDs} };
735 } elsif ($replace eq "selected") {
736 $replaceExisting = "listed";
737 @replaceList = @{ $self->{selectedUserIDs} };
738 }
739
740 my ($replaced, $added, $skipped)
741 = $self->importUsersFromCSV($fileName, $createNew, $replaceExisting, @replaceList);
742
743 # make new users visible... do we really want to do this? probably.
744 push @{ $self->{visibleUserIDs} }, @$added;
745
746 my $numReplaced = @$replaced;
747 my $numAdded = @$added;
748 my $numSkipped = @$skipped;
749
750 return $numReplaced . " user" . ($numReplaced == 1 ? "" : "s") . " replaced, "
751 . $numAdded . " user" . ($numAdded == 1 ? "" : "s") . " added, "
752 . $numSkipped . " user" . ($numSkipped == 1 ? "" : "s") . " skipped.";
753}
754
755sub export_form {
756 my ($self, $onChange, %actionParams) = @_;
757 return join("",
758 "Export ",
759 CGI::popup_menu(
760 -name => "action.export.scope",
761 -values => [qw(all visible selected)],
762 -default => $actionParams{"action.export.scope"}->[0] || "visible",
763 -labels => {
764 all => "all users",
765 visible => "visible users",
766 selected => "selected users"
767 },
768 -onchange => $onChange,
769 ),
770 " to ",
771 CGI::popup_menu(
772 -name=>"action.export.target",
773 -values => [ "new", $self->getCSVList() ],
774 -labels => { new => "a new file named:" },
775 -default => $actionParams{"action.export.target"}->[0] || "",
776 -onchange => $onChange,
777 ),
778 #CGI::br(),
779 #"new file to create: ",
780 CGI::textfield(
781 -name => "action.export.new",
782 -value => $actionParams{"action.export.new"}->[0] || "",,
783 -width => "50",
784 -onchange => $onChange,
785 ),
786 CGI::tt(".lst"),
787 );
788}
789
790sub export_handler {
791 my ($self, $genericParams, $actionParams, $tableParams) = @_;
792
793 my $scope = $actionParams->{"action.export.scope"}->[0];
794 my $target = $actionParams->{"action.export.target"}->[0];
795 my $new = $actionParams->{"action.export.new"}->[0];
796
797 my $fileName;
798 if ($target eq "new") {
799 $fileName = $new;
800 } else {
801 $fileName = $target;
802 }
803
804 $fileName .= ".lst" unless $fileName =~ m/\.lst$/;
805
806 my @userIDsToExport;
807 if ($scope eq "all") {
808 @userIDsToExport = @{ $self->{allUserIDs} };
809 } elsif ($scope eq "visible") {
810 @userIDsToExport = @{ $self->{visibleUserIDs} };
811 } elsif ($scope eq "selected") {
812 @userIDsToExport = @{ $self->{selectedUserIDs} };
813 }
814
815 $self->exportUsersToCSV($fileName, @userIDsToExport);
816
817 return scalar @userIDsToExport . " users exported";
818}
819
820sub cancelEdit_form {
821 my ($self, $onChange, %actionParams) = @_;
822 return "Abandon changes";
823}
824
825sub cancelEdit_handler {
826 my ($self, $genericParams, $actionParams, $tableParams) = @_;
827 my $r = $self->r;
828
829 #$self->{selectedUserIDs} = $self->{visibleUserIDs};
830 # only do the above if we arrived here via "edit selected users"
831 if (defined $r->param("prev_visible_users")) {
832 $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ];
833 } elsif (defined $r->param("no_prev_visible_users")) {
834 $self->{visibleUserIDs} = [];
835 } else {
836 # leave it alone
837 }
838 $self->{editMode} = 0;
839
840 return "changes abandoned";
841}
842
843sub saveEdit_form {
844 my ($self, $onChange, %actionParams) = @_;
845 return "Save changes";
846}
847
848sub saveEdit_handler {
849 my ($self, $genericParams, $actionParams, $tableParams) = @_;
850 my $r = $self->r;
851 my $db = $r->db;
852
853 my @visibleUserIDs = @{ $self->{visibleUserIDs} };
854 foreach my $userID (@visibleUserIDs) {
855 my $User = $db->getUser($userID); # checked
856 die "record for visible user $userID not found" unless $User;
857 my $PermissionLevel = $db->getPermissionLevel($userID); # checked
858 die "permissions for $userID not defined" unless defined $PermissionLevel;
859 foreach my $field ($User->NONKEYFIELDS()) {
860 my $param = "user.${userID}.${field}";
861 if (defined $tableParams->{$param}->[0]) {
862 $User->$field($tableParams->{$param}->[0]);
206 } 863 }
207 },
208 section => {
209 type => "text",
210 size => 4,
211 access => "readwrite",
212 },
213 recitation => {
214 type => "text",
215 size => 4,
216 access => "readwrite",
217 },
218 comment => {
219 type => "text",
220 size => 20,
221 access => "readwrite",
222 },
223 permission => {
224 type => "number",
225 size => 2,
226 access => "readwrite",
227 } 864 }
865
866 foreach my $field ($PermissionLevel->NONKEYFIELDS()) {
867 my $param = "permission.${userID}.${field}";
868 if (defined $tableParams->{$param}->[0]) {
869 $PermissionLevel->$field($tableParams->{$param}->[0]);
870 }
871 }
872
873 $db->putUser($User);
874 $db->putPermissionLevel($PermissionLevel);
875 }
876
877 if (defined $r->param("prev_visible_users")) {
878 $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ];
879 } elsif (defined $r->param("no_prev_visible_users")) {
880 $self->{visibleUserIDs} = [];
881 } else {
882 # leave it alone
883 }
884
885 $self->{editMode} = 0;
886
887 return "changes saved";
888}
889
890################################################################################
891# sorts
892################################################################################
893
894sub byUserID { $a->user_id cmp $b->user_id }
895sub byFirstName { $a->first_name cmp $b->first_name }
896sub byLastName { $a->last_name cmp $b->last_name }
897sub byEmailAddress { $a->email_address cmp $b->email_address }
898sub byStudentID { $a->student_id cmp $b->student_id }
899sub byStatus { $a->status cmp $b->status }
900sub bySection { $a->section cmp $b->section }
901sub byRecitation { $a->recitation cmp $b->recitation }
902sub byComment { $a->comment cmp $b->comment }
903
904sub byLnFnUid { &byLastName || &byFirstName || &byUserID }
905
906################################################################################
907# utilities
908################################################################################
909
910# generate labels for section/recitation popup menus
911sub menuLabels {
912 my ($self, $hashRef) = @_;
913 my %hash = %$hashRef;
914
915 my %result;
916 foreach my $key (keys %hash) {
917 my $count = @{ $hash{$key} };
918 my $displayKey = $key || "<none>";
919 $result{$key} = "$displayKey ($count users)";
920 }
921 return %result;
922}
923
924sub importUsersFromCSV {
925 my ($self, $fileName, $createNew, $replaceExisting, @replaceList) = @_;
926 my $r = $self->r;
927 my $ce = $r->ce;
928 my $db = $r->db;
929 my $dir = $ce->{courseDirs}->{templates};
930
931 die "illegal character in input: \"/\"" if $fileName =~ m|/|;
932 die "won't be able to read from file $dir/$fileName: does it exist? is it readable?"
933 unless -r "$dir/$fileName";
934
935 my %allUserIDs = map { $_ => 1 } @{ $self->{allUserIDs} };
936 my %replaceOK;
937 if ($replaceExisting eq "none") {
938 %replaceOK = ();
939 } elsif ($replaceExisting eq "listed") {
940 %replaceOK = map { $_ => 1 } @replaceList;
941 } elsif ($replaceExisting eq "any") {
942 %replaceOK = %allUserIDs;
943 }
944
945 my (@replaced, @added, @skipped);
946
947 my @contents = split /\n/, readFile("$dir/$fileName");
948 foreach my $string (@contents) {
949 $string =~ s/^\s+//;
950 $string =~ s/\s+$//;
951 my (
952 $student_id, $last_name, $first_name, $status, $comment,
953 $section, $recitation, $email_address, $user_id
954 ) = split /\s*,\s*/, $string;
955
956 if (exists $allUserIDs{$user_id} and not exists $replaceOK{$user_id}) {
957 push @skipped, $user_id;
958 next;
959 }
960
961 if (not exists $allUserIDs{$user_id} and not $createNew) {
962 push @skipped, $user_id;
963 next;
964 }
965
966 my $User = $db->newUser;
967 $User->user_id($user_id);
968 $User->first_name($first_name);
969 $User->last_name($last_name);
970 $User->email_address($email_address);
971 $User->student_id($student_id);
972 $User->status($status);
973 $User->section($section);
974 $User->recitation($recitation);
975 $User->comment($comment);
976
977 my $PermissionLevel = $db->newPermissionLevel;
978 $PermissionLevel->user_id($user_id);
979 $PermissionLevel->permission(0);
980
981 my $Password = $db->newPassword;
982 $Password->user_id($user_id);
983 $Password->password(cryptPassword($student_id));
984
985 if (exists $allUserIDs{$user_id}) {
986 $db->putUser($User);
987 $db->putPermissionLevel($PermissionLevel);
988 $db->putPassword($Password);
989 push @replaced, $user_id;
990 } else {
991 $db->addUser($User);
992 $db->addPermissionLevel($PermissionLevel);
993 $db->addPassword($Password);
994 push @added, $user_id;
995 }
996 }
997
998 return \@replaced, \@added, \@skipped;
999}
1000
1001sub exportUsersToCSV {
1002 my ($self, $fileName, @userIDsToExport) = @_;
1003 my $r = $self->r;
1004 my $ce = $r->ce;
1005 my $db = $r->db;
1006 my $dir = $ce->{courseDirs}->{templates};
1007
1008 die "illegal character in input: \"/\"" if $fileName =~ m|/|;
1009
1010 open my $fh, ">", "$dir/$fileName"
1011 or die "failed to open file $dir/$fileName for writing: $!\n";
1012
1013 foreach my $userID (@userIDsToExport) {
1014 my $User = $db->getUser($userID); # checked
1015 die "record for user $userID not found." unless $User;
1016 my @fields = (
1017 $User->student_id,
1018 $User->last_name,
1019 $User->first_name,
1020 $User->status,
1021 $User->comment,
1022 $User->section,
1023 $User->recitation,
1024 $User->email_address,
1025 $User->user_id,
1026 );
1027 my $string = join ",", @fields;
1028 print $fh "$string\n";
1029 }
1030
1031 close $fh;
1032}
1033
1034################################################################################
1035# "display" methods
1036################################################################################
1037
1038sub fieldEditHTML {
1039 my ($self, $fieldName, $value, $properties) = @_;
1040 my $size = $properties->{size};
1041 my $type = $properties->{type};
1042 my $access = $properties->{access};
1043 my $items = $properties->{items};
1044 my $synonyms = $properties->{synonyms};
1045
1046 if ($access eq "readonly") {
1047 return $value;
1048 }
1049
1050 if ($type eq "number" or $type eq "text") {
1051 return CGI::input({type=>"text", name=>$fieldName, value=>$value, size=>$size});
1052 }
1053
1054 if ($type eq "enumerable") {
1055 my $matched = undef; # Whether a synonym match has occurred
1056
1057 # Process synonyms for enumerable objects
1058 foreach my $synonym (keys %$synonyms) {
1059 if ($synonym ne "*" and $value =~ m/$synonym/) {
1060 $value = $synonyms->{$synonym};
1061 $matched = 1;
1062 }
1063 }
1064
1065 if (!$matched and exists $synonyms->{"*"}) {
1066 $value = $synonyms->{"*"};
1067 }
1068
1069 return CGI::popup_menu({
1070 name => $fieldName,
1071 values => [keys %$items],
1072 default => $value,
1073 labels => $items,
1074 });
1075 }
1076}
1077
1078sub recordEditHTML {
1079 my ($self, $User, $PermissionLevel, %options) = @_;
1080 my $r = $self->r;
1081 my $urlpath = $r->urlpath;
1082 my $ce = $r->ce;
1083 my $root = $ce->{webworkURLs}->{root};
1084 my $courseName = $urlpath->arg("courseID");
1085
1086 my $editMode = $options{editMode};
1087 my $userSelected = $options{userSelected};
1088
1089 my $statusClass = $ce->{siteDefaults}->{status}->{$User->{status}};
1090
1091 my $changeEUserURL = $self->systemLink($urlpath->new(type=>'set_list',args=>{courseID=>$courseName}),
1092 params => {effectiveUser => $User->user_id}
228 ); 1093 );
229 1094
230 print CGI::start_form({method=>"post", action=>$r->uri()}); 1095 my $setsAssignedToUserURL = $self->systemLink($urlpath->new(type=>'instructor_sets_assigned_to_user',
231 print CGI::start_table({}); 1096 args=>{courseID => $courseName,
232 1097 userID => $User->user_id
233 # Table headings, prettied-up 1098 }),
234 print CGI::Tr({}, 1099 params => {effectiveUser => $User->user_id}
235 CGI::th({}, [
236 "Delete?",
237 map {$prettyFieldNames{$_}} (
238 $userTemplate->KEYFIELDS(),
239 $userTemplate->NONKEYFIELDS(),
240 $permissionLevelTemplate->NONKEYFIELDS(),
241 )
242 ])
243 ); 1100 );
244 # get user records 1101
245 my @userRecords = (); 1102 my @tableCells;
246 foreach my $currentUser ( @users) { 1103
247 push (@userRecords, $db->getUser($currentUser) ); 1104 # Select
248 } 1105 if ($editMode) {
249 @userRecords = sort {lc($a->last_name) cmp lc($b->last_name) } @userRecords; 1106 # column not there
250 @userRecords = sort {lc($a->section) cmp lc($b->section)} @userRecords; 1107 } else {
251 # process user records 1108 # selection checkbox
252 foreach my $userRecord (@userRecords) { 1109 push @tableCells, CGI::checkbox(
253 my $currentUser = $userRecord->user_id; 1110 -name => "selected_users",
254 my $permissionLevel = $db->getPermissionLevel($currentUser); 1111 -value => $User->user_id,
255 unless (defined $permissionLevel) { 1112 -checked => $userSelected,
256 warn "No permissionLevel record for user $currentUser" ; 1113 -label => "",
257 my $newPermissionLevel = $db->newPermissionLevel;
258 $newPermissionLevel->user_id($currentUser);
259 $newPermissionLevel->permission(0);
260 $db->addPermissionLevel($newPermissionLevel);
261 # permission set to minimum level
262 next;
263 }
264
265 # A concise way of printing a row containing a cell for each field, editable unless it's a key
266 print CGI::Tr({},
267 CGI::td({}, [
268 CGI::input({type=>"checkbox", name=>"deleteUser", value=>$currentUser}),
269 (
270 map {
271 my $changeEUserURL = "$root/$courseName?user=".$r->param("user")."&effectiveUser=".$userRecord->user_id()."&key=".$r->param("key");
272 CGI::a({href=>$changeEUserURL}, $userRecord->$_)
273 } $userRecord->KEYFIELDS
274 ),
275 (map {
276# CGI::input({type=>"text", size=>"8", name=> "user.".$userRecord->user_id().".".$_, value=>$userRecord->$_})
277 $self->fieldEditHTML("user.".$userRecord->user_id().".".$_, $userRecord->$_, $fieldProperties{$_});
278 } $userRecord->NONKEYFIELDS()),
279 (map {
280# CGI::input({type=>"text", size=>"8", name => "permission.".$permissionLevel->user_id().".".$_, value=>$permissionLevel->$_})
281 $self->fieldEditHTML("permission.".$permissionLevel->user_id().".".$_, $permissionLevel->$_, $fieldProperties{$_});
282 } $permissionLevel->NONKEYFIELDS()),
283 ])
284 ); 1114 );
285 } 1115 }
286 1116
1117 # Act As
1118 if ($editMode) {
1119 # column not there
1120 } else {
1121 # selection checkbox
1122 push @tableCells, CGI::a({href=>$changeEUserURL}, $User->user_id);
1123 }
1124
1125 # User ID
1126 if ($editMode) {
1127 # straight user ID
1128 push @tableCells, CGI::div({class=>$statusClass}, $User->user_id);
1129 } else {
1130 # "edit sets assigned to user" link
1131 push @tableCells, CGI::a({href=>$setsAssignedToUserURL}, "Edit sets");
1132 }
1133
1134 # User Fields
1135 foreach my $field ($User->NONKEYFIELDS) {
1136 my $fieldName = "user." . $User->user_id . "." . $field,
1137 my $fieldValue = $User->$field;
1138 my %properties = %{ FIELD_PROPERTIES()->{$field} };
1139 $properties{access} = "readonly" unless $editMode;
1140 $fieldValue = $self->nbsp($fieldValue) unless $editMode;
1141 push @tableCells, CGI::div({class=>$statusClass}, $self->fieldEditHTML($fieldName, $fieldValue, \%properties));
1142 }
1143
1144 # PermissionLevel Fields
1145 foreach my $field ($PermissionLevel->NONKEYFIELDS) {
1146 my $fieldName = "permission." . $PermissionLevel->user_id . "." . $field,
1147 my $fieldValue = $PermissionLevel->$field;
1148 my %properties = %{ FIELD_PROPERTIES()->{$field} };
1149 $properties{access} = "readonly" unless $editMode;
1150 $fieldValue = $self->nbsp($fieldValue) unless $editMode;
1151 push @tableCells, CGI::div({class=>$statusClass}, $self->fieldEditHTML($fieldName, $fieldValue, \%properties));
1152 }
1153
1154 return CGI::Tr({}, CGI::td({}, \@tableCells));
1155}
1156
1157sub printTableHTML {
1158 my ($self, $UsersRef, $PermissionLevelsRef, $fieldNamesRef, %options) = @_;
1159 my $r = $self->r;
1160 my $userTemplate = $self->{userTemplate};
1161 my $permissionLevelTemplate = $self->{permissionLevelTemplate};
1162 my @Users = @$UsersRef;
1163 my @PermissionLevels = @$PermissionLevelsRef;
1164 my %fieldNames = %$fieldNamesRef;
1165
1166 my $editMode = $options{editMode};
1167 my %selectedUserIDs = map { $_ => 1 } @{ $options{selectedUserIDs} };
1168 my $currentSort = $options{currentSort};
1169
1170 # names of headings:
1171 my @realFieldNames = (
1172 $userTemplate->KEYFIELDS,
1173 $userTemplate->NONKEYFIELDS,
1174 $permissionLevelTemplate->NONKEYFIELDS,
1175 );
1176
1177 my %sortSubs = %{ SORT_SUBS() };
1178 #my @stateParams = @{ STATE_PARAMS() };
1179 #my $hrefPrefix = $r->uri . "?" . $self->url_args(@stateParams); # $self->url_authen_args
1180 my @tableHeadings;
1181 foreach my $field (@realFieldNames) {
1182 my $result = $fieldNames{$field};
1183 push @tableHeadings, $result;
1184 };
1185
1186 # prepend selection checkbox? only if we're NOT editing!
1187 unshift @tableHeadings, "Select", "Act As" unless $editMode;
1188
1189 # print the table
1190 if ($editMode) {
1191 print CGI::start_table({});
1192 } else {
1193 print CGI::start_table({-border=>1});
1194 }
1195
1196 print CGI::Tr({}, CGI::th({}, \@tableHeadings));
1197
1198
1199 for (my $i = 0; $i < @Users; $i++) {
1200 my $User = $Users[$i];
1201 my $PermissionLevel = $PermissionLevels[$i];
1202
1203 print $self->recordEditHTML($User, $PermissionLevel,
1204 editMode => $editMode,
1205 userSelected => exists $selectedUserIDs{$User->user_id}
1206 );
1207 }
1208
287 print CGI::end_table(); 1209 print CGI::end_table();
288 print $self->hidden_authen_fields(); 1210 #########################################
289 print CGI::submit({name=>"save_classlist", value=>"Save Changes to Users"}); 1211 # if there are no users shown print message
290 print CGI::end_form(); 1212 #
1213 ##########################################
291 1214
292 # Add a student form 1215 print CGI::p(
293 print CGI::start_form({method=>"post", action=>$r->uri()}); 1216 CGI::i("No students shown. Choose one of the options above to
294 print $self->hidden_authen_fields(); 1217 list the students in the course.")
295 print "User ID:"; 1218 ) unless @Users;
296 print CGI::input({type=>"text", name=>"newUserID", value=>"", size=>"20"});
297 print CGI::submit({name=>"addStudent", value=>"Add Student"});
298 print CGI::end_form();
299
300 return "";
301} 1219}
302 1220
3031; 12211;
1222

Legend:
Removed from v.1473  
changed lines
  Added in v.2109

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9