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