Parent Directory
|
Revision Log
Normalized headers. All files now contain the text below as a header. This is important since all files now (a) use the full name of the package, (b) assign copyright to "The WeBWorK Project", (c) give the full path of the file (relative to CVSROOT) instead of simply the file name, and (d) include license and warranty information. Here is the new header: ################################################################################ # WeBWorK Online Homework Delivery System # Copyright © 2000-2003 The WeBWorK Projcct, http://openwebwork.sf.net/ # $CVSHeader$ # # This program is free software; you can redistribute it and/or modify it under # the terms of either: (a) the GNU General Public License as published by the # Free Software Foundation; either version 2, or (at your option) any later # version, or (b) the "Artistic License" which comes with this package. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the # Artistic License for more details. ################################################################################
1 ################################################################################ 2 # WeBWorK Online Homework Delivery System 3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ 4 # $CVSHeader$ 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 67 use constant HIDE_USERS_THRESHHOLD => 20; 68 use constant EDIT_FORMS => [qw(cancelEdit saveEdit)]; 69 use constant VIEW_FORMS => [qw(filter edit delete import export)]; 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 148 sub initialize { 149 my ($self) = @_; 150 my $r = $self->{r}; 151 my $db = $self->{db}; 152 my $ce = $self->{ce}; 153 my $authz = $self->{authz}; 154 my $user = $r->param('user'); 155 156 unless ($authz->hasPermissions($user, "modify_student_data")) { 157 $self->{submitError} = "You are not authorized to modify student data"; 158 return; 159 } 160 161 #if (defined($r->param('addStudent'))) { 162 # my $newUser = $db->newUser; 163 # my $newPermissionLevel = $db->newPermissionLevel; 164 # my $newPassword = $db->newPassword; 165 # $newUser->user_id($r->param('newUserID')); 166 # $newPermissionLevel->user_id($r->param('newUserID')); 167 # $newPassword->user_id($r->param('newUserID')); 168 # $newUser->status('C'); 169 # $newPermissionLevel->permission(0); 170 # $db->addUser($newUser); 171 # $db->addPermissionLevel($newPermissionLevel); 172 # $db->addPassword($newPassword); 173 #} 174 } 175 176 sub title { 177 my $self = shift; 178 return "User List"; 179 } 180 181 sub path { 182 my $self = shift; 183 my $args = $_[-1]; 184 my $ce = $self->{ce}; 185 my $root = $ce->{webworkURLs}->{root}; 186 my $courseName = $ce->{courseName}; 187 188 return $self->pathMacro($args, 189 "Home" => "$root", 190 $courseName => "$root/$courseName", 191 "Instructor Tools" => "$root/$courseName/instructor", 192 "Users" => "", # "$root/$courseName/instructor/users", 193 ); 194 } 195 196 sub body { 197 my ($self, $setID) = @_; 198 my $r = $self->{r}; 199 my $authz = $self->{authz}; 200 my $user = $r->param('user'); 201 my $db = $self->{db}; 202 my $ce = $self->{ce}; 203 my $root = $ce->{webworkURLs}->{root}; 204 my $courseName = $ce->{courseName}; 205 206 # templates for getting field names 207 my $userTemplate = $self->{userTemplate} = $db->newUser; 208 my $permissionLevelTemplate = $self->{permissionLevelTemplate} = $db->newPermissionLevel; 209 210 return CGI::em("You are not authorized to access the Instructor tools.") 211 unless $authz->hasPermissions($user, "access_instructor_tools"); 212 213 # This table can be consulted when display-ready forms of field names are needed. 214 my %prettyFieldNames = map { $_ => $_ } 215 $userTemplate->FIELDS(), 216 $permissionLevelTemplate->FIELDS(); 217 218 @prettyFieldNames{qw( 219 user_id 220 first_name 221 last_name 222 email_address 223 student_id 224 status 225 section 226 recitation 227 comment 228 permission 229 )} = ( 230 "User ID", 231 "First Name", 232 "Last Name", 233 "E-mail", 234 "Student ID", 235 "Status", 236 "Section", 237 "Recitation", 238 "Comment", 239 "Perm. Level" 240 ); 241 242 ########## set initial values for state fields 243 244 my @allUserIDs = $db->listUsers; 245 $self->{allUserIDs} = \@allUserIDs; 246 247 if (defined $r->param("visible_users")) { 248 $self->{visibleUserIDs} = [ $r->param("visible_users") ]; 249 } elsif (defined $r->param("no_visible_users")) { 250 $self->{visibleUserIDs} = []; 251 } else { 252 if (@allUserIDs > HIDE_USERS_THRESHHOLD) { 253 $self->{visibleUserIDs} = []; 254 } else { 255 $self->{visibleUserIDs} = [ @allUserIDs ]; 256 } 257 } 258 259 $self->{prevVisibleUserIDs} = $self->{visibleUserIDs}; 260 261 if (defined $r->param("selected_users")) { 262 $self->{selectedUserIDs} = [ $r->param("selected_users") ]; 263 } else { 264 $self->{selectedUserIDs} = []; 265 } 266 267 $self->{editMode} = $r->param("editMode") || 0; 268 269 $self->{sortField} = $r->param("sortField") || "last_name"; 270 271 my @allUsers = $db->getUsers(@allUserIDs); 272 my (%sections, %recitations); 273 foreach my $User (@allUsers) { 274 push @{$sections{$User->section}}, $User->user_id; 275 push @{$recitations{$User->recitation}}, $User->user_id; 276 } 277 $self->{sections} = \%sections; 278 $self->{recitations} = \%recitations; 279 280 ########## call action handler 281 282 my $actionID = $r->param("action"); 283 if ($actionID) { 284 unless (grep { $_ eq $actionID } @{ VIEW_FORMS() }, @{ EDIT_FORMS() }) { 285 die "Action $actionID not found"; 286 } 287 my $actionHandler = "${actionID}_handler"; 288 my %genericParams; 289 foreach my $param (qw(selected_users)) { 290 $genericParams{$param} = [ $r->param($param) ]; 291 } 292 my %actionParams = $self->getActionParams($actionID); 293 my %tableParams = $self->getTableParams(); 294 print CGI::p( 295 "Result of last action performed: ", 296 CGI::i($self->$actionHandler(\%genericParams, \%actionParams, \%tableParams)) 297 ); 298 } 299 300 ########## retrieve possibly changed values for member fields 301 302 #@allUserIDs = @{ $self->{allUserIDs} }; # do we need this one? 303 my @visibleUserIDs = @{ $self->{visibleUserIDs} }; 304 my @prevVisibleUserIDs = @{ $self->{prevVisibleUserIDs} }; 305 my @selectedUserIDs = @{ $self->{selectedUserIDs} }; 306 my $editMode = $self->{editMode}; 307 my $sortField = $self->{sortField}; 308 309 #warn "visibleUserIDs=@visibleUserIDs\n"; 310 #warn "prevVisibleUserIDs=@prevVisibleUserIDs\n"; 311 #warn "selectedUserIDs=@selectedUserIDs\n"; 312 #warn "editMode=$editMode\n"; 313 314 ########## get required users 315 316 my @Users = grep { defined $_ } @visibleUserIDs ? $db->getUsers(@visibleUserIDs) : (); 317 318 # presort users 319 my %sortSubs = %{ SORT_SUBS() }; 320 my $sortSub = $sortSubs{$sortField}; 321 #@Users = sort $sortSub @Users; 322 @Users = sort byLnFnUid @Users; 323 324 my @PermissionLevels; 325 326 for (my $i = 0; $i < @Users; $i++) { 327 my $User = $Users[$i]; 328 my $PermissionLevel = $db->getPermissionLevel($User->user_id); 329 330 unless ($PermissionLevel) { 331 # uh oh! no permission level record found! 332 warn "added missing permission level for user ", $User->user_id, "\n"; 333 334 # create a new permission level record 335 $PermissionLevel = $db->newPermissionLevel; 336 $PermissionLevel->user_id($User->user_id); 337 $PermissionLevel->permission(0); 338 339 # add it to the database 340 $db->addPermissionLevel($PermissionLevel); 341 } 342 343 $PermissionLevels[$i] = $PermissionLevel; 344 } 345 346 ########## print beginning of form 347 348 print CGI::start_form({method=>"post", action=>$r->uri, name=>"userlist"}); 349 print $self->hidden_authen_fields(); 350 351 ########## print state data 352 353 print "\n<!-- state data here -->\n"; 354 355 if (@visibleUserIDs) { 356 print CGI::hidden(-name=>"visible_users", -value=>\@visibleUserIDs); 357 } else { 358 print CGI::hidden(-name=>"no_visible_users", -value=>"1"); 359 } 360 361 if (@prevVisibleUserIDs) { 362 print CGI::hidden(-name=>"prev_visible_users", -value=>\@prevVisibleUserIDs); 363 } else { 364 print CGI::hidden(-name=>"no_prev_visible_users", -value=>"1"); 365 } 366 367 print CGI::hidden(-name=>"editMode", -value=>$editMode); 368 369 print CGI::hidden(-name=>"sortField", -value=>$sortField); 370 371 print "\n<!-- state data here -->\n"; 372 373 ########## print action forms 374 375 print CGI::start_table({}); 376 print CGI::Tr({}, CGI::td({-colspan=>2}, "Select an action to perform:")); 377 378 my @formsToShow; 379 if ($editMode) { 380 @formsToShow = @{ EDIT_FORMS() }; 381 } else { 382 @formsToShow = @{ VIEW_FORMS() }; 383 } 384 385 my $i = 0; 386 foreach my $actionID (@formsToShow) { 387 my $actionForm = "${actionID}_form"; 388 my $onChange = "document.userlist.action[$i].checked=true"; 389 my %actionParams = $self->getActionParams($actionID); 390 391 print CGI::Tr({-valign=>"top"}, 392 CGI::td({}, CGI::input({-type=>"radio", -name=>"action", -value=>$actionID})), 393 CGI::td({}, $self->$actionForm($onChange, %actionParams)) 394 ); 395 396 $i++; 397 } 398 399 print CGI::Tr({}, CGI::td({-colspan=>2, -align=>"center"}, 400 CGI::submit(-value=>"Take Action!")) 401 ); 402 print CGI::end_table(); 403 404 ########## print table 405 406 print CGI::p("Showing ", scalar @visibleUserIDs, " out of ", scalar @allUserIDs, " users."); 407 408 $self->printTableHTML(\@Users, \@PermissionLevels, \%prettyFieldNames, 409 editMode => $editMode, 410 selectedUserIDs => \@selectedUserIDs, 411 ); 412 413 414 ########## print end of form 415 416 print CGI::end_form(); 417 418 return ""; 419 } 420 421 ################################################################################ 422 # extract particular params and put them in a hash (values are ARRAYREFs!) 423 ################################################################################ 424 425 sub getActionParams { 426 my ($self, $actionID) = @_; 427 my $r = $self->{r}; 428 429 my %actionParams; 430 foreach my $param ($r->param) { 431 next unless $param =~ m/^action\.$actionID\./; 432 $actionParams{$param} = [ $r->param($param) ]; 433 } 434 return %actionParams; 435 } 436 437 sub getTableParams { 438 my ($self) = @_; 439 my $r = $self->{r}; 440 441 my %tableParams; 442 foreach my $param ($r->param) { 443 next unless $param =~ m/^(?:user|permission)\./; 444 $tableParams{$param} = [ $r->param($param) ]; 445 } 446 return %tableParams; 447 } 448 449 ################################################################################ 450 # actions and action triggers 451 ################################################################################ 452 453 # filter, edit, cancelEdit, and saveEdit should stay with the display module and 454 # not be real "actions". that way, all actions are shown in view mode and no 455 # actions are shown in edit mode. 456 457 sub filter_form { 458 my ($self, $onChange, %actionParams) = @_; 459 #return CGI::table({}, CGI::Tr({-valign=>"top"}, 460 # CGI::td({}, 461 return join("", 462 "Show ", 463 CGI::popup_menu( 464 -name => "action.filter.scope", 465 -values => [qw(all none selected match_ids match_section match_recitation)], 466 -default => $actionParams{"action.filter.scope"}->[0] || "selected", 467 -labels => { 468 all => "all users", 469 none => "no users", 470 selected => "users selected below", 471 match_ids => "users with matching user IDs:", 472 match_section => "users in selected section", 473 match_recitation => "users in selected recitation", 474 }, 475 -onchange => $onChange, 476 ), 477 " ", 478 CGI::textfield( 479 -name => "action.filter.user_ids", 480 -value => $actionParams{"action.filter.user_ids"}->[0] || "",, 481 -width => "50", 482 -onchange => $onChange, 483 ), 484 " (separate multiple IDs with commas)", 485 CGI::br(), 486 "sections: ", 487 CGI::popup_menu( 488 -name => "action.filter.section", 489 -values => [ keys %{ $self->{sections} } ], 490 -default => $actionParams{"action.filter.section"}->[0] || "", 491 -labels => { $self->menuLabels($self->{sections}) }, 492 -onchange => $onChange, 493 ), 494 " recitations: ", 495 CGI::popup_menu( 496 -name => "action.filter.recitation", 497 -values => [ keys %{ $self->{recitations} } ], 498 -default => $actionParams{"action.filter.recitation"}->[0] || "", 499 -labels => { $self->menuLabels($self->{recitations}) }, 500 -onchange => $onChange, 501 ), 502 ); 503 # ), 504 #)); 505 } 506 507 # this action handler modifies the "visibleUserIDs" field based on the contents 508 # of the "action.filter.scope" parameter and the "selected_users" 509 sub filter_handler { 510 my ($self, $genericParams, $actionParams, $tableParams) = @_; 511 512 my $result; 513 514 my $scope = $actionParams->{"action.filter.scope"}->[0]; 515 if ($scope eq "all") { 516 $result = "showing all users"; 517 $self->{visibleUserIDs} = $self->{allUserIDs}; 518 } elsif ($scope eq "none") { 519 $result = "showing no users"; 520 $self->{visibleUserIDs} = []; 521 } elsif ($scope eq "selected") { 522 $result = "showing selected users"; 523 $self->{visibleUserIDs} = $genericParams->{selected_users}; # an arrayref 524 } elsif ($scope eq "match_ids") { 525 my @userIDs = split /\s*,\s*/, $actionParams->{"action.filter.user_ids"}->[0]; 526 $self->{visibleUserIDs} = \@userIDs; 527 } elsif ($scope eq "match_section") { 528 my $section = $actionParams->{"action.filter.section"}->[0]; 529 $self->{visibleUserIDs} = $self->{sections}->{$section}; # an arrayref 530 } elsif ($scope eq "match_recitation") { 531 my $recitation = $actionParams->{"action.filter.recitation"}->[0]; 532 $self->{visibleUserIDs} = $self->{recitations}->{$recitation}; # an arrayref 533 } 534 535 return $result; 536 } 537 538 sub edit_form { 539 my ($self, $onChange, %actionParams) = @_; 540 return join("", 541 "Edit ", 542 CGI::popup_menu( 543 -name => "action.edit.scope", 544 -values => [qw(all visible selected)], 545 -default => $actionParams{"action.edit.scope"}->[0] || "visible", 546 -labels => { 547 all => "all users", 548 visible => "visible users", 549 selected => "selected users" 550 }, 551 -onchange => $onChange, 552 ), 553 ); 554 } 555 556 sub edit_handler { 557 my ($self, $genericParams, $actionParams, $tableParams) = @_; 558 559 my $result; 560 561 my $scope = $actionParams->{"action.edit.scope"}->[0]; 562 if ($scope eq "all") { 563 $result = "editing all users"; 564 $self->{visibleUserIDs} = $self->{allUserIDs}; 565 } elsif ($scope eq "visible") { 566 $result = "editing visible users"; 567 # leave visibleUserIDs alone 568 } elsif ($scope eq "selected") { 569 $result = "editing selected users"; 570 $self->{visibleUserIDs} = $genericParams->{selected_users}; # an arrayref 571 } 572 $self->{editMode} = 1; 573 574 return $result; 575 } 576 577 sub delete_form { 578 my ($self, $onChange, %actionParams) = @_; 579 return join("", 580 "Delete ", 581 CGI::popup_menu( 582 -name => "action.delete.scope", 583 -values => [qw(visible selected)], 584 -default => $actionParams{"action.delete.scope"}->[0] || "selected", 585 -labels => { 586 visible => "visible users", 587 selected => "selected users" 588 }, 589 -onchange => $onChange, 590 ), 591 CGI::em(" Deletion destroys all user-related data and is not undoable!"), 592 ); 593 } 594 595 sub delete_handler { 596 my ($self, $genericParams, $actionParams, $tableParams) = @_; 597 my $db = $self->{db}; 598 my $scope = $actionParams->{"action.delete.scope"}->[0]; 599 600 my @userIDsToDelete; 601 if ($scope eq "visible") { 602 @userIDsToDelete = @{ $self->{visibleUserIDs} }; 603 } elsif ($scope eq "selected") { 604 @userIDsToDelete = @{ $self->{selectedUserIDs} }; 605 } 606 607 my %allUserIDs = map { $_ => 1 } @{ $self->{allUserIDs} }; 608 my %visibleUserIDs = map { $_ => 1 } @{ $self->{visibleUserIDs} }; 609 my %selectedUserIDs = map { $_ => 1 } @{ $self->{selectedUserIDs} }; 610 611 foreach my $userID (@userIDsToDelete) { 612 delete $allUserIDs{$userID}; 613 delete $visibleUserIDs{$userID}; 614 delete $selectedUserIDs{$userID}; 615 $db->deleteUser($userID); 616 } 617 618 $self->{allUserIDs} = [ keys %allUserIDs ]; 619 $self->{visibleUserIDs} = [ keys %visibleUserIDs ]; 620 $self->{selectedUserIDs} = [ keys %selectedUserIDs ]; 621 622 my $num = @userIDsToDelete; 623 return "deleted $num user" . ($num == 1 ? "" : "s"); 624 } 625 626 sub import_form { 627 my ($self, $onChange, %actionParams) = @_; 628 return join(" ", 629 "Import users from file", 630 CGI::popup_menu( 631 -name => "action.import.source", 632 -values => [ "", $self->getCSVList() ], 633 -default => $actionParams{"action.import.source"}->[0] || "", 634 -onchange => $onChange, 635 ), 636 "replacing", 637 CGI::popup_menu( 638 -name => "action.import.replace", 639 -values => [qw(any visible selected none)], 640 -default => $actionParams{"action.import.replace"}->[0] || "none", 641 -labels => { 642 any => "any", 643 visible => "visible", 644 selected => "selected", 645 none => "no", 646 }, 647 -onchange => $onChange, 648 ), 649 "existing users and adding", 650 CGI::popup_menu( 651 -name => "action.import.add", 652 -values => [qw(any none)], 653 -default => $actionParams{"action.import.add"}->[0] || "any", 654 -labels => { 655 any => "any", 656 none => "no", 657 }, 658 -onchange => $onChange, 659 ), 660 "new users", 661 ); 662 } 663 664 sub import_handler { 665 my ($self, $genericParams, $actionParams, $tableParams) = @_; 666 667 my $source = $actionParams->{"action.import.source"}->[0]; 668 my $add = $actionParams->{"action.import.add"}->[0]; 669 my $replace = $actionParams->{"action.import.replace"}->[0]; 670 671 my $fileName = $source; 672 my $createNew = $add eq "any"; 673 my $replaceExisting; 674 my @replaceList; 675 if ($replace eq "any") { 676 $replaceExisting = "any"; 677 } elsif ($replace eq "none") { 678 $replaceExisting = "none"; 679 } elsif ($replace eq "visible") { 680 $replaceExisting = "listed"; 681 @replaceList = @{ $self->{visibleUserIDs} }; 682 } elsif ($replace eq "selected") { 683 $replaceExisting = "listed"; 684 @replaceList = @{ $self->{selectedUserIDs} }; 685 } 686 687 my ($replaced, $added, $skipped) 688 = $self->importUsersFromCSV($fileName, $createNew, $replaceExisting, @replaceList); 689 690 # make new users visible... do we really want to do this? probably. 691 push @{ $self->{visibleUserIDs} }, @$added; 692 693 my $numReplaced = @$replaced; 694 my $numAdded = @$added; 695 my $numSkipped = @$skipped; 696 697 return $numReplaced . " user" . ($numReplaced == 1 ? "" : "s") . " replaced, " 698 . $numAdded . " user" . ($numAdded == 1 ? "" : "s") . " added, " 699 . $numSkipped . " user" . ($numSkipped == 1 ? "" : "s") . " skipped."; 700 } 701 702 sub export_form { 703 my ($self, $onChange, %actionParams) = @_; 704 return join("", 705 "Export ", 706 CGI::popup_menu( 707 -name => "action.export.scope", 708 -values => [qw(all visible selected)], 709 -default => $actionParams{"action.export.scope"}->[0] || "visible", 710 -labels => { 711 all => "all users", 712 visible => "visible users", 713 selected => "selected users" 714 }, 715 -onchange => $onChange, 716 ), 717 " to ", 718 CGI::popup_menu( 719 -name=>"action.export.target", 720 -values => [ "new", $self->getCSVList() ], 721 -labels => { new => "a new file named:" }, 722 -default => $actionParams{"action.export.target"}->[0] || "", 723 -onchange => $onChange, 724 ), 725 #CGI::br(), 726 #"new file to create: ", 727 CGI::textfield( 728 -name => "action.export.new", 729 -value => $actionParams{"action.export.new"}->[0] || "",, 730 -width => "50", 731 -onchange => $onChange, 732 ), 733 CGI::tt(".lst"), 734 ); 735 } 736 737 sub export_handler { 738 my ($self, $genericParams, $actionParams, $tableParams) = @_; 739 740 my $scope = $actionParams->{"action.export.scope"}->[0]; 741 my $target = $actionParams->{"action.export.target"}->[0]; 742 my $new = $actionParams->{"action.export.new"}->[0]; 743 744 my $fileName; 745 if ($target eq "new") { 746 $fileName = $new; 747 } else { 748 $fileName = $target; 749 } 750 751 $fileName .= ".lst" unless $fileName =~ m/\.lst$/; 752 753 my @userIDsToExport; 754 if ($scope eq "all") { 755 @userIDsToExport = @{ $self->{allUserIDs} }; 756 } elsif ($scope eq "visible") { 757 @userIDsToExport = @{ $self->{visibleUserIDs} }; 758 } elsif ($scope eq "selected") { 759 @userIDsToExport = @{ $self->{selectedUserIDs} }; 760 } 761 762 $self->exportUsersToCSV($fileName, @userIDsToExport); 763 764 return scalar @userIDsToExport . " users exported"; 765 } 766 767 sub cancelEdit_form { 768 my ($self, $onChange, %actionParams) = @_; 769 return "Abandon changes"; 770 } 771 772 sub cancelEdit_handler { 773 my ($self, $genericParams, $actionParams, $tableParams) = @_; 774 my $r = $self->{r}; 775 776 #$self->{selectedUserIDs} = $self->{visibleUserIDs}; 777 # only do the above if we arrived here via "edit selected users" 778 if (defined $r->param("prev_visible_users")) { 779 $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ]; 780 } elsif (defined $r->param("no_prev_visible_users")) { 781 $self->{visibleUserIDs} = []; 782 } else { 783 # leave it alone 784 } 785 $self->{editMode} = 0; 786 787 return "changes abandoned"; 788 } 789 790 sub saveEdit_form { 791 my ($self, $onChange, %actionParams) = @_; 792 return "Save changes"; 793 } 794 795 sub saveEdit_handler { 796 my ($self, $genericParams, $actionParams, $tableParams) = @_; 797 my $r = $self->{r}; 798 my $db = $self->{db}; 799 800 my @visibleUserIDs = @{ $self->{visibleUserIDs} }; 801 foreach my $userID (@visibleUserIDs) { 802 my $User = $db->getUser($userID); 803 my $PermissionLevel = $db->getPermissionLevel($userID); 804 805 foreach my $field ($User->NONKEYFIELDS()) { 806 my $param = "user.${userID}.${field}"; 807 if (defined $tableParams->{$param}->[0]) { 808 $User->$field($tableParams->{$param}->[0]); 809 } 810 } 811 812 foreach my $field ($PermissionLevel->NONKEYFIELDS()) { 813 my $param = "permission.${userID}.${field}"; 814 if (defined $tableParams->{$param}->[0]) { 815 $PermissionLevel->$field($tableParams->{$param}->[0]); 816 } 817 } 818 819 $db->putUser($User); 820 $db->putPermissionLevel($PermissionLevel); 821 } 822 823 if (defined $r->param("prev_visible_users")) { 824 $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ]; 825 } elsif (defined $r->param("no_prev_visible_users")) { 826 $self->{visibleUserIDs} = []; 827 } else { 828 # leave it alone 829 } 830 831 $self->{editMode} = 0; 832 833 return "changes saved"; 834 } 835 836 ################################################################################ 837 # sorts 838 ################################################################################ 839 840 sub byUserID { $a->user_id cmp $b->user_id } 841 sub byFirstName { $a->first_name cmp $b->first_name } 842 sub byLastName { $a->last_name cmp $b->last_name } 843 sub byEmailAddress { $a->email_address cmp $b->email_address } 844 sub byStudentID { $a->student_id cmp $b->student_id } 845 sub byStatus { $a->status cmp $b->status } 846 sub bySection { $a->section cmp $b->section } 847 sub byRecitation { $a->recitation cmp $b->recitation } 848 sub byComment { $a->comment cmp $b->comment } 849 850 sub byLnFnUid { &byLastName || &byFirstName || &byUserID } 851 852 ################################################################################ 853 # utilities 854 ################################################################################ 855 856 # generate labels for section/recitation popup menus 857 sub menuLabels { 858 my ($self, $hashRef) = @_; 859 my %hash = %$hashRef; 860 861 my %result; 862 foreach my $key (keys %hash) { 863 my $count = @{ $hash{$key} }; 864 my $displayKey = $key || "<none>"; 865 $result{$key} = "$displayKey ($count users)"; 866 } 867 return %result; 868 } 869 870 sub importUsersFromCSV { 871 my ($self, $fileName, $createNew, $replaceExisting, @replaceList) = @_; 872 my $ce = $self->{ce}; 873 my $db = $self->{db}; 874 my $dir = $ce->{courseDirs}->{templates}; 875 876 die "illegal character in input: \"/\"" if $fileName =~ m|/|; 877 die "won't be able to read from file $dir/$fileName: does it exist? is it readable?" 878 unless -r "$dir/$fileName"; 879 880 my %allUserIDs = map { $_ => 1 } @{ $self->{allUserIDs} }; 881 my %replaceOK; 882 if ($replaceExisting eq "none") { 883 %replaceOK = (); 884 } elsif ($replaceExisting eq "listed") { 885 %replaceOK = map { $_ => 1 } @replaceList; 886 } elsif ($replaceExisting eq "any") { 887 %replaceOK = %allUserIDs; 888 } 889 890 my (@replaced, @added, @skipped); 891 892 my @contents = split /\n/, readFile("$dir/$fileName"); 893 foreach my $string (@contents) { 894 $string =~ s/^\s+//; 895 $string =~ s/\s+$//; 896 my ( 897 $student_id, $last_name, $first_name, $status, $comment, 898 $section, $recitation, $email_address, $user_id 899 ) = split /\s*,\s*/, $string; 900 901 if (exists $allUserIDs{$user_id} and not exists $replaceOK{$user_id}) { 902 push @skipped, $user_id; 903 next; 904 } 905 906 if (not exists $allUserIDs{$user_id} and not $createNew) { 907 push @skipped, $user_id; 908 next; 909 } 910 911 my $User = $db->newUser; 912 $User->user_id($user_id); 913 $User->first_name($first_name); 914 $User->last_name($last_name); 915 $User->email_address($email_address); 916 $User->student_id($student_id); 917 $User->status($status); 918 $User->section($section); 919 $User->recitation($recitation); 920 $User->comment($comment); 921 922 my $PermissionLevel = $db->newPermissionLevel; 923 $PermissionLevel->user_id($user_id); 924 $PermissionLevel->permission(0); 925 926 my $Password = $db->newPassword; 927 $Password->user_id($user_id); 928 $Password->password(cryptPassword($student_id)); 929 930 if (exists $allUserIDs{$user_id}) { 931 $db->putUser($User); 932 $db->putPermissionLevel($PermissionLevel); 933 $db->putPassword($Password); 934 push @replaced, $user_id; 935 } else { 936 $db->addUser($User); 937 $db->addPermissionLevel($PermissionLevel); 938 $db->addPassword($Password); 939 push @added, $user_id; 940 } 941 } 942 943 return \@replaced, \@added, \@skipped; 944 } 945 946 sub exportUsersToCSV { 947 my ($self, $fileName, @userIDsToExport) = @_; 948 my $ce = $self->{ce}; 949 my $db = $self->{db}; 950 my $dir = $ce->{courseDirs}->{templates}; 951 952 die "illegal character in input: \"/\"" if $fileName =~ m|/|; 953 954 open my $fh, ">", "$dir/$fileName" 955 or die "failed to open file $dir/$fileName for writing: $!\n"; 956 957 foreach my $userID (@userIDsToExport) { 958 my $User = $db->getUser($userID); 959 my @fields = ( 960 $User->student_id, 961 $User->last_name, 962 $User->first_name, 963 $User->status, 964 $User->comment, 965 $User->section, 966 $User->recitation, 967 $User->email_address, 968 $User->user_id, 969 ); 970 my $string = join ",", @fields; 971 print $fh "$string\n"; 972 } 973 974 close $fh; 975 } 976 977 ################################################################################ 978 # "display" methods 979 ################################################################################ 980 981 sub fieldEditHTML { 982 my ($self, $fieldName, $value, $properties) = @_; 983 my $size = $properties->{size}; 984 my $type = $properties->{type}; 985 my $access = $properties->{access}; 986 my $items = $properties->{items}; 987 my $synonyms = $properties->{synonyms}; 988 989 if ($access eq "readonly") { 990 return $value; 991 } 992 993 if ($type eq "number" or $type eq "text") { 994 return CGI::input({type=>"text", name=>$fieldName, value=>$value, size=>$size}); 995 } 996 997 if ($type eq "enumerable") { 998 my $matched = undef; # Whether a synonym match has occurred 999 1000 # Process synonyms for enumerable objects 1001 foreach my $synonym (keys %$synonyms) { 1002 if ($synonym ne "*" and $value =~ m/$synonym/) { 1003 $value = $synonyms->{$synonym}; 1004 $matched = 1; 1005 } 1006 } 1007 1008 if (!$matched and exists $synonyms->{"*"}) { 1009 $value = $synonyms->{"*"}; 1010 } 1011 1012 return CGI::popup_menu({ 1013 name => $fieldName, 1014 values => [keys %$items], 1015 default => $value, 1016 labels => $items, 1017 }); 1018 } 1019 } 1020 1021 sub recordEditHTML { 1022 my ($self, $User, $PermissionLevel, %options) = @_; 1023 my $r = $self->{r}; 1024 my $ce = $self->{ce}; 1025 my $root = $ce->{webworkURLs}->{root}; 1026 my $courseName = $ce->{courseName}; 1027 1028 my $editMode = $options{editMode}; 1029 my $userSelected = $options{userSelected}; 1030 1031 my $changeEUserURL = "$root/$courseName?" 1032 . "user=" . $r->param("user") 1033 . "&effectiveUser=" . $User->user_id 1034 . "&key=" . $r->param("key"); 1035 1036 my $setsAssignedToUserURL = "$root/$courseName/instructor/users/" 1037 . $User->user_id . "/sets/?" 1038 . "user=" . $r->param("user") 1039 . "&effectiveUser=" . $r->param("effectiveUser") 1040 . "&key=" . $r->param("key"); 1041 1042 my @tableCells; 1043 1044 # Select 1045 if ($editMode) { 1046 # column not there 1047 } else { 1048 # selection checkbox 1049 push @tableCells, CGI::checkbox( 1050 -name => "selected_users", 1051 -value => $User->user_id, 1052 -checked => $userSelected, 1053 -label => "", 1054 ); 1055 } 1056 1057 # Act As 1058 if ($editMode) { 1059 # column not there 1060 } else { 1061 # selection checkbox 1062 push @tableCells, CGI::a({href=>$changeEUserURL}, "Act As"); 1063 } 1064 1065 # User ID 1066 if ($editMode) { 1067 # straight user ID 1068 push @tableCells, $User->user_id; 1069 } else { 1070 # "edit sets assigned to user" link 1071 push @tableCells, CGI::a({href=>$setsAssignedToUserURL}, $User->user_id); 1072 } 1073 1074 # User Fields 1075 foreach my $field ($User->NONKEYFIELDS) { 1076 my $fieldName = "user." . $User->user_id . "." . $field, 1077 my $fieldValue = $User->$field; 1078 my %properties = %{ FIELD_PROPERTIES()->{$field} }; 1079 $properties{access} = "readonly" unless $editMode; 1080 push @tableCells, $self->fieldEditHTML($fieldName, $fieldValue, \%properties); 1081 } 1082 1083 # PermissionLevel Fields 1084 foreach my $field ($PermissionLevel->NONKEYFIELDS) { 1085 my $fieldName = "permission." . $PermissionLevel->user_id . "." . $field, 1086 my $fieldValue = $PermissionLevel->$field; 1087 my %properties = %{ FIELD_PROPERTIES()->{$field} }; 1088 $properties{access} = "readonly" unless $editMode; 1089 push @tableCells, $self->fieldEditHTML($fieldName, $fieldValue, \%properties); 1090 } 1091 1092 return CGI::Tr({}, CGI::td({}, \@tableCells)); 1093 } 1094 1095 sub printTableHTML { 1096 my ($self, $UsersRef, $PermissionLevelsRef, $fieldNamesRef, %options) = @_; 1097 my $r = $self->{r}; 1098 my $userTemplate = $self->{userTemplate}; 1099 my $permissionLevelTemplate = $self->{permissionLevelTemplate}; 1100 my @Users = @$UsersRef; 1101 my @PermissionLevels = @$PermissionLevelsRef; 1102 my %fieldNames = %$fieldNamesRef; 1103 1104 my $editMode = $options{editMode}; 1105 my %selectedUserIDs = map { $_ => 1 } @{ $options{selectedUserIDs} }; 1106 my $currentSort = $options{currentSort}; 1107 1108 # names of headings: 1109 my @realFieldNames = ( 1110 $userTemplate->KEYFIELDS, 1111 $userTemplate->NONKEYFIELDS, 1112 $permissionLevelTemplate->NONKEYFIELDS, 1113 ); 1114 1115 my %sortSubs = %{ SORT_SUBS() }; 1116 #my @stateParams = @{ STATE_PARAMS() }; 1117 #my $hrefPrefix = $r->uri . "?" . $self->url_args(@stateParams); # $self->url_authen_args 1118 my @tableHeadings; 1119 foreach my $field (@realFieldNames) { 1120 my $result; 1121 #if (exists $sortSubs{$field}) { 1122 # $result = CGI::a({-href=>"$hrefPrefix&sort=$field"}, $fieldNames{$field}); 1123 #} else { 1124 $result = $fieldNames{$field}; 1125 #} 1126 push @tableHeadings, $result; 1127 }; 1128 1129 # prepend selection checkbox? only if we're NOT editing! 1130 unshift @tableHeadings, "Select", "Act As" unless $editMode; 1131 1132 # print the table 1133 if ($editMode) { 1134 print CGI::start_table({}); 1135 } else { 1136 print CGI::start_table({-border=>1}); 1137 } 1138 1139 print CGI::Tr({}, CGI::th({}, \@tableHeadings)); 1140 1141 for (my $i = 0; $i < @Users; $i++) { 1142 my $User = $Users[$i]; 1143 my $PermissionLevel = $PermissionLevels[$i]; 1144 1145 print $self->recordEditHTML($User, $PermissionLevel, 1146 editMode => $editMode, 1147 userSelected => exists $selectedUserIDs{$User->user_id} 1148 ); 1149 } 1150 1151 print CGI::end_table(); 1152 } 1153 1154 1; 1155 1156 __END__ 1157 1158 my $editMode = 0; 1159 if (defined $r->param("edit_selected") or defined $r->param("edit_visible")) { 1160 $editMode = 1; 1161 } 1162 1163 my @userIDs = $db->listUsers; 1164 my @userRecords = $db->getUsers(@userIDs); 1165 1166 my (%sections, %recitations); 1167 foreach my $user (@userRecords) { 1168 push @{$sections{$user->section}}, $user; 1169 push @{$recitations{$user->recitation}}, $user; 1170 } 1171 1172 my $filter_type = $r->param("filter_type") 1173 || (@userIDs > HIDE_USERS_THRESHHOLD ? "none" : "all"); 1174 my $filter_user_id = $filter_type eq "filter_user_id" 1175 ? $r->param("filter_user_id") || "" 1176 : ""; 1177 my $filter_section = $filter_type eq "filter_section" 1178 ? $r->param("filter_section") || "" 1179 : ""; 1180 my $filter_recitation = $filter_type eq "filter_recitation" 1181 ? $r->param("filter_recitation") || "" 1182 : ""; 1183 1184 # override filter selection if "Edit Selected Users" button is pressed 1185 if (defined $r->param("edit_selected")) { 1186 $filter_type = "filter_selected"; 1187 } 1188 1189 if ($filter_type eq "none") { 1190 @userRecords = (); 1191 } elsif ($filter_type eq "filter_selected") { 1192 @userRecords = (); 1193 my @userIDs = $r->param("selectUser"); 1194 if (@userIDs) { 1195 @userRecords = $db->getUsers(@userIDs); 1196 } 1197 } elsif ($filter_type eq "filter_user_id") { 1198 @userRecords = (); 1199 if ($filter_user_id ne "") { 1200 my $userRecord = $db->getUser($filter_user_id); 1201 @userRecords = ($userRecord) if $userRecord; 1202 } 1203 } elsif ($filter_type eq "filter_section") { 1204 @userRecords = (); 1205 @userRecords = @{$sections{$filter_section}} 1206 if exists $sections{$filter_section}; 1207 } elsif ($filter_type eq "filter_recitation") { 1208 @userRecords = (); 1209 @userRecords = @{$recitations{$filter_recitation}} 1210 if exists $recitations{$filter_recitation}; 1211 } 1212 1213 @userRecords = sort { 1214 (lc $a->section cmp lc $b->section) 1215 || (lc $a->last_name cmp lc $b->last_name) 1216 || (lc $a->first_name cmp lc $b->first_name) 1217 || (lc $a->user_id cmp lc $b->user_id) 1218 } @userRecords; 1219 1220 print CGI::start_form({method=>"post", action=>$r->uri()}); 1221 print $self->hidden_authen_fields(); 1222 1223 filter options 1224 my %labels = ( 1225 none => "No users", 1226 all => "All " . scalar @userIDs . " users", 1227 filter_selected => "Users selected below", 1228 filter_user_id => "User with ID " . CGI::input({ 1229 type=>"text", 1230 name=>"filter_user_id", 1231 value=>$filter_user_id, 1232 size=>"20" 1233 }), 1234 filter_section => "Users in section " . CGI::popup_menu( 1235 -name=>"filter_section", 1236 -values=>[ keys %sections ], 1237 -labels=>{ $self->menuLabels(\%sections) }, 1238 -default=>$filter_section, 1239 ), 1240 filter_recitation => "Users in recitation " . CGI::popup_menu( 1241 -name=>"filter_recitation", 1242 -values=>[ sort keys %recitations ], 1243 -labels=>{ $self->menuLabels(\%recitations) }, 1244 -default=>$filter_recitation, 1245 ), 1246 ); 1247 1248 if ($editMode) { 1249 print CGI::hidden( 1250 -name=>"filter_type", 1251 -value=>"filter_selected", 1252 ); 1253 } else { 1254 my $cgi = new CGI; 1255 $cgi->autoEscape(0); 1256 print "Show:", CGI::br(); 1257 print $cgi->radio_group( 1258 -name=>"filter_type", 1259 -values=>[ qw(none all filter_selected filter_user_id filter_section filter_recitation) ], 1260 -default=>$filter_type, 1261 -linebreak=>"true", 1262 -labels=>\%labels, 1263 -rows=>3, 1264 -columns=>2, 1265 ); 1266 print CGI::submit({name=>"filter", value=>"Filter"}); 1267 } 1268 1269 print CGI::start_table({}); 1270 1271 # Table headings, prettied-up 1272 my @tableHeadings = ( 1273 ($editMode ? () : "Select"), 1274 map {$prettyFieldNames{$_}} ( 1275 $userTemplate->KEYFIELDS(), 1276 $userTemplate->NONKEYFIELDS(), 1277 $permissionLevelTemplate->NONKEYFIELDS(), 1278 ), 1279 ); 1280 1281 # now print them 1282 print CGI::Tr({}, 1283 CGI::th({}, \@tableHeadings) 1284 ); 1285 1286 my @userIDsForHiddenSelectField; 1287 1288 # process user records 1289 foreach my $userRecord (@userRecords) { 1290 my $currentUser = $userRecord->user_id; 1291 push @userIDsForHiddenSelectField, $currentUser; 1292 my $permissionLevel = $db->getPermissionLevel($currentUser); 1293 unless (defined $permissionLevel) { 1294 warn "No permissionLevel record for user $currentUser -- added"; 1295 my $newPermissionLevel = $db->newPermissionLevel; 1296 $newPermissionLevel->user_id($currentUser); 1297 $newPermissionLevel->permission(0); 1298 $db->addPermissionLevel($newPermissionLevel); 1299 $permissionLevel = $newPermissionLevel; 1300 # permission set to minimum level 1301 } 1302 1303 # A concise way of printing a row containing a cell for each field, editable unless it's a key 1304 print CGI::Tr({}, 1305 CGI::td({}, [ 1306 ($editMode 1307 ? () # don't show selection checkbox if we're in edit mode -- hidden field below 1308 #: CGI::input({type=>"checkbox", name=>"selectUser", value=>$currentUser}) 1309 : CGI::checkbox( 1310 -name=>"selectUser", 1311 -value=>$currentUser, 1312 -checked=>($filter_type eq "filter_selected" and not defined $r->param("editingAllVisibleUsers")), 1313 -label=>"" 1314 ) 1315 ), 1316 ($editMode 1317 ? $currentUser 1318 : (map { 1319 my $changeEUserURL = "$root/$courseName?" 1320 . "user=" . $r->param("user") 1321 . "&effectiveUser=" . $userRecord->user_id 1322 . "&key=" . $r->param("key"); 1323 CGI::a({href=>$changeEUserURL}, $userRecord->$_) 1324 } $userRecord->KEYFIELDS) 1325 ), 1326 (map { 1327 $self->fieldEditHTML( 1328 "user." . $userRecord->user_id . "." .$_, 1329 $userRecord->$_, $fieldProperties{$_}); 1330 } $userRecord->NONKEYFIELDS()), 1331 (map { 1332 $self->fieldEditHTML( 1333 "permission." . $permissionLevel->user_id . "." . $_, 1334 $permissionLevel->$_, $fieldProperties{$_}); 1335 } $permissionLevel->NONKEYFIELDS()), 1336 ]) 1337 ); 1338 } 1339 1340 unless (@userRecords) { 1341 print CGI::Tr({}, 1342 CGI::td({-colspan=>scalar(@tableHeadings), -align=>"center"}, 1343 "No users match the filter criteria above." 1344 ), 1345 ); 1346 } 1347 1348 print CGI::end_table(); 1349 1350 if ($editMode) { 1351 print CGI::hidden(-name=>"selectUser", -value=>[ @userIDsForHiddenSelectField ]); 1352 if (defined $r->param("edit_visible")) { 1353 print CGI::hidden(-name=>"editingAllVisibleUsers", -value=>1); 1354 } 1355 } 1356 1357 if ($editMode) { 1358 print CGI::submit({name=>"discard_chagnes", value=>"Discard Changes to Users"}); 1359 print CGI::submit({name=>"save_classlist", value=>"Save Changes to Users"}); 1360 } else { 1361 print CGI::submit({name=>"edit_visible", value=>"Edit Visible Users"}); 1362 print CGI::submit({name=>"edit_selected", value=>"Edit Selected Users"}); 1363 print CGI::submit({name=>"delete_selected", value=>"Delete Selected Users"}); 1364 } 1365 1366 print CGI::end_form(); 1367 1368 # Add a student form 1369 unless ($editMode) { 1370 print CGI::start_form({method=>"post", action=>$r->uri()}); 1371 print $self->hidden_authen_fields(); 1372 print "User ID:"; 1373 print CGI::input({type=>"text", name=>"newUserID", value=>"", size=>"20"}); 1374 print CGI::submit({name=>"addStudent", value=>"Add User"}); 1375 print CGI::end_form(); 1376 } 1377 1378 return "";
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |