Parent Directory
|
Revision Log
Resolves bug #942, in which a missing permission level field causes a
warning:
This is in fact due to UserList assuming that $record{permission} is
defined when it is allowed to be undefined (as per the format of
classlist files).
I added a case that sets is to $default_permission_level if it is not
defined.
1 ################################################################################ 2 # WeBWorK Online Homework Delivery System 3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ 4 # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm,v 1.75 2005/12/26 22:02:58 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 Switch from view mode to password mode: 40 - showing visible users 41 - showing selected users 42 Switch from password mode to view and save changes 43 Switch from password mode to view and abandon changes 44 Delete users: 45 - visible 46 - selected 47 Import users: 48 - replace: 49 - any users 50 - visible users 51 - selected users 52 - no users 53 - add: 54 - any users 55 - no users 56 Export users: 57 - export: 58 - all 59 - visible 60 - selected 61 - to: 62 - existing file on server (overwrite): [ list of files ] 63 - new file on server (create): [ filename ] 64 65 =cut 66 67 use strict; 68 use warnings; 69 use CGI qw(); 70 use WeBWorK::File::Classlist; 71 use WeBWorK::Utils qw(readFile readDirectory cryptPassword); 72 use WeBWorK::Authen qw(checkKey); 73 use Apache::Constants qw(:common REDIRECT DONE); #FIXME -- this should be called higher up in the object tree. 74 use constant HIDE_USERS_THRESHHOLD => 200; 75 use constant EDIT_FORMS => [qw(cancelEdit saveEdit)]; 76 use constant PASSWORD_FORMS => [qw(cancelPassword savePassword)]; 77 use constant VIEW_FORMS => [qw(filter sort edit password import export add delete)]; 78 79 # permissions needed to perform a given action 80 use constant FORM_PERMS => { 81 saveEdit => "modify_student_data", 82 edit => "modify_student_data", 83 savePassword => "change_password", 84 password => "change_password", 85 import => "modify_student_data", 86 export => "modify_classlist_files", 87 add => "modify_student_data", 88 delete => "modify_student_data", 89 }; 90 91 # permissions needed to view a given field 92 use constant FIELD_PERMS => { 93 act_as => "become_student", 94 sets => "assign_problem_sets", 95 }; 96 97 use constant STATE_PARAMS => [qw(user effectiveUser key visible_users no_visible_users prev_visible_users no_prev_visible_users editMode passwordMode primarySortField secondarySortField ternarySortField labelSortMethod)]; 98 99 use constant SORT_SUBS => { 100 user_id => \&byUserID, 101 first_name => \&byFirstName, 102 last_name => \&byLastName, 103 email_address => \&byEmailAddress, 104 student_id => \&byStudentID, 105 status => \&byStatus, 106 section => \&bySection, 107 recitation => \&byRecitation, 108 comment => \&byComment, 109 permission => \&byPermission, 110 }; 111 112 use constant FIELD_PROPERTIES => { 113 user_id => { 114 type => "text", 115 size => 8, 116 access => "readonly", 117 }, 118 first_name => { 119 type => "text", 120 size => 10, 121 access => "readwrite", 122 }, 123 last_name => { 124 type => "text", 125 size => 10, 126 access => "readwrite", 127 }, 128 email_address => { 129 type => "text", 130 size => 20, 131 access => "readwrite", 132 }, 133 student_id => { 134 type => "text", 135 size => 11, 136 access => "readwrite", 137 }, 138 status => { 139 #type => "enumerable", 140 type => "status", 141 size => 4, 142 access => "readwrite", 143 #items => { 144 # "C" => "Enrolled", 145 # "D" => "Drop", 146 # "A" => "Audit", 147 #}, 148 #synonyms => { 149 # qr/^[ce]/i => "C", 150 # qr/^[dw]/i => "D", 151 # qr/^a/i => "A", 152 # "*" => "C", 153 #} 154 }, 155 section => { 156 type => "text", 157 size => 4, 158 access => "readwrite", 159 }, 160 recitation => { 161 type => "text", 162 size => 4, 163 access => "readwrite", 164 }, 165 comment => { 166 type => "text", 167 size => 20, 168 access => "readwrite", 169 }, 170 permission => { 171 type => "number", 172 size => 2, 173 access => "readwrite", 174 } 175 }; 176 sub pre_header_initialize { 177 my $self = shift; 178 my $r = $self->r; 179 my $urlpath = $r->urlpath; 180 my $authz = $r->authz; 181 my $ce = $r->ce; 182 my $courseName = $urlpath->arg("courseID"); 183 my $user = $r->param('user'); 184 # Handle redirects, if any. 185 ############################## 186 # Redirect to the addUser page 187 ################################## 188 189 # Check permissions 190 return unless $authz->hasPermissions($user, "access_instructor_tools"); 191 192 defined($r->param('action')) && $r->param('action') eq 'add' && do { 193 # fix url and redirect 194 my $root = $ce->{webworkURLs}->{root}; 195 196 my $numberOfStudents = $r->param('number_of_students'); 197 warn "number of students not defined " unless defined $numberOfStudents; 198 199 my $uri=$self->systemLink( $urlpath->newFromModule('WeBWorK::ContentGenerator::Instructor::AddUsers',courseID=>$courseName), 200 params=>{ 201 number_of_students=>$numberOfStudents, 202 } 203 ); 204 #FIXME does the display mode need to be defined? 205 #FIXME url_authen_args also includes an effective user, so the new one must come first. 206 # even that might not work with every browser since there are two effective User assignments. 207 $r->header_out(Location => $uri); 208 $self->{noContent} = 1; # forces redirect 209 return; 210 }; 211 } 212 # FIXME -- this should be moved up to instructor or contentgenerator 213 sub header { 214 my $self = shift; 215 return REDIRECT if $self->{noContent}; 216 my $r = $self->r; 217 $r->content_type('text/html'); 218 $r->send_http_header(); 219 return OK; 220 } 221 222 #FIXME -- this should probably be moved up to instructor or contentgenerator as well 223 #sub nbsp { 224 # my $str = shift; 225 # ($str =~/\S/) ? $str : ' ' ; # returns non-breaking space for empty strings 226 # # tricky cases: $str =0; 227 # # $str is a complex number 228 #} 229 # moved to ContentGenerator.pm 230 231 sub initialize { 232 my ($self) = @_; 233 my $r = $self->r; 234 my $db = $r->db; 235 my $ce = $r->ce; 236 my $authz = $r->authz; 237 my $user = $r->param('user'); 238 239 # Check permissions 240 return unless $authz->hasPermissions($user, "access_instructor_tools"); 241 242 #if (defined($r->param('addStudent'))) { 243 # my $newUser = $db->newUser; 244 # my $newPermissionLevel = $db->newPermissionLevel; 245 # my $newPassword = $db->newPassword; 246 # $newUser->user_id($r->param('newUserID')); 247 # $newPermissionLevel->user_id($r->param('newUserID')); 248 # $newPassword->user_id($r->param('newUserID')); 249 # $newUser->status('C'); 250 # $newPermissionLevel->permission(0); 251 # $db->addUser($newUser); 252 # $db->addPermissionLevel($newPermissionLevel); 253 # $db->addPassword($newPassword); 254 #} 255 } 256 257 258 259 sub body { 260 my ($self) = @_; 261 my $r = $self->r; 262 my $urlpath = $r->urlpath; 263 my $db = $r->db; 264 my $ce = $r->ce; 265 my $authz = $r->authz; 266 my $courseName = $urlpath->arg("courseID"); 267 my $setID = $urlpath->arg("setID"); 268 my $user = $r->param('user'); 269 270 my $root = $ce->{webworkURLs}->{root}; 271 272 # templates for getting field names 273 my $userTemplate = $self->{userTemplate} = $db->newUser; 274 my $permissionLevelTemplate = $self->{permissionLevelTemplate} = $db->newPermissionLevel; 275 276 return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to access the instructor tools.")) 277 unless $authz->hasPermissions($user, "access_instructor_tools"); 278 279 # This table can be consulted when display-ready forms of field names are needed. 280 my %prettyFieldNames = map { $_ => $_ } 281 $userTemplate->FIELDS(), 282 $permissionLevelTemplate->FIELDS(); 283 284 @prettyFieldNames{qw( 285 user_id 286 first_name 287 last_name 288 email_address 289 student_id 290 status 291 section 292 recitation 293 comment 294 permission 295 )} = ( 296 "Login Name", 297 "First Name", 298 "Last Name", 299 "Email Address", 300 "Student ID", 301 "Status", 302 "Section", 303 "Recitation", 304 "Comment", 305 "Permission Level" 306 ); 307 308 $self->{prettyFieldNames} = \%prettyFieldNames; 309 ########## set initial values for state fields 310 311 my @allUserIDs = $db->listUsers; 312 $self->{totalSets} = $db->listGlobalSets; # save for use in "assigned sets" links 313 $self->{allUserIDs} = \@allUserIDs; 314 315 if (defined $r->param("visable_user_string")) { 316 my @visableUserIDs = split /:/, $r->param("visable_user_string"); 317 $self->{visibleUserIDs} = [ @visableUserIDs ]; 318 } elsif (defined $r->param("visible_users")) { 319 $self->{visibleUserIDs} = [ $r->param("visible_users") ]; 320 } elsif (defined $r->param("no_visible_users")) { 321 $self->{visibleUserIDs} = []; 322 } else { 323 if ((@allUserIDs > HIDE_USERS_THRESHHOLD) and (not defined $r->param("show_all_users") )) { 324 $self->{visibleUserIDs} = []; 325 } else { 326 $self->{visibleUserIDs} = [ @allUserIDs ]; 327 } 328 } 329 330 $self->{prevVisibleUserIDs} = $self->{visibleUserIDs}; 331 332 if (defined $r->param("selected_users")) { 333 $self->{selectedUserIDs} = [ $r->param("selected_users") ]; 334 } else { 335 $self->{selectedUserIDs} = []; 336 } 337 338 $self->{editMode} = $r->param("editMode") || 0; 339 340 return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to modify student data")) 341 if $self->{editMode} and not $authz->hasPermissions($user, "modify_student_data"); 342 343 344 $self->{passwordMode} = $r->param("passwordMode") || 0; 345 346 return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to modify student data")) 347 if $self->{passwordMode} and not $authz->hasPermissions($user, "modify_student_data"); 348 349 if (defined $r->param("labelSortMethod")) { 350 $self->{primarySortField} = $r->param("labelSortMethod"); 351 $self->{secondarySortField} = $r->param("primarySortField"); 352 $self->{ternarySortField} = $r->param("secondarySortField"); 353 } 354 else { 355 $self->{primarySortField} = $r->param("primarySortField") || "last_name"; 356 $self->{secondarySortField} = $r->param("secondarySortField") || "first_name"; 357 $self->{ternarySortField} = $r->param("ternarySortField") || "student_id"; 358 } 359 360 my @allUsers = $db->getUsers(@allUserIDs); 361 my (%sections, %recitations); 362 foreach my $User (@allUsers) { 363 push @{$sections{defined $User->section ? $User->section : ""}}, $User->user_id; 364 push @{$recitations{defined $User->recitation ? $User->recitation : ""}}, $User->user_id; 365 } 366 $self->{sections} = \%sections; 367 $self->{recitations} = \%recitations; 368 369 ########## call action handler 370 371 my $actionID = $r->param("action"); 372 if ($actionID) { 373 unless (grep { $_ eq $actionID } @{ VIEW_FORMS() }, @{ EDIT_FORMS() }, @{ PASSWORD_FORMS() } ) { 374 die "Action $actionID not found"; 375 } 376 # Check permissions 377 if (not FORM_PERMS()->{$actionID} or $authz->hasPermissions($user, FORM_PERMS()->{$actionID})) { 378 my $actionHandler = "${actionID}_handler"; 379 my %genericParams; 380 foreach my $param (qw(selected_users)) { 381 $genericParams{$param} = [ $r->param($param) ]; 382 } 383 my %actionParams = $self->getActionParams($actionID); 384 my %tableParams = $self->getTableParams(); 385 print CGI::p( 386 '<div style="color:green">', 387 "Result of last action performed: ", 388 CGI::i($self->$actionHandler(\%genericParams, \%actionParams, \%tableParams)), 389 '</div>', 390 CGI::hr() 391 ); 392 } else { 393 return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to perform this action.")); 394 } 395 } 396 397 ########## retrieve possibly changed values for member fields 398 399 #@allUserIDs = @{ $self->{allUserIDs} }; # do we need this one? 400 @allUserIDs = $db->listUsers; # recompute value in case some were added 401 my @visibleUserIDs = @{ $self->{visibleUserIDs} }; 402 my @prevVisibleUserIDs = @{ $self->{prevVisibleUserIDs} }; 403 my @selectedUserIDs = @{ $self->{selectedUserIDs} }; 404 my $editMode = $self->{editMode}; 405 my $passwordMode = $self->{passwordMode}; 406 my $primarySortField = $self->{primarySortField}; 407 my $secondarySortField = $self->{secondarySortField}; 408 my $ternarySortField = $self->{ternarySortField}; 409 410 #warn "visibleUserIDs=@visibleUserIDs\n"; 411 #warn "prevVisibleUserIDs=@prevVisibleUserIDs\n"; 412 #warn "selectedUserIDs=@selectedUserIDs\n"; 413 #warn "editMode=$editMode\n"; 414 #warn "passwordMode=$passwordMode\n"; 415 #warn "primarySortField=$primarySortField\n"; 416 #warn "secondarySortField=$secondarySortField\n"; 417 #warn "ternarySortField=$ternarySortField\n"; 418 419 ########## get required users 420 421 my @Users = grep { defined $_ } @visibleUserIDs ? $db->getUsers(@visibleUserIDs) : (); 422 423 my %sortSubs = %{ SORT_SUBS() }; 424 my $primarySortSub = $sortSubs{$primarySortField}; 425 my $secondarySortSub = $sortSubs{$secondarySortField}; 426 my $ternarySortSub = $sortSubs{$ternarySortField}; 427 428 # add permission level to user record hash so we can sort it if necessary 429 if ($primarySortField eq 'permission' or $secondarySortField eq 'permission' or $ternarySortField eq 'permission') { 430 foreach my $User (@Users) { 431 next unless $User; 432 my $permissionLevel = $db->getPermissionLevel($User->user_id); 433 $User->{permission} = $permissionLevel->permission; 434 } 435 } 436 437 438 # # don't forget to sort in opposite order of importance 439 # @Users = sort $secondarySortSub @Users; 440 # @Users = sort $primarySortSub @Users; 441 # #@Users = sort byLnFnUid @Users; 442 443 # Always have a definite sort order even if first three sorts don't determine things 444 @Users = sort { 445 &$primarySortSub 446 || 447 &$secondarySortSub 448 || 449 &$ternarySortSub 450 || 451 byLastName 452 || 453 byFirstName 454 || 455 byUserID 456 } 457 @Users; 458 459 my @PermissionLevels; 460 461 for (my $i = 0; $i < @Users; $i++) { 462 my $User = $Users[$i]; 463 my $PermissionLevel = $db->getPermissionLevel($User->user_id); # checked 464 465 unless ($PermissionLevel) { 466 # uh oh! no permission level record found! 467 warn "added missing permission level for user ", $User->user_id, "\n"; 468 469 # create a new permission level record 470 $PermissionLevel = $db->newPermissionLevel; 471 $PermissionLevel->user_id($User->user_id); 472 $PermissionLevel->permission(0); 473 474 # add it to the database 475 $db->addPermissionLevel($PermissionLevel); 476 } 477 478 $PermissionLevels[$i] = $PermissionLevel; 479 } 480 481 ########## print beginning of form 482 483 print CGI::start_form({method=>"post", action=>$self->systemLink($urlpath,authen=>0), name=>"userlist"}); 484 print $self->hidden_authen_fields(); 485 486 ########## print state data 487 488 print "\n<!-- state data here -->\n"; 489 490 if (@visibleUserIDs) { 491 print CGI::hidden(-name=>"visible_users", -value=>\@visibleUserIDs); 492 } else { 493 print CGI::hidden(-name=>"no_visible_users", -value=>"1"); 494 } 495 496 if (@prevVisibleUserIDs) { 497 print CGI::hidden(-name=>"prev_visible_users", -value=>\@prevVisibleUserIDs); 498 } else { 499 print CGI::hidden(-name=>"no_prev_visible_users", -value=>"1"); 500 } 501 502 print CGI::hidden(-name=>"editMode", -value=>$editMode); 503 504 print CGI::hidden(-name=>"passwordMode", -value=>$passwordMode); 505 506 print CGI::hidden(-name=>"primarySortField", -value=>$primarySortField); 507 print CGI::hidden(-name=>"secondarySortField", -value=>$secondarySortField); 508 print CGI::hidden(-name=>"ternarySortField", -value=>$ternarySortField); 509 510 print "\n<!-- state data here -->\n"; 511 512 ########## print action forms 513 514 print CGI::start_table({}); 515 print CGI::Tr({}, CGI::td({-colspan=>2}, "Select an action to perform:")); 516 517 my @formsToShow; 518 if ($editMode) { 519 @formsToShow = @{ EDIT_FORMS() }; 520 }elsif ($passwordMode) { 521 @formsToShow = @{ PASSWORD_FORMS() }; 522 } else { 523 @formsToShow = @{ VIEW_FORMS() }; 524 } 525 526 my $i = 0; 527 foreach my $actionID (@formsToShow) { 528 # Check permissions 529 next if FORM_PERMS()->{$actionID} and not $authz->hasPermissions($user, FORM_PERMS()->{$actionID}); 530 my $actionForm = "${actionID}_form"; 531 my $onChange = "document.userlist.action[$i].checked=true"; 532 my %actionParams = $self->getActionParams($actionID); 533 534 print CGI::Tr({-valign=>"top"}, 535 CGI::td({}, CGI::input({-type=>"radio", -name=>"action", -value=>$actionID})), 536 CGI::td({}, $self->$actionForm($onChange, %actionParams)) 537 ); 538 539 $i++; 540 } 541 542 print CGI::Tr({}, CGI::td({-colspan=>2, -align=>"center"}, 543 CGI::submit(-value=>"Take Action!")) 544 ); 545 print CGI::end_table(); 546 547 ########## print table 548 549 print CGI::p("Showing ", scalar @Users, " out of ", scalar @allUserIDs, " users."); 550 551 print CGI::p("If a password field is left blank, the student's current password will be maintained.") if $passwordMode; 552 if ($editMode) { 553 554 555 print CGI::p('<b>Click</b> on the login name to <b>edit individual problem set data</b>, (e.g. due dates) for these students.'); 556 } 557 $self->printTableHTML(\@Users, \@PermissionLevels, \%prettyFieldNames, 558 editMode => $editMode, 559 passwordMode => $passwordMode, 560 selectedUserIDs => \@selectedUserIDs, 561 primarySortField => $primarySortField, 562 secondarySortField => $secondarySortField, 563 visableUserIDs => \@visibleUserIDs, 564 ); 565 566 567 ########## print end of form 568 569 print CGI::end_form(); 570 571 return ""; 572 } 573 574 ################################################################################ 575 # extract particular params and put them in a hash (values are ARRAYREFs!) 576 ################################################################################ 577 578 sub getActionParams { 579 my ($self, $actionID) = @_; 580 my $r = $self->{r}; 581 582 my %actionParams; 583 foreach my $param ($r->param) { 584 next unless $param =~ m/^action\.$actionID\./; 585 $actionParams{$param} = [ $r->param($param) ]; 586 } 587 return %actionParams; 588 } 589 590 sub getTableParams { 591 my ($self) = @_; 592 my $r = $self->{r}; 593 594 my %tableParams; 595 foreach my $param ($r->param) { 596 next unless $param =~ m/^(?:user|permission)\./; 597 $tableParams{$param} = [ $r->param($param) ]; 598 } 599 return %tableParams; 600 } 601 602 ################################################################################ 603 # actions and action triggers 604 ################################################################################ 605 606 # filter, edit, cancelEdit, and saveEdit should stay with the display module and 607 # not be real "actions". that way, all actions are shown in view mode and no 608 # actions are shown in edit mode. 609 610 sub filter_form { 611 my ($self, $onChange, %actionParams) = @_; 612 #return CGI::table({}, CGI::Tr({-valign=>"top"}, 613 # CGI::td({}, 614 615 my %prettyFieldNames = %{ $self->{prettyFieldNames} }; 616 617 return join("", 618 "Show ", 619 CGI::popup_menu( 620 -name => "action.filter.scope", 621 -values => [qw(all none selected match_regex)], 622 -default => $actionParams{"action.filter.scope"}->[0] || "match_regex", 623 -labels => { 624 all => "all users", 625 none => "no users", 626 selected => "selected users", 627 # match_ids => "users with matching user IDs:", 628 match_regex => "users who match:", 629 # match_section => "users in selected section", 630 # match_recitation => "users in selected recitation", 631 }, 632 -onchange => $onChange, 633 ), 634 " ", 635 CGI::textfield( 636 -name => "action.filter.user_ids", 637 -value => $actionParams{"action.filter.user_ids"}->[0] || "",, 638 -width => "50", 639 -onchange => $onChange, 640 ), 641 # " (separate multiple IDs with commas)", 642 # CGI::br(), 643 # "sections: ", 644 # CGI::popup_menu( 645 # -name => "action.filter.section", 646 # -values => [ keys %{ $self->{sections} } ], 647 # -default => $actionParams{"action.filter.section"}->[0] || "", 648 # -labels => { $self->menuLabels($self->{sections}) }, 649 # -onchange => $onChange, 650 # ), 651 # " recitations: ", 652 # CGI::popup_menu( 653 # -name => "action.filter.recitation", 654 # -values => [ keys %{ $self->{recitations} } ], 655 # -default => $actionParams{"action.filter.recitation"}->[0] || "", 656 # -labels => { $self->menuLabels($self->{recitations}) }, 657 # -onchange => $onChange, 658 # ), 659 " in their ", 660 CGI::popup_menu( 661 -name => "action.filter.field", 662 -value => [ keys %{ FIELD_PROPERTIES() } ], 663 -default => $actionParams{"action.filter.field"}->[0] || "user_id", 664 -labels => \%prettyFieldNames, 665 -onchange => $onChange, 666 ), 667 ); 668 # ), 669 #)); 670 } 671 672 # this action handler modifies the "visibleUserIDs" field based on the contents 673 # of the "action.filter.scope" parameter and the "selected_users" 674 sub filter_handler { 675 my ($self, $genericParams, $actionParams, $tableParams) = @_; 676 677 my $r = $self->r; 678 my $db = $r->db; 679 680 my $result; 681 682 my $scope = $actionParams->{"action.filter.scope"}->[0]; 683 if ($scope eq "all") { 684 $result = "showing all users"; 685 $self->{visibleUserIDs} = $self->{allUserIDs}; 686 } elsif ($scope eq "none") { 687 $result = "showing no users"; 688 $self->{visibleUserIDs} = []; 689 } elsif ($scope eq "selected") { 690 $result = "showing selected users"; 691 $self->{visibleUserIDs} = $genericParams->{selected_users}; # an arrayref 692 } elsif ($scope eq "match_regex") { 693 $result = "showing matching users"; 694 my $regex = $actionParams->{"action.filter.user_ids"}->[0]; 695 my $field = $actionParams->{"action.filter.field"}->[0]; 696 my @userRecords = $db->getUsers(@{$self->{allUserIDs}}); 697 my @userIDs; 698 foreach my $record (@userRecords) { 699 next unless $record; 700 701 # add permission level to user record hash so we can match it if necessary 702 if ($field eq "permission") { 703 my $permissionLevel = $db->getPermissionLevel($record->user_id); 704 $record->{permission} = $permissionLevel->permission; 705 } 706 push @userIDs, $record->user_id if $record->{$field} =~ /^$regex/i; 707 } 708 $self->{visibleUserIDs} = \@userIDs; 709 } elsif ($scope eq "match_ids") { 710 my @userIDs = split /\s*,\s*/, $actionParams->{"action.filter.user_ids"}->[0]; 711 $self->{visibleUserIDs} = \@userIDs; 712 } elsif ($scope eq "match_section") { 713 my $section = $actionParams->{"action.filter.section"}->[0]; 714 $self->{visibleUserIDs} = $self->{sections}->{$section}; # an arrayref 715 } elsif ($scope eq "match_recitation") { 716 my $recitation = $actionParams->{"action.filter.recitation"}->[0]; 717 $self->{visibleUserIDs} = $self->{recitations}->{$recitation}; # an arrayref 718 } 719 720 return $result; 721 } 722 723 sub sort_form { 724 my ($self, $onChange, %actionParams) = @_; 725 return join ("", 726 "Sort by ", 727 CGI::popup_menu( 728 -name => "action.sort.primary", 729 -values => [qw(user_id first_name last_name email_address student_id status section recitation comment permission)], 730 -default => $actionParams{"action.sort.primary"}->[0] || "last_name", 731 -labels => { 732 user_id => "Login Name", 733 first_name => "First Name", 734 last_name => "Last Name", 735 email_address => "Email Address", 736 student_id => "Student ID", 737 status => "Enrollment Status", 738 section => "Section", 739 recitation => "Recitation", 740 comment => "Comment", 741 permission => "Permission Level" 742 }, 743 -onchange => $onChange, 744 ), 745 ", then by ", 746 CGI::popup_menu( 747 -name => "action.sort.secondary", 748 -values => [qw(user_id first_name last_name email_address student_id status section recitation comment permission)], 749 -default => $actionParams{"action.sort.secondary"}->[0] || "first_name", 750 -labels => { 751 user_id => "Login Name", 752 first_name => "First Name", 753 last_name => "Last Name", 754 email_address => "Email Address", 755 student_id => "Student ID", 756 status => "Enrollment Status", 757 section => "Section", 758 recitation => "Recitation", 759 comment => "Comment", 760 permission => "Permission Level" 761 }, 762 -onchange => $onChange, 763 ), 764 ", then by ", 765 CGI::popup_menu( 766 -name => "action.sort.ternary", 767 -values => [qw(user_id first_name last_name email_address student_id status section recitation comment permission)], 768 -default => $actionParams{"action.sort.ternary"}->[0] || "user_id", 769 -labels => { 770 user_id => "Login Name", 771 first_name => "First Name", 772 last_name => "Last Name", 773 email_address => "Email Address", 774 student_id => "Student ID", 775 status => "Enrollment Status", 776 section => "Section", 777 recitation => "Recitation", 778 comment => "Comment", 779 permission => "Permission Level" 780 }, 781 -onchange => $onChange, 782 ), 783 784 ".", 785 ); 786 } 787 788 sub sort_handler { 789 my ($self, $genericParams, $actionParams, $tableParams) = @_; 790 791 my $primary = $actionParams->{"action.sort.primary"}->[0]; 792 my $secondary = $actionParams->{"action.sort.secondary"}->[0]; 793 my $ternary = $actionParams->{"action.sort.ternary"}->[0]; 794 795 $self->{primarySortField} = $primary; 796 $self->{secondarySortField} = $secondary; 797 $self->{ternarySortField} = $ternary; 798 799 my %names = ( 800 user_id => "Login Name", 801 first_name => "First Name", 802 last_name => "Last Name", 803 email_address => "Email Address", 804 student_id => "Student ID", 805 status => "Enrollment Status", 806 section => "Section", 807 recitation => "Recitation", 808 comment => "Comment", 809 permission => "Permission Level" 810 ); 811 812 return "Users sorted by $names{$primary}, then by $names{$secondary}, then by $names{$ternary}."; 813 } 814 815 sub edit_form { 816 my ($self, $onChange, %actionParams) = @_; 817 818 return join("", 819 "Edit ", 820 CGI::popup_menu( 821 -name => "action.edit.scope", 822 -values => [qw(all visible selected)], 823 -default => $actionParams{"action.edit.scope"}->[0] || "selected", 824 -labels => { 825 all => "all users", 826 visible => "visible users", 827 selected => "selected users" 828 }, 829 -onchange => $onChange, 830 ), 831 ); 832 } 833 834 sub edit_handler { 835 my ($self, $genericParams, $actionParams, $tableParams) = @_; 836 837 my $result; 838 839 my $scope = $actionParams->{"action.edit.scope"}->[0]; 840 if ($scope eq "all") { 841 $result = "editing all users"; 842 $self->{visibleUserIDs} = $self->{allUserIDs}; 843 } elsif ($scope eq "visible") { 844 $result = "editing visible users"; 845 # leave visibleUserIDs alone 846 } elsif ($scope eq "selected") { 847 $result = "editing selected users"; 848 $self->{visibleUserIDs} = $genericParams->{selected_users}; # an arrayref 849 } 850 $self->{editMode} = 1; 851 852 return $result; 853 } 854 855 856 sub password_form { 857 my ($self, $onChange, %actionParams) = @_; 858 859 return join("", 860 "Give new password to ", 861 CGI::popup_menu( 862 -name => "action.password.scope", 863 -values => [qw(all visible selected)], 864 -default => $actionParams{"action.password.scope"}->[0] || "selected", 865 -labels => { 866 all => "all users", 867 visible => "visible users", 868 selected => "selected users" 869 }, 870 -onchange => $onChange, 871 ), 872 ); 873 } 874 875 sub password_handler { 876 my ($self, $genericParams, $actionParams, $tableParams) = @_; 877 878 my $result; 879 880 my $scope = $actionParams->{"action.password.scope"}->[0]; 881 if ($scope eq "all") { 882 $result = "giving new passwords to all users"; 883 $self->{visibleUserIDs} = $self->{allUserIDs}; 884 } elsif ($scope eq "visible") { 885 $result = "giving new passwords to visible users"; 886 # leave visibleUserIDs alone 887 } elsif ($scope eq "selected") { 888 $result = "giving new passwords to selected users"; 889 $self->{visibleUserIDs} = $genericParams->{selected_users}; # an arrayref 890 } 891 $self->{passwordMode} = 1; 892 893 return $result; 894 } 895 896 sub delete_form { 897 my ($self, $onChange, %actionParams) = @_; 898 899 return join("", 900 CGI::div({class=>"ResultsWithError"}, 901 "Delete ", 902 CGI::popup_menu( 903 -name => "action.delete.scope", 904 -values => [qw(none selected)], 905 -default => $actionParams{"action.delete.scope"}->[0] || "none", 906 -labels => { 907 none => "no users.", 908 #visible => "visible users.", 909 selected => "selected users." 910 }, 911 -onchange => $onChange, 912 ), 913 CGI::em(" Deletion destroys all user-related data and is not undoable!"), 914 ), 915 ); 916 } 917 918 sub delete_handler { 919 my ($self, $genericParams, $actionParams, $tableParams) = @_; 920 my $r = $self->r; 921 my $db = $r->db; 922 my $user = $r->param('user'); 923 my $scope = $actionParams->{"action.delete.scope"}->[0]; 924 925 my @userIDsToDelete = (); 926 #if ($scope eq "visible") { 927 # @userIDsToDelete = @{ $self->{visibleUserIDs} }; 928 #} elsif ($scope eq "selected") { 929 if ($scope eq "selected") { 930 @userIDsToDelete = @{ $self->{selectedUserIDs} }; 931 } 932 933 my %allUserIDs = map { $_ => 1 } @{ $self->{allUserIDs} }; 934 my %visibleUserIDs = map { $_ => 1 } @{ $self->{visibleUserIDs} }; 935 my %selectedUserIDs = map { $_ => 1 } @{ $self->{selectedUserIDs} }; 936 937 my $error = ""; 938 my $num = 0; 939 foreach my $userID (@userIDsToDelete) { 940 if ($user eq $userID) { # don't delete yourself!! 941 $error = "You cannot delete yourself!"; 942 next; 943 } 944 delete $allUserIDs{$userID}; 945 delete $visibleUserIDs{$userID}; 946 delete $selectedUserIDs{$userID}; 947 $db->deleteUser($userID); 948 $num++; 949 } 950 951 $self->{allUserIDs} = [ keys %allUserIDs ]; 952 $self->{visibleUserIDs} = [ keys %visibleUserIDs ]; 953 $self->{selectedUserIDs} = [ keys %selectedUserIDs ]; 954 955 return "deleted $num user" . ($num == 1 ? "" : "s. ") . $error; 956 } 957 sub add_form { 958 my ($self, $onChange, %actionParams) = @_; 959 960 return "Add ", CGI::input({name=>'number_of_students', value=>1,size => 3}), " student(s). "; 961 } 962 963 sub add_handler { 964 my ($self, $genericParams, $actionParams, $tableParams) = @_; 965 # This action is redirected to the addUser.pm module using ../instructor/add_user/... 966 return "Nothing done by add student handler"; 967 } 968 sub import_form { 969 my ($self, $onChange, %actionParams) = @_; 970 return join(" ", 971 "Import users from file", 972 CGI::popup_menu( 973 -name => "action.import.source", 974 -values => [ $self->getCSVList() ], 975 -default => $actionParams{"action.import.source"}->[0] || "", 976 -onchange => $onChange, 977 ), 978 "replacing", 979 CGI::popup_menu( 980 -name => "action.import.replace", 981 -values => [qw(any visible selected none)], 982 -default => $actionParams{"action.import.replace"}->[0] || "none", 983 -labels => { 984 any => "any", 985 visible => "visible", 986 selected => "selected", 987 none => "no", 988 }, 989 -onchange => $onChange, 990 ), 991 "existing users and adding", 992 CGI::popup_menu( 993 -name => "action.import.add", 994 -values => [qw(any none)], 995 -default => $actionParams{"action.import.add"}->[0] || "any", 996 -labels => { 997 any => "any", 998 none => "no", 999 }, 1000 -onchange => $onChange, 1001 ), 1002 "new users", 1003 ); 1004 } 1005 1006 sub import_handler { 1007 my ($self, $genericParams, $actionParams, $tableParams) = @_; 1008 1009 my $source = $actionParams->{"action.import.source"}->[0]; 1010 my $add = $actionParams->{"action.import.add"}->[0]; 1011 my $replace = $actionParams->{"action.import.replace"}->[0]; 1012 1013 my $fileName = $source; 1014 my $createNew = $add eq "any"; 1015 my $replaceExisting; 1016 my @replaceList; 1017 if ($replace eq "any") { 1018 $replaceExisting = "any"; 1019 } elsif ($replace eq "none") { 1020 $replaceExisting = "none"; 1021 } elsif ($replace eq "visible") { 1022 $replaceExisting = "listed"; 1023 @replaceList = @{ $self->{visibleUserIDs} }; 1024 } elsif ($replace eq "selected") { 1025 $replaceExisting = "listed"; 1026 @replaceList = @{ $self->{selectedUserIDs} }; 1027 } 1028 1029 my ($replaced, $added, $skipped) 1030 = $self->importUsersFromCSV($fileName, $createNew, $replaceExisting, @replaceList); 1031 1032 # make new users visible... do we really want to do this? probably. 1033 push @{ $self->{visibleUserIDs} }, @$added; 1034 1035 my $numReplaced = @$replaced; 1036 my $numAdded = @$added; 1037 my $numSkipped = @$skipped; 1038 1039 return $numReplaced . " user" . ($numReplaced == 1 ? "" : "s") . " replaced, " 1040 . $numAdded . " user" . ($numAdded == 1 ? "" : "s") . " added, " 1041 . $numSkipped . " user" . ($numSkipped == 1 ? "" : "s") . " skipped" 1042 . " (" . join (", ", @$skipped) . ") "; 1043 } 1044 1045 sub export_form { 1046 my ($self, $onChange, %actionParams) = @_; 1047 return join("", 1048 "Export ", 1049 CGI::popup_menu( 1050 -name => "action.export.scope", 1051 -values => [qw(all visible selected)], 1052 -default => $actionParams{"action.export.scope"}->[0] || "visible", 1053 -labels => { 1054 all => "all users", 1055 visible => "visible users", 1056 selected => "selected users" 1057 }, 1058 -onchange => $onChange, 1059 ), 1060 " to ", 1061 CGI::popup_menu( 1062 -name=>"action.export.target", 1063 -values => [ "new", $self->getCSVList() ], 1064 -labels => { new => "a new file named:" }, 1065 -default => $actionParams{"action.export.target"}->[0] || "", 1066 -onchange => $onChange, 1067 ), 1068 #CGI::br(), 1069 #"new file to create: ", 1070 CGI::textfield( 1071 -name => "action.export.new", 1072 -value => $actionParams{"action.export.new"}->[0] || "",, 1073 -width => "50", 1074 -onchange => $onChange, 1075 ), 1076 CGI::tt(".lst"), 1077 ); 1078 } 1079 1080 sub export_handler { 1081 my ($self, $genericParams, $actionParams, $tableParams) = @_; 1082 my $r = $self->r; 1083 my $ce = $r->ce; 1084 my $dir = $ce->{courseDirs}->{templates}; 1085 1086 my $scope = $actionParams->{"action.export.scope"}->[0]; 1087 my $target = $actionParams->{"action.export.target"}->[0]; 1088 my $new = $actionParams->{"action.export.new"}->[0]; 1089 1090 #get name of templates directory as it appears in file manager 1091 $dir =~ s|.*/||; 1092 1093 my $fileName; 1094 if ($target eq "new") { 1095 $fileName = $new; 1096 } else { 1097 $fileName = $target; 1098 } 1099 1100 $fileName .= ".lst" unless $fileName =~ m/\.lst$/; 1101 1102 my @userIDsToExport; 1103 if ($scope eq "all") { 1104 @userIDsToExport = @{ $self->{allUserIDs} }; 1105 } elsif ($scope eq "visible") { 1106 @userIDsToExport = @{ $self->{visibleUserIDs} }; 1107 } elsif ($scope eq "selected") { 1108 @userIDsToExport = @{ $self->{selectedUserIDs} }; 1109 } 1110 1111 $self->exportUsersToCSV($fileName, @userIDsToExport); 1112 1113 return scalar @userIDsToExport . " users exported to file $dir/$fileName"; 1114 } 1115 1116 sub cancelEdit_form { 1117 my ($self, $onChange, %actionParams) = @_; 1118 return "Abandon changes"; 1119 } 1120 1121 sub cancelEdit_handler { 1122 my ($self, $genericParams, $actionParams, $tableParams) = @_; 1123 my $r = $self->r; 1124 1125 #$self->{selectedUserIDs} = $self->{visibleUserIDs}; 1126 # only do the above if we arrived here via "edit selected users" 1127 if (defined $r->param("prev_visible_users")) { 1128 $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ]; 1129 } elsif (defined $r->param("no_prev_visible_users")) { 1130 $self->{visibleUserIDs} = []; 1131 } else { 1132 # leave it alone 1133 } 1134 $self->{editMode} = 0; 1135 1136 return "changes abandoned"; 1137 } 1138 1139 sub saveEdit_form { 1140 my ($self, $onChange, %actionParams) = @_; 1141 return "Save changes"; 1142 } 1143 1144 sub saveEdit_handler { 1145 my ($self, $genericParams, $actionParams, $tableParams) = @_; 1146 my $r = $self->r; 1147 my $db = $r->db; 1148 1149 my @visibleUserIDs = @{ $self->{visibleUserIDs} }; 1150 foreach my $userID (@visibleUserIDs) { 1151 my $User = $db->getUser($userID); # checked 1152 die "record for visible user $userID not found" unless $User; 1153 my $PermissionLevel = $db->getPermissionLevel($userID); # checked 1154 die "permissions for $userID not defined" unless defined $PermissionLevel; 1155 foreach my $field ($User->NONKEYFIELDS()) { 1156 my $param = "user.${userID}.${field}"; 1157 if (defined $tableParams->{$param}->[0]) { 1158 $User->$field($tableParams->{$param}->[0]); 1159 } 1160 } 1161 1162 foreach my $field ($PermissionLevel->NONKEYFIELDS()) { 1163 my $param = "permission.${userID}.${field}"; 1164 if (defined $tableParams->{$param}->[0]) { 1165 $PermissionLevel->$field($tableParams->{$param}->[0]); 1166 } 1167 } 1168 1169 $db->putUser($User); 1170 $db->putPermissionLevel($PermissionLevel); 1171 } 1172 1173 if (defined $r->param("prev_visible_users")) { 1174 $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ]; 1175 } elsif (defined $r->param("no_prev_visible_users")) { 1176 $self->{visibleUserIDs} = []; 1177 } else { 1178 # leave it alone 1179 } 1180 1181 $self->{editMode} = 0; 1182 1183 return "changes saved"; 1184 } 1185 1186 sub cancelPassword_form { 1187 my ($self, $onChange, %actionParams) = @_; 1188 return "Abandon changes"; 1189 } 1190 1191 sub cancelPassword_handler { 1192 my ($self, $genericParams, $actionParams, $tableParams) = @_; 1193 my $r = $self->r; 1194 1195 #$self->{selectedUserIDs} = $self->{visibleUserIDs}; 1196 # only do the above if we arrived here via "edit selected users" 1197 if (defined $r->param("prev_visible_users")) { 1198 $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ]; 1199 } elsif (defined $r->param("no_prev_visible_users")) { 1200 $self->{visibleUserIDs} = []; 1201 } else { 1202 # leave it alone 1203 } 1204 $self->{passwordMode} = 0; 1205 1206 return "changes abandoned"; 1207 } 1208 1209 sub savePassword_form { 1210 my ($self, $onChange, %actionParams) = @_; 1211 return "Save changes"; 1212 } 1213 1214 sub savePassword_handler { 1215 my ($self, $genericParams, $actionParams, $tableParams) = @_; 1216 my $r = $self->r; 1217 my $db = $r->db; 1218 1219 my @visibleUserIDs = @{ $self->{visibleUserIDs} }; 1220 foreach my $userID (@visibleUserIDs) { 1221 my $User = $db->getUser($userID); # checked 1222 die "record for visible user $userID not found" unless $User; 1223 my $param = "user.${userID}.new_password"; 1224 if ((defined $tableParams->{$param}->[0]) and ($tableParams->{$param}->[0])) { 1225 my $newP = $tableParams->{$param}->[0]; 1226 my $Password = eval {$db->getPassword($User->user_id)}; # checked 1227 my $cryptPassword = cryptPassword($newP); 1228 $Password->password(cryptPassword($newP)); 1229 eval { $db->putPassword($Password) }; 1230 } 1231 } 1232 1233 if (defined $r->param("prev_visible_users")) { 1234 $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ]; 1235 } elsif (defined $r->param("no_prev_visible_users")) { 1236 $self->{visibleUserIDs} = []; 1237 } else { 1238 # leave it alone 1239 } 1240 1241 $self->{passwordMode} = 0; 1242 1243 return "new passwords saved"; 1244 } 1245 1246 1247 ################################################################################ 1248 # sorts 1249 ################################################################################ 1250 1251 sub byUserID { lc $a->user_id cmp lc $b->user_id } 1252 sub byFirstName { (defined $a->first_name && defined $b->first_name) ? lc $a->first_name cmp lc $b->first_name : 0; } 1253 sub byLastName { (defined $a->last_name && defined $b->last_name ) ? lc $a->last_name cmp lc $b->last_name : 0; } 1254 sub byEmailAddress { lc $a->email_address cmp lc $b->email_address } 1255 sub byStudentID { lc $a->student_id cmp lc $b->student_id } 1256 sub byStatus { lc $a->status cmp lc $b->status } 1257 sub bySection { lc $a->section cmp lc $b->section } 1258 sub byRecitation { lc $a->recitation cmp lc $b->recitation } 1259 sub byComment { lc $a->comment cmp lc $b->comment } 1260 sub byPermission { $a->{permission} <=> $b->{permission} } ## permission level is added to user record hash so we can sort it if necessary 1261 1262 # sub byLnFnUid { &byLastName || &byFirstName || &byUserID } 1263 1264 ################################################################################ 1265 # utilities 1266 ################################################################################ 1267 1268 # generate labels for section/recitation popup menus 1269 sub menuLabels { 1270 my ($self, $hashRef) = @_; 1271 my %hash = %$hashRef; 1272 1273 my %result; 1274 foreach my $key (keys %hash) { 1275 my $count = @{ $hash{$key} }; 1276 my $displayKey = $key || "<none>"; 1277 $result{$key} = "$displayKey ($count users)"; 1278 } 1279 return %result; 1280 } 1281 1282 sub importUsersFromCSV { 1283 my ($self, $fileName, $createNew, $replaceExisting, @replaceList) = @_; 1284 my $r = $self->r; 1285 my $ce = $r->ce; 1286 my $db = $r->db; 1287 my $dir = $ce->{courseDirs}->{templates}; 1288 my $user = $r->param('user'); 1289 1290 die "illegal character in input: '/'" if $fileName =~ m|/|; 1291 die "won't be able to read from file $dir/$fileName: does it exist? is it readable?" 1292 unless -r "$dir/$fileName"; 1293 1294 my %allUserIDs = map { $_ => 1 } @{ $self->{allUserIDs} }; 1295 my %replaceOK; 1296 if ($replaceExisting eq "none") { 1297 %replaceOK = (); 1298 } elsif ($replaceExisting eq "listed") { 1299 %replaceOK = map { $_ => 1 } @replaceList; 1300 } elsif ($replaceExisting eq "any") { 1301 %replaceOK = %allUserIDs; 1302 } 1303 1304 my $default_permission_level = $ce->{default_permission_level}; 1305 1306 my (@replaced, @added, @skipped); 1307 1308 # get list of hashrefs representing lines in classlist file 1309 my @classlist = parse_classlist("$dir/$fileName"); 1310 1311 foreach my $record (@classlist) { 1312 my %record = %$record; 1313 my $user_id = $record{user_id}; 1314 1315 if ($user_id eq $user) { # don't replace yourself!! 1316 push @skipped, $user_id; 1317 next; 1318 } 1319 1320 if (exists $allUserIDs{$user_id} and not exists $replaceOK{$user_id}) { 1321 push @skipped, $user_id; 1322 next; 1323 } 1324 1325 if (not exists $allUserIDs{$user_id} and not $createNew) { 1326 push @skipped, $user_id; 1327 next; 1328 } 1329 1330 # make sure permission level is defined 1331 if (not defined $record{permission}) { 1332 $record{permission} = $default_permission_level; 1333 } 1334 1335 # make sure permission level is numeric 1336 unless ($record{permission} =~ m/^[+\-]?\d*$/) { 1337 $self->addbadmessage("permission level '$record{permission}' for user '$user_id' is not an integer. using default permission level '$default_permission_level'.\n"); 1338 $record{permission} = $default_permission_level; 1339 } 1340 1341 my $User = $db->newUser(%record); 1342 my $PermissionLevel = $db->newPermissionLevel(user_id => $user_id, permission => 0); 1343 my $Password = $db->newPassword(user_id => $user_id, password => cryptPassword($record{student_id})); 1344 1345 # use password and permission from record if there 1346 if (exists $record{permission}) { 1347 $PermissionLevel->permission($record{permission}); 1348 } 1349 1350 if (exists $record{password}) { 1351 $Password->password($record{password}); 1352 } 1353 1354 if (exists $allUserIDs{$user_id}) { 1355 $db->putUser($User); 1356 $db->putPermissionLevel($PermissionLevel); 1357 $db->putPassword($Password); 1358 push @replaced, $user_id; 1359 } else { 1360 $db->addUser($User); 1361 $db->addPermissionLevel($PermissionLevel); 1362 $db->addPassword($Password); 1363 push @added, $user_id; 1364 } 1365 } 1366 1367 return \@replaced, \@added, \@skipped; 1368 } 1369 1370 sub exportUsersToCSV { 1371 my ($self, $fileName, @userIDsToExport) = @_; 1372 my $r = $self->r; 1373 my $ce = $r->ce; 1374 my $db = $r->db; 1375 my $dir = $ce->{courseDirs}->{templates}; 1376 1377 die "illegal character in input: '/'" if $fileName =~ m|/|; 1378 1379 my @records; 1380 1381 my @Users = $db->getUsers(@userIDsToExport); 1382 my @Passwords = $db->getPasswords(@userIDsToExport); 1383 my @PermissionLevels = $db->getPermissionLevels(@userIDsToExport); 1384 foreach my $i (0 .. $#userIDsToExport) { 1385 my $User = $Users[$i]; 1386 my $Password = $Passwords[$i]; 1387 my $PermissionLevel = $PermissionLevels[$i]; 1388 next unless defined $User; 1389 my %record = ( 1390 defined $PermissionLevel ? $PermissionLevel->toHash : (), 1391 defined $Password ? $Password->toHash : (), 1392 $User->toHash, 1393 ); 1394 push @records, \%record; 1395 } 1396 1397 write_classlist("$dir/$fileName", @records); 1398 } 1399 1400 ################################################################################ 1401 # "display" methods 1402 ################################################################################ 1403 1404 sub fieldEditHTML { 1405 my ($self, $fieldName, $value, $properties) = @_; 1406 my $ce = $self->r->ce; 1407 my $size = $properties->{size}; 1408 my $type = $properties->{type}; 1409 my $access = $properties->{access}; 1410 my $items = $properties->{items}; 1411 my $synonyms = $properties->{synonyms}; 1412 1413 if ($type eq "email") { 1414 if ($value eq ' ') { 1415 return $value;} 1416 else { 1417 return CGI::a({-href=>"mailto:$value"},$value); 1418 } 1419 } 1420 1421 if ($access eq "readonly") { 1422 # hack for status 1423 if ($type eq "status") { 1424 my $status_name = $ce->status_abbrev_to_name($value); 1425 if (defined $status_name) { 1426 $value = "$status_name ($value)"; 1427 } 1428 } 1429 return $value; 1430 } 1431 1432 if ($type eq "number" or $type eq "text") { 1433 return CGI::input({type=>"text", name=>$fieldName, value=>$value, size=>$size}); 1434 } 1435 1436 if ($type eq "enumerable") { 1437 my $matched = undef; # Whether a synonym match has occurred 1438 1439 # Process synonyms for enumerable objects 1440 foreach my $synonym (keys %$synonyms) { 1441 if ($synonym ne "*" and $value =~ m/$synonym/) { 1442 $value = $synonyms->{$synonym}; 1443 $matched = 1; 1444 } 1445 } 1446 1447 if (!$matched and exists $synonyms->{"*"}) { 1448 $value = $synonyms->{"*"}; 1449 } 1450 1451 return CGI::popup_menu({ 1452 name => $fieldName, 1453 values => [keys %$items], 1454 default => $value, 1455 labels => $items, 1456 }); 1457 } 1458 1459 if ($type eq "status") { 1460 # we used to surreptitously map synonyms to a canonical value... 1461 # so should we continue to do that? 1462 my $status_name = $ce->status_abbrev_to_name($value); 1463 if (defined $status_name) { 1464 $value = ($ce->status_name_to_abbrevs($status_name))[0]; 1465 } 1466 1467 my (@values, %labels); 1468 while (my ($k, $v) = each %{$ce->{statuses}}) { 1469 my @abbrevs = @{$v->{abbrevs}}; 1470 push @values, $abbrevs[0]; 1471 foreach my $abbrev (@abbrevs) { 1472 $labels{$abbrev} = $k; 1473 } 1474 } 1475 1476 return CGI::popup_menu({ 1477 name => $fieldName, 1478 values => \@values, 1479 default => $value, 1480 labels => \%labels, 1481 }); 1482 } 1483 } 1484 1485 sub recordEditHTML { 1486 my ($self, $User, $PermissionLevel, %options) = @_; 1487 my $r = $self->r; 1488 my $urlpath = $r->urlpath; 1489 my $db = $r->db; 1490 my $ce = $r->ce; 1491 my $authz = $r->authz; 1492 my $user = $r->param('user'); 1493 my $root = $ce->{webworkURLs}->{root}; 1494 my $courseName = $urlpath->arg("courseID"); 1495 1496 my $editMode = $options{editMode}; 1497 my $passwordMode = $options{passwordMode}; 1498 my $userSelected = $options{userSelected}; 1499 1500 my $statusClass = $ce->status_abbrev_to_name($User->status); 1501 1502 my $sets = $db->countUserSets($User->user_id); 1503 my $totalSets = $self->{totalSets}; 1504 1505 my $changeEUserURL = $self->systemLink($urlpath->new(type=>'set_list',args=>{courseID=>$courseName}), 1506 params => {effectiveUser => $User->user_id} 1507 ); 1508 1509 my $setsAssignedToUserURL = $self->systemLink($urlpath->new(type=>'instructor_user_detail', 1510 args=>{courseID => $courseName, 1511 userID => $User->user_id 1512 }), 1513 params => {effectiveUser => $User->user_id} 1514 ); 1515 1516 my $userListURL = $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName} )) . "&editMode=1&visible_users=" . $User->user_id; 1517 1518 my $imageURL = $ce->{webworkURLs}->{htdocs}."/images/edit.gif"; 1519 my $imageLink = CGI::a({href => $userListURL}, CGI::img({src=>$imageURL, border=>0})); 1520 1521 my @tableCells; 1522 1523 # Select 1524 if ($editMode or $passwordMode) { 1525 # column not there 1526 } else { 1527 # selection checkbox 1528 push @tableCells, CGI::checkbox( 1529 -name => "selected_users", 1530 -value => $User->user_id, 1531 -checked => $userSelected, 1532 -label => "", 1533 ); 1534 } 1535 1536 # Act As 1537 if ($editMode or $passwordMode) { 1538 # column not there 1539 } else { 1540 # selection checkbox 1541 if ( FIELD_PERMS()->{act_as} and not $authz->hasPermissions($user, FIELD_PERMS()->{act_as}) ){ 1542 push @tableCells, $User->user_id . $imageLink; 1543 } else { 1544 push @tableCells, CGI::a({href=>$changeEUserURL}, $User->user_id) . $imageLink; 1545 } 1546 } 1547 1548 # Login Status 1549 if ($editMode or $passwordMode) { 1550 # column not there 1551 } else { 1552 # check to see if a user is currently logged in 1553 my $Key = $db->getKey($User->user_id); 1554 push @tableCells, ($Key and WeBWorK::Authen::checkKey($self, $User->user_id, $Key->key)) ? CGI::b("active") : CGI::em("inactive"); 1555 } 1556 1557 # change password (only in password mode) 1558 if ($passwordMode) { 1559 if ($User->user_id eq $user) { 1560 push @tableCells, '' # don't allow a professor to change their own password from this form 1561 } 1562 else { 1563 my $fieldName = 'user.' . $User->user_id . '.' . 'new_password'; 1564 push @tableCells, CGI::input({type=>"text", name=>$fieldName, size=>14});; 1565 } 1566 } 1567 # User ID (edit mode) or Assigned Sets (otherwise) 1568 if ( $passwordMode) { 1569 # straight user ID 1570 push @tableCells, CGI::div({class=>$statusClass}, $User->user_id); 1571 } elsif ($editMode) { 1572 # straight user ID 1573 my $userDetailPage = $urlpath->new(type =>'instructor_user_detail', 1574 args =>{ 1575 courseID => $courseName, 1576 userID => $User->user_id, #FIXME eventually this should be a list?? 1577 } 1578 ); 1579 my $userDetailUrl = $self->systemLink($userDetailPage,params =>{}); 1580 push @tableCells, CGI::a({href=>$userDetailUrl}, $User->user_id); 1581 1582 } else { 1583 # "edit sets assigned to user" link 1584 #push @tableCells, CGI::a({href=>$setsAssignedToUserURL}, "Edit sets"); 1585 if ( FIELD_PERMS()->{sets} and not $authz->hasPermissions($user, FIELD_PERMS()->{sets}) ) { 1586 push @tableCells, "$sets/$totalSets"; 1587 } else { 1588 push @tableCells, CGI::a({href=>$setsAssignedToUserURL}, "$sets/$totalSets"); 1589 } 1590 } 1591 1592 # User Fields 1593 foreach my $field ($User->NONKEYFIELDS) { 1594 my $fieldName = 'user.' . $User->user_id . '.' . $field, 1595 my $fieldValue = $User->$field; 1596 my %properties = %{ FIELD_PROPERTIES()->{$field} }; 1597 $properties{access} = 'readonly' unless $editMode; 1598 $properties{type} = 'email' if ($field eq 'email_address' and !$editMode and !$passwordMode); 1599 $fieldValue = $self->nbsp($fieldValue) unless $editMode; 1600 push @tableCells, CGI::div({class=>$statusClass}, $self->fieldEditHTML($fieldName, $fieldValue, \%properties)); 1601 } 1602 1603 # PermissionLevel Fields 1604 foreach my $field ($PermissionLevel->NONKEYFIELDS) { 1605 my $fieldName = 'permission.' . $PermissionLevel->user_id . '.' . $field, 1606 my $fieldValue = $PermissionLevel->$field; 1607 my %properties = %{ FIELD_PROPERTIES()->{$field} }; 1608 $properties{access} = 'readonly' unless $editMode; 1609 $fieldValue = $self->nbsp($fieldValue) unless $editMode; 1610 push @tableCells, CGI::div({class=>$statusClass}, $self->fieldEditHTML($fieldName, $fieldValue, \%properties)); 1611 } 1612 1613 return CGI::Tr({}, CGI::td({nowrap=>1}, \@tableCells)); 1614 } 1615 1616 sub printTableHTML { 1617 my ($self, $UsersRef, $PermissionLevelsRef, $fieldNamesRef, %options) = @_; 1618 my $r = $self->r; 1619 my $urlpath = $r->urlpath; 1620 my $courseName = $urlpath->arg("courseID"); 1621 my $userTemplate = $self->{userTemplate}; 1622 my $permissionLevelTemplate = $self->{permissionLevelTemplate}; 1623 my @Users = @$UsersRef; 1624 my @PermissionLevels = @$PermissionLevelsRef; 1625 my %fieldNames = %$fieldNamesRef; 1626 1627 my $editMode = $options{editMode}; 1628 my $passwordMode = $options{passwordMode}; 1629 my %selectedUserIDs = map { $_ => 1 } @{ $options{selectedUserIDs} }; 1630 # my $currentSort = $options{currentSort}; 1631 my $primarySortField = $options{primarySortField}; 1632 my $secondarySortField = $options{secondarySortField}; 1633 my @visableUserIDs = @{ $options{visableUserIDs} }; 1634 1635 # names of headings: 1636 my @realFieldNames = ( 1637 $userTemplate->KEYFIELDS, 1638 $userTemplate->NONKEYFIELDS, 1639 $permissionLevelTemplate->NONKEYFIELDS, 1640 ); 1641 1642 # my %sortSubs = %{ SORT_SUBS() }; 1643 #my @stateParams = @{ STATE_PARAMS() }; 1644 #my $hrefPrefix = $r->uri . "?" . $self->url_args(@stateParams); # $self->url_authen_args 1645 my @tableHeadings; 1646 foreach my $field (@realFieldNames) { 1647 my $result = $fieldNames{$field}; 1648 push @tableHeadings, $result; 1649 }; 1650 1651 # prepend selection checkbox? only if we're NOT editing! 1652 unless($editMode or $passwordMode) { 1653 1654 #warn "line 1582 visibleUserIDs=@visableUserIDs \n"; 1655 my %current_state =(); 1656 if (@visableUserIDs) { 1657 # This is a hack to get around: Maximum URL Length Is 2,083 Characters in Internet Explorer. 1658 # Without passing visable users the URL is about 250 characters. If the total URL is under the limit 1659 # we will pass visable users. If it is over, we will not pass any and all users will be displayed. 1660 # Maybe we should replace the GET method by POST (but this doesn't look good) --- AKP 1661 1662 my $visableUserIDsString = join ':', @visableUserIDs; 1663 if (length($visableUserIDsString) < 1830) { 1664 %current_state = ( 1665 primarySortField => "$primarySortField", 1666 secondarySortField => "$secondarySortField", 1667 visable_user_string => "$visableUserIDsString" 1668 ); 1669 } else { 1670 %current_state = ( 1671 primarySortField => "$primarySortField", 1672 secondarySortField => "$secondarySortField", 1673 show_all_users => "1" 1674 ); 1675 } 1676 } else { 1677 %current_state = ( 1678 primarySortField => "$primarySortField", 1679 secondarySortField => "$secondarySortField", 1680 no_visible_users => "1" 1681 ); 1682 } 1683 @tableHeadings = ( 1684 "Select", 1685 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'user_id', %current_state})}, 'Login Name'), 1686 "Login Status", 1687 "Assigned Sets", 1688 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'first_name', %current_state})}, 'First Name'), 1689 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'last_name', %current_state})}, 'Last Name'), 1690 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'email_address', %current_state})}, 'Email Address'), 1691 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'student_id', %current_state})}, 'Student ID'), 1692 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'status', %current_state})}, 'Status'), 1693 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'section', %current_state})}, 'Section'), 1694 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'recitation', %current_state})}, 'Recitation'), 1695 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'comment', %current_state})}, 'Comment'), 1696 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'permission', %current_state})}, 'Permission Level'), 1697 ) 1698 } 1699 if($passwordMode) { 1700 unshift @tableHeadings, "New Password"; 1701 } 1702 1703 # print the table 1704 if ($editMode or $passwordMode) { 1705 print CGI::start_table({}); 1706 } else { 1707 print CGI::start_table({-border=>1, -nowrap=>1}); 1708 } 1709 1710 print CGI::Tr({}, CGI::th({}, \@tableHeadings)); 1711 1712 1713 for (my $i = 0; $i < @Users; $i++) { 1714 my $User = $Users[$i]; 1715 my $PermissionLevel = $PermissionLevels[$i]; 1716 1717 print $self->recordEditHTML($User, $PermissionLevel, 1718 editMode => $editMode, 1719 passwordMode => $passwordMode, 1720 userSelected => exists $selectedUserIDs{$User->user_id} 1721 ); 1722 } 1723 1724 print CGI::end_table(); 1725 ######################################### 1726 # if there are no users shown print message 1727 # 1728 ########################################## 1729 1730 print CGI::p( 1731 CGI::i("No students shown. Choose one of the options above to 1732 list the students in the course.") 1733 ) unless @Users; 1734 } 1735 1736 1; 1737
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |