[system] / branches / rel-2-3-dev / webwork-modperl / lib / WeBWorK / ContentGenerator / Instructor / UserList.pm Repository:
ViewVC logotype

View of /branches/rel-2-3-dev/webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4419 - (download) (as text) (annotate)
Sat Aug 26 17:36:05 2006 UTC (6 years, 9 months ago) by sh002i
File size: 54282 byte(s)
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 &nbsp;&nbsp; $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 '&nbsp;') {
 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