Parent Directory
|
Revision Log
backport (gage): Fixed problem where blank status in classlist.lst was not interpreted as enrolled. Squashes bug 1058
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 sub importUsersFromCSV { 1263 my ($self, $fileName, $createNew, $replaceExisting, @replaceList) = @_; 1264 my $r = $self->r; 1265 my $ce = $r->ce; 1266 my $db = $r->db; 1267 my $dir = $ce->{courseDirs}->{templates}; 1268 my $user = $r->param('user'); 1269 1270 die "illegal character in input: '/'" if $fileName =~ m|/|; 1271 die "won't be able to read from file $dir/$fileName: does it exist? is it readable?" 1272 unless -r "$dir/$fileName"; 1273 1274 my %allUserIDs = map { $_ => 1 } @{ $self->{allUserIDs} }; 1275 my %replaceOK; 1276 if ($replaceExisting eq "none") { 1277 %replaceOK = (); 1278 } elsif ($replaceExisting eq "listed") { 1279 %replaceOK = map { $_ => 1 } @replaceList; 1280 } elsif ($replaceExisting eq "any") { 1281 %replaceOK = %allUserIDs; 1282 } 1283 1284 my $default_permission_level = $ce->{default_permission_level}; 1285 1286 my (@replaced, @added, @skipped); 1287 1288 # get list of hashrefs representing lines in classlist file 1289 my @classlist = parse_classlist("$dir/$fileName"); 1290 1291 # Default status is enrolled -- fetch abbreviation for enrolled 1292 my $default_status_abbrev = $ce->{statuses}->{Enrolled}->{abbrevs}->[0]; 1293 1294 foreach my $record (@classlist) { 1295 my %record = %$record; 1296 my $user_id = $record{user_id}; 1297 1298 $record{status} = $default_status_abbrev unless defined($record{status}) and $record{status}; 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 my $User = $db->newUser(%record); 1316 my $PermissionLevel = $db->newPermissionLevel(user_id => $user_id, permission => 0); 1317 my $Password = $db->newPassword(user_id => $user_id, password => cryptPassword($record{student_id})); 1318 1319 # use password and permission from record if there 1320 if (exists $record{permission}) { 1321 # make sure permission level is numeric 1322 unless (defined($record{permission}) and $record{permission} =~ m/^[+\-]?\d*$/) { 1323 $self->addbadmessage("permission levelĘ for user '$user_id' is not defined or is not an integer. Set the permission level to the default permission level '$default_permission_level'.\n"); 1324 $record{permission} = $default_permission_level; 1325 } 1326 $PermissionLevel->permission($record{permission}); 1327 } 1328 1329 if (exists $record{password}) { 1330 $Password->password($record{password}); 1331 } 1332 1333 if (exists $allUserIDs{$user_id}) { 1334 $db->putUser($User); 1335 $db->putPermissionLevel($PermissionLevel); 1336 $db->putPassword($Password); 1337 push @replaced, $user_id; 1338 } else { 1339 $db->addUser($User); 1340 $db->addPermissionLevel($PermissionLevel); 1341 $db->addPassword($Password); 1342 push @added, $user_id; 1343 } 1344 } 1345 1346 return \@replaced, \@added, \@skipped; 1347 } 1348 1349 sub exportUsersToCSV { 1350 my ($self, $fileName, @userIDsToExport) = @_; 1351 my $r = $self->r; 1352 my $ce = $r->ce; 1353 my $db = $r->db; 1354 my $dir = $ce->{courseDirs}->{templates}; 1355 1356 die "illegal character in input: '/'" if $fileName =~ m|/|; 1357 1358 my @records; 1359 1360 my @Users = $db->getUsers(@userIDsToExport); 1361 my @Passwords = $db->getPasswords(@userIDsToExport); 1362 my @PermissionLevels = $db->getPermissionLevels(@userIDsToExport); 1363 foreach my $i (0 .. $#userIDsToExport) { 1364 my $User = $Users[$i]; 1365 my $Password = $Passwords[$i]; 1366 my $PermissionLevel = $PermissionLevels[$i]; 1367 next unless defined $User; 1368 my %record = ( 1369 defined $PermissionLevel ? $PermissionLevel->toHash : (), 1370 defined $Password ? $Password->toHash : (), 1371 $User->toHash, 1372 ); 1373 push @records, \%record; 1374 } 1375 1376 write_classlist("$dir/$fileName", @records); 1377 } 1378 1379 ################################################################################ 1380 # "display" methods 1381 ################################################################################ 1382 1383 sub fieldEditHTML { 1384 my ($self, $fieldName, $value, $properties) = @_; 1385 my $ce = $self->r->ce; 1386 my $size = $properties->{size}; 1387 my $type = $properties->{type}; 1388 my $access = $properties->{access}; 1389 my $items = $properties->{items}; 1390 my $synonyms = $properties->{synonyms}; 1391 1392 if ($type eq "email") { 1393 if ($value eq ' ') { 1394 return $value;} 1395 else { 1396 return CGI::a({-href=>"mailto:$value"},$value); 1397 } 1398 } 1399 1400 if ($access eq "readonly") { 1401 # hack for status 1402 if ($type eq "status") { 1403 my $status_name = $ce->status_abbrev_to_name($value); 1404 if (defined $status_name) { 1405 $value = "$status_name ($value)"; 1406 } 1407 } 1408 return $value; 1409 } 1410 1411 if ($type eq "number" or $type eq "text") { 1412 return CGI::input({type=>"text", name=>$fieldName, value=>$value, size=>$size}); 1413 } 1414 1415 if ($type eq "enumerable") { 1416 my $matched = undef; # Whether a synonym match has occurred 1417 1418 # Process synonyms for enumerable objects 1419 foreach my $synonym (keys %$synonyms) { 1420 if ($synonym ne "*" and $value =~ m/$synonym/) { 1421 $value = $synonyms->{$synonym}; 1422 $matched = 1; 1423 } 1424 } 1425 1426 if (!$matched and exists $synonyms->{"*"}) { 1427 $value = $synonyms->{"*"}; 1428 } 1429 1430 return CGI::popup_menu({ 1431 name => $fieldName, 1432 values => [keys %$items], 1433 default => $value, 1434 labels => $items, 1435 }); 1436 } 1437 1438 if ($type eq "status") { 1439 # we used to surreptitously map synonyms to a canonical value... 1440 # so should we continue to do that? 1441 my $status_name = $ce->status_abbrev_to_name($value); 1442 if (defined $status_name) { 1443 $value = ($ce->status_name_to_abbrevs($status_name))[0]; 1444 } 1445 1446 my (@values, %labels); 1447 while (my ($k, $v) = each %{$ce->{statuses}}) { 1448 my @abbrevs = @{$v->{abbrevs}}; 1449 push @values, $abbrevs[0]; 1450 foreach my $abbrev (@abbrevs) { 1451 $labels{$abbrev} = $k; 1452 } 1453 } 1454 1455 return CGI::popup_menu({ 1456 name => $fieldName, 1457 values => \@values, 1458 default => $value, 1459 labels => \%labels, 1460 }); 1461 } 1462 } 1463 1464 sub recordEditHTML { 1465 my ($self, $User, $PermissionLevel, %options) = @_; 1466 my $r = $self->r; 1467 my $urlpath = $r->urlpath; 1468 my $db = $r->db; 1469 my $ce = $r->ce; 1470 my $authz = $r->authz; 1471 my $user = $r->param('user'); 1472 my $root = $ce->{webworkURLs}->{root}; 1473 my $courseName = $urlpath->arg("courseID"); 1474 1475 my $editMode = $options{editMode}; 1476 my $passwordMode = $options{passwordMode}; 1477 my $userSelected = $options{userSelected}; 1478 1479 my $statusClass = $ce->status_abbrev_to_name($User->status); 1480 1481 my $sets = $db->countUserSets($User->user_id); 1482 my $totalSets = $self->{totalSets}; 1483 1484 my $changeEUserURL = $self->systemLink($urlpath->new(type=>'set_list',args=>{courseID=>$courseName}), 1485 params => {effectiveUser => $User->user_id} 1486 ); 1487 1488 my $setsAssignedToUserURL = $self->systemLink($urlpath->new(type=>'instructor_user_detail', 1489 args=>{courseID => $courseName, 1490 userID => $User->user_id 1491 }), 1492 params => {effectiveUser => $User->user_id} 1493 ); 1494 1495 my $userListURL = $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName} )) . "&editMode=1&visible_users=" . $User->user_id; 1496 1497 my $imageURL = $ce->{webworkURLs}->{htdocs}."/images/edit.gif"; 1498 my $imageLink = CGI::a({href => $userListURL}, CGI::img({src=>$imageURL, border=>0})); 1499 1500 my @tableCells; 1501 1502 # Select 1503 if ($editMode or $passwordMode) { 1504 # column not there 1505 } else { 1506 # selection checkbox 1507 push @tableCells, CGI::checkbox( 1508 -name => "selected_users", 1509 -value => $User->user_id, 1510 -checked => $userSelected, 1511 -label => "", 1512 ); 1513 } 1514 1515 # Act As 1516 if ($editMode or $passwordMode) { 1517 # column not there 1518 } else { 1519 # selection checkbox 1520 if ( FIELD_PERMS()->{act_as} and not $authz->hasPermissions($user, FIELD_PERMS()->{act_as}) ){ 1521 push @tableCells, $User->user_id . $imageLink; 1522 } else { 1523 push @tableCells, CGI::a({href=>$changeEUserURL}, $User->user_id) . $imageLink; 1524 } 1525 } 1526 1527 # Login Status 1528 if ($editMode or $passwordMode) { 1529 # column not there 1530 } else { 1531 # check to see if a user is currently logged in 1532 my $Key = $db->getKey($User->user_id); 1533 my $is_active = ($Key and time <= $Key->timestamp()+$ce->{sessionKeyTimeout}); # cribbed from check_session 1534 push @tableCells, $is_active ? CGI::b("active") : CGI::em("inactive"); 1535 } 1536 1537 # change password (only in password mode) 1538 if ($passwordMode) { 1539 if ($User->user_id eq $user) { 1540 push @tableCells, '' # don't allow a professor to change their own password from this form 1541 } 1542 else { 1543 my $fieldName = 'user.' . $User->user_id . '.' . 'new_password'; 1544 push @tableCells, CGI::input({type=>"text", name=>$fieldName, size=>14});; 1545 } 1546 } 1547 # User ID (edit mode) or Assigned Sets (otherwise) 1548 if ( $passwordMode) { 1549 # straight user ID 1550 push @tableCells, CGI::div({class=>$statusClass}, $User->user_id); 1551 } elsif ($editMode) { 1552 # straight user ID 1553 my $userDetailPage = $urlpath->new(type =>'instructor_user_detail', 1554 args =>{ 1555 courseID => $courseName, 1556 userID => $User->user_id, #FIXME eventually this should be a list?? 1557 } 1558 ); 1559 my $userDetailUrl = $self->systemLink($userDetailPage,params =>{}); 1560 push @tableCells, CGI::a({href=>$userDetailUrl}, $User->user_id); 1561 1562 } else { 1563 # "edit sets assigned to user" link 1564 #push @tableCells, CGI::a({href=>$setsAssignedToUserURL}, "Edit sets"); 1565 if ( FIELD_PERMS()->{sets} and not $authz->hasPermissions($user, FIELD_PERMS()->{sets}) ) { 1566 push @tableCells, "$sets/$totalSets"; 1567 } else { 1568 push @tableCells, CGI::a({href=>$setsAssignedToUserURL}, "$sets/$totalSets"); 1569 } 1570 } 1571 1572 # User Fields 1573 foreach my $field ($User->NONKEYFIELDS) { 1574 my $fieldName = 'user.' . $User->user_id . '.' . $field, 1575 my $fieldValue = $User->$field; 1576 my %properties = %{ FIELD_PROPERTIES()->{$field} }; 1577 $properties{access} = 'readonly' unless $editMode; 1578 $properties{type} = 'email' if ($field eq 'email_address' and !$editMode and !$passwordMode); 1579 $fieldValue = $self->nbsp($fieldValue) unless $editMode; 1580 push @tableCells, CGI::div({class=>$statusClass}, $self->fieldEditHTML($fieldName, $fieldValue, \%properties)); 1581 } 1582 1583 # PermissionLevel Fields 1584 foreach my $field ($PermissionLevel->NONKEYFIELDS) { 1585 my $fieldName = 'permission.' . $PermissionLevel->user_id . '.' . $field, 1586 my $fieldValue = $PermissionLevel->$field; 1587 my %properties = %{ FIELD_PROPERTIES()->{$field} }; 1588 $properties{access} = 'readonly' unless $editMode; 1589 $fieldValue = $self->nbsp($fieldValue) unless $editMode; 1590 push @tableCells, CGI::div({class=>$statusClass}, $self->fieldEditHTML($fieldName, $fieldValue, \%properties)); 1591 } 1592 1593 return CGI::Tr({}, CGI::td({nowrap=>1}, \@tableCells)); 1594 } 1595 1596 sub printTableHTML { 1597 my ($self, $UsersRef, $PermissionLevelsRef, $fieldNamesRef, %options) = @_; 1598 my $r = $self->r; 1599 my $urlpath = $r->urlpath; 1600 my $courseName = $urlpath->arg("courseID"); 1601 my $userTemplate = $self->{userTemplate}; 1602 my $permissionLevelTemplate = $self->{permissionLevelTemplate}; 1603 my @Users = @$UsersRef; 1604 my @PermissionLevels = @$PermissionLevelsRef; 1605 my %fieldNames = %$fieldNamesRef; 1606 1607 my $editMode = $options{editMode}; 1608 my $passwordMode = $options{passwordMode}; 1609 my %selectedUserIDs = map { $_ => 1 } @{ $options{selectedUserIDs} }; 1610 # my $currentSort = $options{currentSort}; 1611 my $primarySortField = $options{primarySortField}; 1612 my $secondarySortField = $options{secondarySortField}; 1613 my @visableUserIDs = @{ $options{visableUserIDs} }; 1614 1615 # names of headings: 1616 my @realFieldNames = ( 1617 $userTemplate->KEYFIELDS, 1618 $userTemplate->NONKEYFIELDS, 1619 $permissionLevelTemplate->NONKEYFIELDS, 1620 ); 1621 1622 # my %sortSubs = %{ SORT_SUBS() }; 1623 #my @stateParams = @{ STATE_PARAMS() }; 1624 #my $hrefPrefix = $r->uri . "?" . $self->url_args(@stateParams); # $self->url_authen_args 1625 my @tableHeadings; 1626 foreach my $field (@realFieldNames) { 1627 my $result = $fieldNames{$field}; 1628 push @tableHeadings, $result; 1629 }; 1630 1631 # prepend selection checkbox? only if we're NOT editing! 1632 unless($editMode or $passwordMode) { 1633 1634 #warn "line 1582 visibleUserIDs=@visableUserIDs \n"; 1635 my %current_state =(); 1636 if (@visableUserIDs) { 1637 # This is a hack to get around: Maximum URL Length Is 2,083 Characters in Internet Explorer. 1638 # Without passing visable users the URL is about 250 characters. If the total URL is under the limit 1639 # we will pass visable users. If it is over, we will not pass any and all users will be displayed. 1640 # Maybe we should replace the GET method by POST (but this doesn't look good) --- AKP 1641 1642 my $visableUserIDsString = join ':', @visableUserIDs; 1643 if (length($visableUserIDsString) < 1830) { 1644 %current_state = ( 1645 primarySortField => "$primarySortField", 1646 secondarySortField => "$secondarySortField", 1647 visable_user_string => "$visableUserIDsString" 1648 ); 1649 } else { 1650 %current_state = ( 1651 primarySortField => "$primarySortField", 1652 secondarySortField => "$secondarySortField", 1653 show_all_users => "1" 1654 ); 1655 } 1656 } else { 1657 %current_state = ( 1658 primarySortField => "$primarySortField", 1659 secondarySortField => "$secondarySortField", 1660 no_visible_users => "1" 1661 ); 1662 } 1663 @tableHeadings = ( 1664 "Select", 1665 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'user_id', %current_state})}, 'Login Name'), 1666 "Login Status", 1667 "Assigned Sets", 1668 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'first_name', %current_state})}, 'First Name'), 1669 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'last_name', %current_state})}, 'Last Name'), 1670 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'email_address', %current_state})}, 'Email Address'), 1671 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'student_id', %current_state})}, 'Student ID'), 1672 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'status', %current_state})}, 'Status'), 1673 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'section', %current_state})}, 'Section'), 1674 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'recitation', %current_state})}, 'Recitation'), 1675 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'comment', %current_state})}, 'Comment'), 1676 CGI::a({href => $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName,} ), params=>{labelSortMethod=>'permission', %current_state})}, 'Permission Level'), 1677 ) 1678 } 1679 if($passwordMode) { 1680 unshift @tableHeadings, "New Password"; 1681 } 1682 1683 # print the table 1684 if ($editMode or $passwordMode) { 1685 print CGI::start_table({}); 1686 } else { 1687 print CGI::start_table({-border=>1, -nowrap=>1}); 1688 } 1689 1690 print CGI::Tr({}, CGI::th({}, \@tableHeadings)); 1691 1692 1693 for (my $i = 0; $i < @Users; $i++) { 1694 my $User = $Users[$i]; 1695 my $PermissionLevel = $PermissionLevels[$i]; 1696 1697 print $self->recordEditHTML($User, $PermissionLevel, 1698 editMode => $editMode, 1699 passwordMode => $passwordMode, 1700 userSelected => exists $selectedUserIDs{$User->user_id} 1701 ); 1702 } 1703 1704 print CGI::end_table(); 1705 ######################################### 1706 # if there are no users shown print message 1707 # 1708 ########################################## 1709 1710 print CGI::p( 1711 CGI::i("No students shown. Choose one of the options above to 1712 list the students in the course.") 1713 ) unless @Users; 1714 } 1715 1716 1; 1717
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |