Parent Directory
|
Revision Log
added text classes for students with status Audit, Drop, Enrolled
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 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 $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 179 sub 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 196 197 sub initialize { 198 my ($self) = @_; 199 my $r = $self->r; 200 my $db = $r->db; 201 my $ce = $r->ce; 202 my $authz = $r->authz; 203 my $user = $r->param('user'); 204 205 unless ($authz->hasPermissions($user, "modify_student_data")) { 206 $self->addmessage(CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to modify student data"))); 207 return; 208 } 209 210 #if (defined($r->param('addStudent'))) { 211 # my $newUser = $db->newUser; 212 # my $newPermissionLevel = $db->newPermissionLevel; 213 # my $newPassword = $db->newPassword; 214 # $newUser->user_id($r->param('newUserID')); 215 # $newPermissionLevel->user_id($r->param('newUserID')); 216 # $newPassword->user_id($r->param('newUserID')); 217 # $newUser->status('C'); 218 # $newPermissionLevel->permission(0); 219 # $db->addUser($newUser); 220 # $db->addPermissionLevel($newPermissionLevel); 221 # $db->addPassword($newPassword); 222 #} 223 } 224 225 226 227 sub body { 228 my ($self) = @_; 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'); 237 238 my $root = $ce->{webworkURLs}->{root}; 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{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"; 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=>$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 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 checked 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 $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 } 668 sub 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 674 sub 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 } 679 sub 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 717 sub 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 755 sub 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 790 sub 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 820 sub cancelEdit_form { 821 my ($self, $onChange, %actionParams) = @_; 822 return "Abandon changes"; 823 } 824 825 sub 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 843 sub saveEdit_form { 844 my ($self, $onChange, %actionParams) = @_; 845 return "Save changes"; 846 } 847 848 sub 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]); 863 } 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 894 sub byUserID { $a->user_id cmp $b->user_id } 895 sub byFirstName { $a->first_name cmp $b->first_name } 896 sub byLastName { $a->last_name cmp $b->last_name } 897 sub byEmailAddress { $a->email_address cmp $b->email_address } 898 sub byStudentID { $a->student_id cmp $b->student_id } 899 sub byStatus { $a->status cmp $b->status } 900 sub bySection { $a->section cmp $b->section } 901 sub byRecitation { $a->recitation cmp $b->recitation } 902 sub byComment { $a->comment cmp $b->comment } 903 904 sub byLnFnUid { &byLastName || &byFirstName || &byUserID } 905 906 ################################################################################ 907 # utilities 908 ################################################################################ 909 910 # generate labels for section/recitation popup menus 911 sub 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 924 sub 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 1001 sub 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 1038 sub 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 1078 sub 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} 1093 ); 1094 1095 my $setsAssignedToUserURL = $self->systemLink($urlpath->new(type=>'instructor_sets_assigned_to_user', 1096 args=>{courseID => $courseName, 1097 userID => $User->user_id 1098 }), 1099 params => {effectiveUser => $User->user_id} 1100 ); 1101 1102 my @tableCells; 1103 1104 # Select 1105 if ($editMode) { 1106 # column not there 1107 } else { 1108 # selection checkbox 1109 push @tableCells, CGI::checkbox( 1110 -name => "selected_users", 1111 -value => $User->user_id, 1112 -checked => $userSelected, 1113 -label => "", 1114 ); 1115 } 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 1157 sub 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 1209 print CGI::end_table(); 1210 ######################################### 1211 # if there are no users shown print message 1212 # 1213 ########################################## 1214 1215 print CGI::p( 1216 CGI::i("No students shown. Choose one of the options above to 1217 list the students in the course.") 1218 ) unless @Users; 1219 } 1220 1221 1; 1222
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |