[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 4757 - (download) (as text) (annotate)
Wed Jan 24 22:21:20 2007 UTC (6 years, 3 months ago) by sh002i
File size: 54488 byte(s)
backport (sh002i): fix permission not getting set (introduced with prior
changes) UGH!

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9