Parent Directory
|
Revision Log
Made modifications to header that make it clearer how to edit all sets for one user. --Mike
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.38 2004/01/16 00:42:38 gage 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 17 package WeBWorK::ContentGenerator::Instructor::UserList; 18 use base qw(WeBWorK::ContentGenerator::Instructor); 19 20 =head1 NAME 21 22 WeBWorK::ContentGenerator::Instructor::UserList - Entry point for User-specific 23 data editing 24 25 =cut 26 27 =for comment 28 29 What do we want to be able to do here? 30 31 Filter what users are shown: 32 - none, all, selected 33 - matching user_id, matching section, matching recitation 34 Switch from view mode to edit mode: 35 - showing visible users 36 - showing selected users 37 Switch from edit mode to view and save changes 38 Switch from edit mode to view and abandon changes 39 Delete users: 40 - visible 41 - selected 42 Import users: 43 - replace: 44 - any users 45 - visible users 46 - selected users 47 - no users 48 - add: 49 - any users 50 - no users 51 Export 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 ] 59 60 =cut 61 62 use strict; 63 use warnings; 64 use CGI qw(); 65 use WeBWorK::Utils qw(readFile readDirectory); 66 use Apache::Constants qw(:common REDIRECT DONE); #FIXME -- this should be called higher up in the object tree. 67 use constant HIDE_USERS_THRESHHOLD => 20; 68 use constant EDIT_FORMS => [qw(cancelEdit saveEdit)]; 69 use constant VIEW_FORMS => [qw(filter edit import export add delete)]; 70 use constant STATE_PARAMS => [qw(user effectiveUser key visible_users no_visible_users prev_visible_users no_prev_visible_users editMode sortField)]; 71 72 use 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 84 use 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 }; 147 sub pre_header_initialize { 148 my $self = shift; 149 my $r = $self->{r}; 150 my $ce = $self->{ce}; 151 152 # Handle redirects, if any. 153 ############################## 154 # Redirect to the addUser page 155 ################################## 156 157 defined($r->param('action')) && $r->param('action') eq 'add' && do { 158 # fix url and redirect 159 my $root = $ce->{webworkURLs}->{root}; 160 my $courseName = $ce->{courseName}; 161 my $numberOfStudents = $r->param('number_of_students'); 162 warn "number of students not defined " unless defined $numberOfStudents; 163 164 my $uri="$root/$courseName/instructor/add_users?number_of_students=$numberOfStudents&".$self->url_authen_args; 165 #FIXME does the display mode need to be defined? 166 #FIXME url_authen_args also includes an effective user, so the new one must come first. 167 # even that might not work with every browser since there are two effective User assignments. 168 $r->header_out(Location => $uri); 169 $self->{noContent} = 1; # forces redirect 170 return; 171 }; 172 } 173 # FIXME -- this should be moved up to instructor or contentgenerator 174 sub header { 175 my $self = shift; 176 return REDIRECT if $self->{noContent}; 177 my $r = $self->{r}; 178 $r->content_type('text/html'); 179 $r->send_http_header(); 180 return OK; 181 } 182 sub initialize { 183 my ($self) = @_; 184 my $r = $self->{r}; 185 my $db = $self->{db}; 186 my $ce = $self->{ce}; 187 my $authz = $self->{authz}; 188 my $user = $r->param('user'); 189 190 unless ($authz->hasPermissions($user, "modify_student_data")) { 191 $self->{submitError} = "You are not authorized to modify student data"; 192 return; 193 } 194 195 #if (defined($r->param('addStudent'))) { 196 # my $newUser = $db->newUser; 197 # my $newPermissionLevel = $db->newPermissionLevel; 198 # my $newPassword = $db->newPassword; 199 # $newUser->user_id($r->param('newUserID')); 200 # $newPermissionLevel->user_id($r->param('newUserID')); 201 # $newPassword->user_id($r->param('newUserID')); 202 # $newUser->status('C'); 203 # $newPermissionLevel->permission(0); 204 # $db->addUser($newUser); 205 # $db->addPermissionLevel($newPermissionLevel); 206 # $db->addPassword($newPassword); 207 #} 208 } 209 210 sub title { 211 my $self = shift; 212 return "User List"; 213 } 214 215 sub path { 216 my $self = shift; 217 my $args = $_[-1]; 218 my $ce = $self->{ce}; 219 my $root = $ce->{webworkURLs}->{root}; 220 my $courseName = $ce->{courseName}; 221 222 return $self->pathMacro($args, 223 "Home" => "$root", 224 $courseName => "$root/$courseName", 225 "Instructor Tools" => "$root/$courseName/instructor", 226 "Users" => "", # "$root/$courseName/instructor/users", 227 ); 228 } 229 230 sub body { 231 my ($self, $setID) = @_; 232 my $r = $self->{r}; 233 my $authz = $self->{authz}; 234 my $user = $r->param('user'); 235 my $db = $self->{db}; 236 my $ce = $self->{ce}; 237 my $root = $ce->{webworkURLs}->{root}; 238 my $courseName = $ce->{courseName}; 239 240 # templates for getting field names 241 my $userTemplate = $self->{userTemplate} = $db->newUser; 242 my $permissionLevelTemplate = $self->{permissionLevelTemplate} = $db->newPermissionLevel; 243 244 return CGI::em("You are not authorized to access the Instructor tools.") 245 unless $authz->hasPermissions($user, "access_instructor_tools"); 246 247 # This table can be consulted when display-ready forms of field names are needed. 248 my %prettyFieldNames = map { $_ => $_ } 249 $userTemplate->FIELDS(), 250 $permissionLevelTemplate->FIELDS(); 251 252 @prettyFieldNames{qw( 253 user_id 254 first_name 255 last_name 256 email_address 257 student_id 258 status 259 section 260 recitation 261 comment 262 permission 263 )} = ( 264 "Assigned sets", 265 "First Name", 266 "Last Name", 267 "E-mail", 268 "Student ID", 269 "Status", 270 "Section", 271 "Recitation", 272 "Comment", 273 "Perm. Level" 274 ); 275 276 ########## set initial values for state fields 277 278 my @allUserIDs = $db->listUsers; 279 $self->{allUserIDs} = \@allUserIDs; 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 ]; 290 } 291 } 292 293 $self->{prevVisibleUserIDs} = $self->{visibleUserIDs}; 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{$User->section}}, $User->user_id; 309 push @{$recitations{$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"; 320 } 321 my $actionHandler = "${actionID}_handler"; 322 my %genericParams; 323 foreach my $param (qw(selected_users)) { 324 $genericParams{$param} = [ $r->param($param) ]; 325 } 326 my %actionParams = $self->getActionParams($actionID); 327 my %tableParams = $self->getTableParams(); 328 print CGI::p( 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); 379 } 380 381 $PermissionLevels[$i] = $PermissionLevel; 382 } 383 384 ########## print beginning of form 385 386 print CGI::start_form({method=>"post", action=>$r->uri, 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 463 sub 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 475 sub 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 495 sub 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 selected below", 509 match_ids => "users with matching user IDs:", 510 match_section => "users in selected section", 511 match_recitation => "users in selected recitation", 512 }, 513 -onchange => $onChange, 514 ), 515 " ", 516 CGI::textfield( 517 -name => "action.filter.user_ids", 518 -value => $actionParams{"action.filter.user_ids"}->[0] || "",, 519 -width => "50", 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" 547 sub 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 576 sub 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" 588 }, 589 -onchange => $onChange, 590 ), 591 ); 592 } 593 594 sub 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 615 sub 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 636 sub delete_handler { 637 my ($self, $genericParams, $actionParams, $tableParams) = @_; 638 my $db = $self->{db}; 639 my $scope = $actionParams->{"action.delete.scope"}->[0]; 640 641 my @userIDsToDelete = (); 642 if ($scope eq "visible") { 643 @userIDsToDelete = @{ $self->{visibleUserIDs} }; 644 } elsif ($scope eq "selected") { 645 @userIDsToDelete = @{ $self->{selectedUserIDs} }; 646 } 647 648 my %allUserIDs = map { $_ => 1 } @{ $self->{allUserIDs} }; 649 my %visibleUserIDs = map { $_ => 1 } @{ $self->{visibleUserIDs} }; 650 my %selectedUserIDs = map { $_ => 1 } @{ $self->{selectedUserIDs} }; 651 652 foreach my $userID (@userIDsToDelete) { 653 delete $allUserIDs{$userID}; 654 delete $visibleUserIDs{$userID}; 655 delete $selectedUserIDs{$userID}; 656 $db->deleteUser($userID); 657 } 658 659 $self->{allUserIDs} = [ keys %allUserIDs ]; 660 $self->{visibleUserIDs} = [ keys %visibleUserIDs ]; 661 $self->{selectedUserIDs} = [ keys %selectedUserIDs ]; 662 663 my $num = @userIDsToDelete; 664 return "deleted $num user" . ($num == 1 ? "" : "s"); 665 } 666 sub add_form { 667 my ($self, $onChange, %actionParams) = @_; 668 669 return "Add ", CGI::input({name=>'number_of_students', value=>1,size => 3}), " student(s). "; 670 } 671 672 sub add_handler { 673 my ($self, $genericParams, $actionParams, $tableParams) = @_; 674 # This action is redirected to the addUser.pm module using ../instructor/add_user/... 675 return "Nothing done by add student handler"; 676 } 677 sub import_form { 678 my ($self, $onChange, %actionParams) = @_; 679 return join(" ", 680 "Import users from file", 681 CGI::popup_menu( 682 -name => "action.import.source", 683 -values => [ "", $self->getCSVList() ], 684 -default => $actionParams{"action.import.source"}->[0] || "", 685 -onchange => $onChange, 686 ), 687 "replacing", 688 CGI::popup_menu( 689 -name => "action.import.replace", 690 -values => [qw(any visible selected none)], 691 -default => $actionParams{"action.import.replace"}->[0] || "none", 692 -labels => { 693 any => "any", 694 visible => "visible", 695 selected => "selected", 696 none => "no", 697 }, 698 -onchange => $onChange, 699 ), 700 "existing users and adding", 701 CGI::popup_menu( 702 -name => "action.import.add", 703 -values => [qw(any none)], 704 -default => $actionParams{"action.import.add"}->[0] || "any", 705 -labels => { 706 any => "any", 707 none => "no", 708 }, 709 -onchange => $onChange, 710 ), 711 "new users", 712 ); 713 } 714 715 sub import_handler { 716 my ($self, $genericParams, $actionParams, $tableParams) = @_; 717 718 my $source = $actionParams->{"action.import.source"}->[0]; 719 my $add = $actionParams->{"action.import.add"}->[0]; 720 my $replace = $actionParams->{"action.import.replace"}->[0]; 721 722 my $fileName = $source; 723 my $createNew = $add eq "any"; 724 my $replaceExisting; 725 my @replaceList; 726 if ($replace eq "any") { 727 $replaceExisting = "any"; 728 } elsif ($replace eq "none") { 729 $replaceExisting = "none"; 730 } elsif ($replace eq "visible") { 731 $replaceExisting = "listed"; 732 @replaceList = @{ $self->{visibleUserIDs} }; 733 } elsif ($replace eq "selected") { 734 $replaceExisting = "listed"; 735 @replaceList = @{ $self->{selectedUserIDs} }; 736 } 737 738 my ($replaced, $added, $skipped) 739 = $self->importUsersFromCSV($fileName, $createNew, $replaceExisting, @replaceList); 740 741 # make new users visible... do we really want to do this? probably. 742 push @{ $self->{visibleUserIDs} }, @$added; 743 744 my $numReplaced = @$replaced; 745 my $numAdded = @$added; 746 my $numSkipped = @$skipped; 747 748 return $numReplaced . " user" . ($numReplaced == 1 ? "" : "s") . " replaced, " 749 . $numAdded . " user" . ($numAdded == 1 ? "" : "s") . " added, " 750 . $numSkipped . " user" . ($numSkipped == 1 ? "" : "s") . " skipped."; 751 } 752 753 sub export_form { 754 my ($self, $onChange, %actionParams) = @_; 755 return join("", 756 "Export ", 757 CGI::popup_menu( 758 -name => "action.export.scope", 759 -values => [qw(all visible selected)], 760 -default => $actionParams{"action.export.scope"}->[0] || "visible", 761 -labels => { 762 all => "all users", 763 visible => "visible users", 764 selected => "selected users" 765 }, 766 -onchange => $onChange, 767 ), 768 " to ", 769 CGI::popup_menu( 770 -name=>"action.export.target", 771 -values => [ "new", $self->getCSVList() ], 772 -labels => { new => "a new file named:" }, 773 -default => $actionParams{"action.export.target"}->[0] || "", 774 -onchange => $onChange, 775 ), 776 #CGI::br(), 777 #"new file to create: ", 778 CGI::textfield( 779 -name => "action.export.new", 780 -value => $actionParams{"action.export.new"}->[0] || "",, 781 -width => "50", 782 -onchange => $onChange, 783 ), 784 CGI::tt(".lst"), 785 ); 786 } 787 788 sub export_handler { 789 my ($self, $genericParams, $actionParams, $tableParams) = @_; 790 791 my $scope = $actionParams->{"action.export.scope"}->[0]; 792 my $target = $actionParams->{"action.export.target"}->[0]; 793 my $new = $actionParams->{"action.export.new"}->[0]; 794 795 my $fileName; 796 if ($target eq "new") { 797 $fileName = $new; 798 } else { 799 $fileName = $target; 800 } 801 802 $fileName .= ".lst" unless $fileName =~ m/\.lst$/; 803 804 my @userIDsToExport; 805 if ($scope eq "all") { 806 @userIDsToExport = @{ $self->{allUserIDs} }; 807 } elsif ($scope eq "visible") { 808 @userIDsToExport = @{ $self->{visibleUserIDs} }; 809 } elsif ($scope eq "selected") { 810 @userIDsToExport = @{ $self->{selectedUserIDs} }; 811 } 812 813 $self->exportUsersToCSV($fileName, @userIDsToExport); 814 815 return scalar @userIDsToExport . " users exported"; 816 } 817 818 sub cancelEdit_form { 819 my ($self, $onChange, %actionParams) = @_; 820 return "Abandon changes"; 821 } 822 823 sub cancelEdit_handler { 824 my ($self, $genericParams, $actionParams, $tableParams) = @_; 825 my $r = $self->{r}; 826 827 #$self->{selectedUserIDs} = $self->{visibleUserIDs}; 828 # only do the above if we arrived here via "edit selected users" 829 if (defined $r->param("prev_visible_users")) { 830 $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ]; 831 } elsif (defined $r->param("no_prev_visible_users")) { 832 $self->{visibleUserIDs} = []; 833 } else { 834 # leave it alone 835 } 836 $self->{editMode} = 0; 837 838 return "changes abandoned"; 839 } 840 841 sub saveEdit_form { 842 my ($self, $onChange, %actionParams) = @_; 843 return "Save changes"; 844 } 845 846 sub saveEdit_handler { 847 my ($self, $genericParams, $actionParams, $tableParams) = @_; 848 my $r = $self->{r}; 849 my $db = $self->{db}; 850 851 my @visibleUserIDs = @{ $self->{visibleUserIDs} }; 852 foreach my $userID (@visibleUserIDs) { 853 my $User = $db->getUser($userID); # checked 854 die "record for visible user $userID not found" unless $User; 855 my $PermissionLevel = $db->getPermissionLevel($userID); # checked 856 die "permissions for $userID not defined" unless defined $PermissionLevel; 857 foreach my $field ($User->NONKEYFIELDS()) { 858 my $param = "user.${userID}.${field}"; 859 if (defined $tableParams->{$param}->[0]) { 860 $User->$field($tableParams->{$param}->[0]); 861 } 862 } 863 864 foreach my $field ($PermissionLevel->NONKEYFIELDS()) { 865 my $param = "permission.${userID}.${field}"; 866 if (defined $tableParams->{$param}->[0]) { 867 $PermissionLevel->$field($tableParams->{$param}->[0]); 868 } 869 } 870 871 $db->putUser($User); 872 $db->putPermissionLevel($PermissionLevel); 873 } 874 875 if (defined $r->param("prev_visible_users")) { 876 $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ]; 877 } elsif (defined $r->param("no_prev_visible_users")) { 878 $self->{visibleUserIDs} = []; 879 } else { 880 # leave it alone 881 } 882 883 $self->{editMode} = 0; 884 885 return "changes saved"; 886 } 887 888 ################################################################################ 889 # sorts 890 ################################################################################ 891 892 sub byUserID { $a->user_id cmp $b->user_id } 893 sub byFirstName { $a->first_name cmp $b->first_name } 894 sub byLastName { $a->last_name cmp $b->last_name } 895 sub byEmailAddress { $a->email_address cmp $b->email_address } 896 sub byStudentID { $a->student_id cmp $b->student_id } 897 sub byStatus { $a->status cmp $b->status } 898 sub bySection { $a->section cmp $b->section } 899 sub byRecitation { $a->recitation cmp $b->recitation } 900 sub byComment { $a->comment cmp $b->comment } 901 902 sub byLnFnUid { &byLastName || &byFirstName || &byUserID } 903 904 ################################################################################ 905 # utilities 906 ################################################################################ 907 908 # generate labels for section/recitation popup menus 909 sub menuLabels { 910 my ($self, $hashRef) = @_; 911 my %hash = %$hashRef; 912 913 my %result; 914 foreach my $key (keys %hash) { 915 my $count = @{ $hash{$key} }; 916 my $displayKey = $key || "<none>"; 917 $result{$key} = "$displayKey ($count users)"; 918 } 919 return %result; 920 } 921 922 sub importUsersFromCSV { 923 my ($self, $fileName, $createNew, $replaceExisting, @replaceList) = @_; 924 my $ce = $self->{ce}; 925 my $db = $self->{db}; 926 my $dir = $ce->{courseDirs}->{templates}; 927 928 die "illegal character in input: \"/\"" if $fileName =~ m|/|; 929 die "won't be able to read from file $dir/$fileName: does it exist? is it readable?" 930 unless -r "$dir/$fileName"; 931 932 my %allUserIDs = map { $_ => 1 } @{ $self->{allUserIDs} }; 933 my %replaceOK; 934 if ($replaceExisting eq "none") { 935 %replaceOK = (); 936 } elsif ($replaceExisting eq "listed") { 937 %replaceOK = map { $_ => 1 } @replaceList; 938 } elsif ($replaceExisting eq "any") { 939 %replaceOK = %allUserIDs; 940 } 941 942 my (@replaced, @added, @skipped); 943 944 my @contents = split /\n/, readFile("$dir/$fileName"); 945 foreach my $string (@contents) { 946 $string =~ s/^\s+//; 947 $string =~ s/\s+$//; 948 my ( 949 $student_id, $last_name, $first_name, $status, $comment, 950 $section, $recitation, $email_address, $user_id 951 ) = split /\s*,\s*/, $string; 952 953 if (exists $allUserIDs{$user_id} and not exists $replaceOK{$user_id}) { 954 push @skipped, $user_id; 955 next; 956 } 957 958 if (not exists $allUserIDs{$user_id} and not $createNew) { 959 push @skipped, $user_id; 960 next; 961 } 962 963 my $User = $db->newUser; 964 $User->user_id($user_id); 965 $User->first_name($first_name); 966 $User->last_name($last_name); 967 $User->email_address($email_address); 968 $User->student_id($student_id); 969 $User->status($status); 970 $User->section($section); 971 $User->recitation($recitation); 972 $User->comment($comment); 973 974 my $PermissionLevel = $db->newPermissionLevel; 975 $PermissionLevel->user_id($user_id); 976 $PermissionLevel->permission(0); 977 978 my $Password = $db->newPassword; 979 $Password->user_id($user_id); 980 $Password->password(cryptPassword($student_id)); 981 982 if (exists $allUserIDs{$user_id}) { 983 $db->putUser($User); 984 $db->putPermissionLevel($PermissionLevel); 985 $db->putPassword($Password); 986 push @replaced, $user_id; 987 } else { 988 $db->addUser($User); 989 $db->addPermissionLevel($PermissionLevel); 990 $db->addPassword($Password); 991 push @added, $user_id; 992 } 993 } 994 995 return \@replaced, \@added, \@skipped; 996 } 997 998 sub exportUsersToCSV { 999 my ($self, $fileName, @userIDsToExport) = @_; 1000 my $ce = $self->{ce}; 1001 my $db = $self->{db}; 1002 my $dir = $ce->{courseDirs}->{templates}; 1003 1004 die "illegal character in input: \"/\"" if $fileName =~ m|/|; 1005 1006 open my $fh, ">", "$dir/$fileName" 1007 or die "failed to open file $dir/$fileName for writing: $!\n"; 1008 1009 foreach my $userID (@userIDsToExport) { 1010 my $User = $db->getUser($userID); # checked 1011 die "record for user $userID not found." unless $User; 1012 my @fields = ( 1013 $User->student_id, 1014 $User->last_name, 1015 $User->first_name, 1016 $User->status, 1017 $User->comment, 1018 $User->section, 1019 $User->recitation, 1020 $User->email_address, 1021 $User->user_id, 1022 ); 1023 my $string = join ",", @fields; 1024 print $fh "$string\n"; 1025 } 1026 1027 close $fh; 1028 } 1029 1030 ################################################################################ 1031 # "display" methods 1032 ################################################################################ 1033 1034 sub fieldEditHTML { 1035 my ($self, $fieldName, $value, $properties) = @_; 1036 my $size = $properties->{size}; 1037 my $type = $properties->{type}; 1038 my $access = $properties->{access}; 1039 my $items = $properties->{items}; 1040 my $synonyms = $properties->{synonyms}; 1041 1042 if ($access eq "readonly") { 1043 return $value; 1044 } 1045 1046 if ($type eq "number" or $type eq "text") { 1047 return CGI::input({type=>"text", name=>$fieldName, value=>$value, size=>$size}); 1048 } 1049 1050 if ($type eq "enumerable") { 1051 my $matched = undef; # Whether a synonym match has occurred 1052 1053 # Process synonyms for enumerable objects 1054 foreach my $synonym (keys %$synonyms) { 1055 if ($synonym ne "*" and $value =~ m/$synonym/) { 1056 $value = $synonyms->{$synonym}; 1057 $matched = 1; 1058 } 1059 } 1060 1061 if (!$matched and exists $synonyms->{"*"}) { 1062 $value = $synonyms->{"*"}; 1063 } 1064 1065 return CGI::popup_menu({ 1066 name => $fieldName, 1067 values => [keys %$items], 1068 default => $value, 1069 labels => $items, 1070 }); 1071 } 1072 } 1073 1074 sub recordEditHTML { 1075 my ($self, $User, $PermissionLevel, %options) = @_; 1076 my $r = $self->{r}; 1077 my $ce = $self->{ce}; 1078 my $root = $ce->{webworkURLs}->{root}; 1079 my $courseName = $ce->{courseName}; 1080 1081 my $editMode = $options{editMode}; 1082 my $userSelected = $options{userSelected}; 1083 1084 my $changeEUserURL = "$root/$courseName?" 1085 . "user=" . $r->param("user") 1086 . "&effectiveUser=" . $User->user_id 1087 . "&key=" . $r->param("key"); 1088 1089 my $setsAssignedToUserURL = "$root/$courseName/instructor/users/" 1090 . $User->user_id . "/sets/?" 1091 . "user=" . $r->param("user") 1092 . "&effectiveUser=" . $r->param("effectiveUser") 1093 . "&key=" . $r->param("key"); 1094 1095 my @tableCells; 1096 1097 # Select 1098 if ($editMode) { 1099 # column not there 1100 } else { 1101 # selection checkbox 1102 push @tableCells, CGI::checkbox( 1103 -name => "selected_users", 1104 -value => $User->user_id, 1105 -checked => $userSelected, 1106 -label => "", 1107 ); 1108 } 1109 1110 # Act As 1111 if ($editMode) { 1112 # column not there 1113 } else { 1114 # selection checkbox 1115 push @tableCells, CGI::a({href=>$changeEUserURL}, $User->user_id); 1116 } 1117 1118 # User ID 1119 if ($editMode) { 1120 # straight user ID 1121 push @tableCells, $User->user_id; 1122 } else { 1123 # "edit sets assigned to user" link 1124 push @tableCells, CGI::a({href=>$setsAssignedToUserURL}, "Edit"); 1125 } 1126 1127 # User Fields 1128 foreach my $field ($User->NONKEYFIELDS) { 1129 my $fieldName = "user." . $User->user_id . "." . $field, 1130 my $fieldValue = $User->$field; 1131 my %properties = %{ FIELD_PROPERTIES()->{$field} }; 1132 $properties{access} = "readonly" unless $editMode; 1133 push @tableCells, $self->fieldEditHTML($fieldName, $fieldValue, \%properties); 1134 } 1135 1136 # PermissionLevel Fields 1137 foreach my $field ($PermissionLevel->NONKEYFIELDS) { 1138 my $fieldName = "permission." . $PermissionLevel->user_id . "." . $field, 1139 my $fieldValue = $PermissionLevel->$field; 1140 my %properties = %{ FIELD_PROPERTIES()->{$field} }; 1141 $properties{access} = "readonly" unless $editMode; 1142 push @tableCells, $self->fieldEditHTML($fieldName, $fieldValue, \%properties); 1143 } 1144 1145 return CGI::Tr({}, CGI::td({}, \@tableCells)); 1146 } 1147 1148 sub printTableHTML { 1149 my ($self, $UsersRef, $PermissionLevelsRef, $fieldNamesRef, %options) = @_; 1150 my $r = $self->{r}; 1151 my $userTemplate = $self->{userTemplate}; 1152 my $permissionLevelTemplate = $self->{permissionLevelTemplate}; 1153 my @Users = @$UsersRef; 1154 my @PermissionLevels = @$PermissionLevelsRef; 1155 my %fieldNames = %$fieldNamesRef; 1156 1157 my $editMode = $options{editMode}; 1158 my %selectedUserIDs = map { $_ => 1 } @{ $options{selectedUserIDs} }; 1159 my $currentSort = $options{currentSort}; 1160 1161 # names of headings: 1162 my @realFieldNames = ( 1163 $userTemplate->KEYFIELDS, 1164 $userTemplate->NONKEYFIELDS, 1165 $permissionLevelTemplate->NONKEYFIELDS, 1166 ); 1167 1168 my %sortSubs = %{ SORT_SUBS() }; 1169 #my @stateParams = @{ STATE_PARAMS() }; 1170 #my $hrefPrefix = $r->uri . "?" . $self->url_args(@stateParams); # $self->url_authen_args 1171 my @tableHeadings; 1172 foreach my $field (@realFieldNames) { 1173 my $result; 1174 #if (exists $sortSubs{$field}) { 1175 # $result = CGI::a({-href=>"$hrefPrefix&sort=$field"}, $fieldNames{$field}); 1176 #} else { 1177 $result = $fieldNames{$field}; 1178 #} 1179 push @tableHeadings, $result; 1180 }; 1181 1182 # prepend selection checkbox? only if we're NOT editing! 1183 unshift @tableHeadings, "Select", "Act As" unless $editMode; 1184 1185 # print the table 1186 if ($editMode) { 1187 print CGI::start_table({}); 1188 } else { 1189 print CGI::start_table({-border=>1}); 1190 } 1191 1192 print CGI::Tr({}, CGI::th({}, \@tableHeadings)); 1193 1194 1195 for (my $i = 0; $i < @Users; $i++) { 1196 my $User = $Users[$i]; 1197 my $PermissionLevel = $PermissionLevels[$i]; 1198 1199 print $self->recordEditHTML($User, $PermissionLevel, 1200 editMode => $editMode, 1201 userSelected => exists $selectedUserIDs{$User->user_id} 1202 ); 1203 } 1204 1205 print CGI::end_table(); 1206 ######################################### 1207 # if there are no users shown print message 1208 # 1209 ########################################## 1210 1211 print CGI::p( 1212 CGI::i("No students shown. Choose one of the options above to 1213 list the students in the course.") 1214 ) unless @Users; 1215 } 1216 1217 1; 1218 1219 __END__ 1220 1221 my $editMode = 0; 1222 if (defined $r->param("edit_selected") or defined $r->param("edit_visible")) { 1223 $editMode = 1; 1224 } 1225 1226 my @userIDs = $db->listUsers; 1227 my @userRecords = $db->getUsers(@userIDs); 1228 1229 my (%sections, %recitations); 1230 foreach my $user (@userRecords) { 1231 push @{$sections{$user->section}}, $user; 1232 push @{$recitations{$user->recitation}}, $user; 1233 } 1234 1235 my $filter_type = $r->param("filter_type") 1236 || (@userIDs > HIDE_USERS_THRESHHOLD ? "none" : "all"); 1237 my $filter_user_id = $filter_type eq "filter_user_id" 1238 ? $r->param("filter_user_id") || "" 1239 : ""; 1240 my $filter_section = $filter_type eq "filter_section" 1241 ? $r->param("filter_section") || "" 1242 : ""; 1243 my $filter_recitation = $filter_type eq "filter_recitation" 1244 ? $r->param("filter_recitation") || "" 1245 : ""; 1246 1247 # override filter selection if "Edit Selected Users" button is pressed 1248 if (defined $r->param("edit_selected")) { 1249 $filter_type = "filter_selected"; 1250 } 1251 1252 if ($filter_type eq "none") { 1253 @userRecords = (); 1254 } elsif ($filter_type eq "filter_selected") { 1255 @userRecords = (); 1256 my @userIDs = $r->param("selectUser"); 1257 if (@userIDs) { 1258 @userRecords = $db->getUsers(@userIDs); 1259 } 1260 } elsif ($filter_type eq "filter_user_id") { 1261 @userRecords = (); 1262 if ($filter_user_id ne "") { 1263 my $userRecord = $db->getUser($filter_user_id); 1264 @userRecords = ($userRecord) if $userRecord; 1265 } 1266 } elsif ($filter_type eq "filter_section") { 1267 @userRecords = (); 1268 @userRecords = @{$sections{$filter_section}} 1269 if exists $sections{$filter_section}; 1270 } elsif ($filter_type eq "filter_recitation") { 1271 @userRecords = (); 1272 @userRecords = @{$recitations{$filter_recitation}} 1273 if exists $recitations{$filter_recitation}; 1274 } 1275 1276 @userRecords = sort { 1277 (lc $a->section cmp lc $b->section) 1278 || (lc $a->last_name cmp lc $b->last_name) 1279 || (lc $a->first_name cmp lc $b->first_name) 1280 || (lc $a->user_id cmp lc $b->user_id) 1281 } @userRecords; 1282 1283 print CGI::start_form({method=>"post", action=>$r->uri()}); 1284 print $self->hidden_authen_fields(); 1285 1286 filter options 1287 my %labels = ( 1288 none => "No users", 1289 all => "All " . scalar @userIDs . " users", 1290 filter_selected => "Users selected below", 1291 filter_user_id => "User with ID " . CGI::input({ 1292 type=>"text", 1293 name=>"filter_user_id", 1294 value=>$filter_user_id, 1295 size=>"20" 1296 }), 1297 filter_section => "Users in section " . CGI::popup_menu( 1298 -name=>"filter_section", 1299 -values=>[ keys %sections ], 1300 -labels=>{ $self->menuLabels(\%sections) }, 1301 -default=>$filter_section, 1302 ), 1303 filter_recitation => "Users in recitation " . CGI::popup_menu( 1304 -name=>"filter_recitation", 1305 -values=>[ sort keys %recitations ], 1306 -labels=>{ $self->menuLabels(\%recitations) }, 1307 -default=>$filter_recitation, 1308 ), 1309 ); 1310 1311 if ($editMode) { 1312 print CGI::hidden( 1313 -name=>"filter_type", 1314 -value=>"filter_selected", 1315 ); 1316 } else { 1317 my $cgi = new CGI; 1318 $cgi->autoEscape(0); 1319 print "Show:", CGI::br(); 1320 print $cgi->radio_group( 1321 -name=>"filter_type", 1322 -values=>[ qw(none all filter_selected filter_user_id filter_section filter_recitation) ], 1323 -default=>$filter_type, 1324 -linebreak=>"true", 1325 -labels=>\%labels, 1326 -rows=>3, 1327 -columns=>2, 1328 ); 1329 print CGI::submit({name=>"filter", value=>"Filter"}); 1330 } 1331 1332 print CGI::start_table({}); 1333 1334 # Table headings, prettied-up 1335 my @tableHeadings = ( 1336 ($editMode ? () : "Select"), 1337 map {$prettyFieldNames{$_}} ( 1338 $userTemplate->KEYFIELDS(), 1339 $userTemplate->NONKEYFIELDS(), 1340 $permissionLevelTemplate->NONKEYFIELDS(), 1341 ), 1342 ); 1343 1344 # now print them 1345 print CGI::Tr({}, 1346 CGI::th({}, \@tableHeadings) 1347 ); 1348 1349 my @userIDsForHiddenSelectField; 1350 1351 # process user records 1352 foreach my $userRecord (@userRecords) { 1353 my $currentUser = $userRecord->user_id; 1354 push @userIDsForHiddenSelectField, $currentUser; 1355 my $permissionLevel = $db->getPermissionLevel($currentUser); 1356 unless (defined $permissionLevel) { 1357 warn "No permissionLevel record for user $currentUser -- added"; 1358 my $newPermissionLevel = $db->newPermissionLevel; 1359 $newPermissionLevel->user_id($currentUser); 1360 $newPermissionLevel->permission(0); 1361 $db->addPermissionLevel($newPermissionLevel); 1362 $permissionLevel = $newPermissionLevel; 1363 # permission set to minimum level 1364 } 1365 1366 # A concise way of printing a row containing a cell for each field, editable unless it's a key 1367 print CGI::Tr({}, 1368 CGI::td({}, [ 1369 ($editMode 1370 ? () # don't show selection checkbox if we're in edit mode -- hidden field below 1371 #: CGI::input({type=>"checkbox", name=>"selectUser", value=>$currentUser}) 1372 : CGI::checkbox( 1373 -name=>"selectUser", 1374 -value=>$currentUser, 1375 -checked=>($filter_type eq "filter_selected" and not defined $r->param("editingAllVisibleUsers")), 1376 -label=>"" 1377 ) 1378 ), 1379 ($editMode 1380 ? $currentUser 1381 : (map { 1382 my $changeEUserURL = "$root/$courseName?" 1383 . "user=" . $r->param("user") 1384 . "&effectiveUser=" . $userRecord->user_id 1385 . "&key=" . $r->param("key"); 1386 CGI::a({href=>$changeEUserURL}, $userRecord->$_) 1387 } $userRecord->KEYFIELDS) 1388 ), 1389 (map { 1390 $self->fieldEditHTML( 1391 "user." . $userRecord->user_id . "." .$_, 1392 $userRecord->$_, $fieldProperties{$_}); 1393 } $userRecord->NONKEYFIELDS()), 1394 (map { 1395 $self->fieldEditHTML( 1396 "permission." . $permissionLevel->user_id . "." . $_, 1397 $permissionLevel->$_, $fieldProperties{$_}); 1398 } $permissionLevel->NONKEYFIELDS()), 1399 ]) 1400 ); 1401 } 1402 1403 unless (@userRecords) { 1404 print CGI::Tr({}, 1405 CGI::td({-colspan=>scalar(@tableHeadings), -align=>"center"}, 1406 "No users match the filter criteria above." 1407 ), 1408 ); 1409 } 1410 1411 print CGI::end_table(); 1412 1413 if ($editMode) { 1414 print CGI::hidden(-name=>"selectUser", -value=>[ @userIDsForHiddenSelectField ]); 1415 if (defined $r->param("edit_visible")) { 1416 print CGI::hidden(-name=>"editingAllVisibleUsers", -value=>1); 1417 } 1418 } 1419 1420 if ($editMode) { 1421 print CGI::submit({name=>"discard_chagnes", value=>"Discard Changes to Users"}); 1422 print CGI::submit({name=>"save_classlist", value=>"Save Changes to Users"}); 1423 } else { 1424 print CGI::submit({name=>"edit_visible", value=>"Edit Visible Users"}); 1425 print CGI::submit({name=>"edit_selected", value=>"Edit Selected Users"}); 1426 print CGI::submit({name=>"delete_selected", value=>"Delete Selected Users"}); 1427 } 1428 1429 print CGI::end_form(); 1430 1431 # Add a student form 1432 unless ($editMode) { 1433 print CGI::start_form({method=>"post", action=>$r->uri()}); 1434 print $self->hidden_authen_fields(); 1435 print "User ID:"; 1436 print CGI::input({type=>"text", name=>"newUserID", value=>"", size=>"20"}); 1437 print CGI::submit({name=>"addStudent", value=>"Add User"}); 1438 print CGI::end_form(); 1439 } 1440 1441 return "";
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |