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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3893 - (download) (as text) (annotate)
Tue Jan 10 00:07:11 2006 UTC (7 years, 4 months ago) by sh002i
File size: 54961 byte(s)
Resolves bug #942, in which a missing permission level field causes a
warning:

This is in fact due to UserList assuming that $record{permission} is
defined when it is allowed to be undefined (as per the format of
classlist files).

I added a case that sets is to $default_permission_level if it is not
defined.

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9