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