[system] / trunk / webwork-modperl / lib / WeBWorK / ContentGenerator / Instructor / UserList.pm Repository:
ViewVC logotype

View of /trunk/webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3287 - (download) (as text) (annotate)
Fri Jun 10 18:02:24 2005 UTC (7 years, 11 months ago) by apizer
File size: 48561 byte(s)
Added the ability to change passwords on the classlist page and fixed up sorting a bit.
For example commented out sorting by permission which didn't work.  I'll work on sorting
next fixing the above and allowing profs to sort by clicking on heading as on the
Student Progress page.

Arnie

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm,v 1.62 2005/01/14 03:15:33 toenail 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 => 50;
   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)];
   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     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     $r->header_out(Location => $uri);
  207     $self->{noContent} =  1;  # forces redirect
  208     return;
  209   };
  210 }
  211 # FIXME  -- this should be moved up to instructor or contentgenerator
  212 sub header {
  213   my $self = shift;
  214   return REDIRECT if $self->{noContent};
  215   my $r    = $self->r;
  216   $r->content_type('text/html');
  217   $r->send_http_header();
  218   return OK;
  219 }
  220 
  221 #FIXME -- this should probably be moved up to instructor or contentgenerator as well
  222 #sub nbsp {
  223 # my $str = shift;
  224 #        ($str =~/\S/) ? $str : ' '  ;  # returns non-breaking space for empty strings
  225 #                                            # tricky cases:   $str =0;
  226 #                                            #  $str is a complex number
  227 #}
  228 # moved to ContentGenerator.pm
  229 
  230 sub initialize {
  231   my ($self) = @_;
  232   my $r      = $self->r;
  233   my $db     = $r->db;
  234   my $ce     = $r->ce;
  235   my $authz  = $r->authz;
  236   my $user   = $r->param('user');
  237 
  238   # Check permissions
  239   return unless $authz->hasPermissions($user, "access_instructor_tools");
  240 
  241   #if (defined($r->param('addStudent'))) {
  242   # my $newUser = $db->newUser;
  243   # my $newPermissionLevel = $db->newPermissionLevel;
  244   # my $newPassword = $db->newPassword;
  245   # $newUser->user_id($r->param('newUserID'));
  246   # $newPermissionLevel->user_id($r->param('newUserID'));
  247   # $newPassword->user_id($r->param('newUserID'));
  248   # $newUser->status('C');
  249   # $newPermissionLevel->permission(0);
  250   # $db->addUser($newUser);
  251   # $db->addPermissionLevel($newPermissionLevel);
  252   # $db->addPassword($newPassword);
  253   #}
  254 }
  255 
  256 
  257 
  258 sub body {
  259   my ($self)       = @_;
  260   my $r            = $self->r;
  261   my $urlpath      = $r->urlpath;
  262   my $db           = $r->db;
  263   my $ce           = $r->ce;
  264   my $authz        = $r->authz;
  265   my $courseName   = $urlpath->arg("courseID");
  266   my $setID        = $urlpath->arg("setID");
  267   my $user         = $r->param('user');
  268 
  269   my $root = $ce->{webworkURLs}->{root};
  270 
  271   # templates for getting field names
  272   my $userTemplate            = $self->{userTemplate}            = $db->newUser;
  273   my $permissionLevelTemplate = $self->{permissionLevelTemplate} = $db->newPermissionLevel;
  274 
  275   return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to access the instructor tools."))
  276     unless $authz->hasPermissions($user, "access_instructor_tools");
  277 
  278   # This table can be consulted when display-ready forms of field names are needed.
  279   my %prettyFieldNames = map { $_ => $_ }
  280     $userTemplate->FIELDS(),
  281     $permissionLevelTemplate->FIELDS();
  282 
  283   @prettyFieldNames{qw(
  284     user_id
  285     first_name
  286     last_name
  287     email_address
  288     student_id
  289     status
  290     section
  291     recitation
  292     comment
  293     permission
  294   )} = (
  295     "Login Name",
  296     "First Name",
  297     "Last Name",
  298     "E-mail",
  299     "Student ID",
  300     "Status",
  301     "Section",
  302     "Recitation",
  303     "Comment",
  304     "Perm. Level"
  305   );
  306 
  307   $self->{prettyFieldNames} = \%prettyFieldNames;
  308   ########## set initial values for state fields
  309 
  310   my @allUserIDs = $db->listUsers;
  311   $self->{totalSets} = $db->listGlobalSets; # save for use in "assigned sets" links
  312   $self->{allUserIDs} = \@allUserIDs;
  313 
  314   if (defined $r->param("visible_users")) {
  315     $self->{visibleUserIDs} = [ $r->param("visible_users") ];
  316   } elsif (defined $r->param("no_visible_users")) {
  317     $self->{visibleUserIDs} = [];
  318   } else {
  319     if (@allUserIDs > HIDE_USERS_THRESHHOLD) {
  320       $self->{visibleUserIDs} = [];
  321     } else {
  322       $self->{visibleUserIDs} = [ @allUserIDs ];
  323     }
  324   }
  325 
  326   $self->{prevVisibleUserIDs} = $self->{visibleUserIDs};
  327 
  328   if (defined $r->param("selected_users")) {
  329     $self->{selectedUserIDs} = [ $r->param("selected_users") ];
  330   } else {
  331     $self->{selectedUserIDs} = [];
  332   }
  333 
  334   $self->{editMode} = $r->param("editMode") || 0;
  335 
  336   return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to modify student data"))
  337     if $self->{editMode} and not $authz->hasPermissions($user, "modify_student_data");
  338 
  339 
  340   $self->{passwordMode} = $r->param("passwordMode") || 0;
  341 
  342   return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to modify student data"))
  343     if $self->{passwordMode} and not $authz->hasPermissions($user, "modify_student_data");
  344 
  345 
  346   $self->{primarySortField} = $r->param("primarySortField") || "last_name";
  347   $self->{secondarySortField} = $r->param("secondarySortField") || "first_name";
  348   $self->{ternarySortField} = $r->param("ternarySortField") || "student_id";
  349 
  350   my @allUsers = $db->getUsers(@allUserIDs);
  351   my (%sections, %recitations);
  352   foreach my $User (@allUsers) {
  353     push @{$sections{defined $User->section ? $User->section : ""}}, $User->user_id;
  354     push @{$recitations{defined $User->recitation ? $User->recitation : ""}}, $User->user_id;
  355   }
  356   $self->{sections} = \%sections;
  357   $self->{recitations} = \%recitations;
  358 
  359   ########## call action handler
  360 
  361   my $actionID = $r->param("action");
  362   if ($actionID) {
  363     unless (grep { $_ eq $actionID } @{ VIEW_FORMS() }, @{ EDIT_FORMS() }, @{ PASSWORD_FORMS() } ) {
  364       die "Action $actionID not found";
  365     }
  366     # Check permissions
  367     if (not FORM_PERMS()->{$actionID} or $authz->hasPermissions($user, FORM_PERMS()->{$actionID})) {
  368       my $actionHandler = "${actionID}_handler";
  369       my %genericParams;
  370       foreach my $param (qw(selected_users)) {
  371         $genericParams{$param} = [ $r->param($param) ];
  372       }
  373       my %actionParams = $self->getActionParams($actionID);
  374       my %tableParams = $self->getTableParams();
  375       print CGI::p(
  376           '<div style="color:green">',
  377         "Result of last action performed: ",
  378         CGI::i($self->$actionHandler(\%genericParams, \%actionParams, \%tableParams)),
  379         '</div>',
  380         CGI::hr()
  381       );
  382     } else {
  383       return CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to perform this action."));
  384     }
  385   }
  386 
  387   ########## retrieve possibly changed values for member fields
  388 
  389   #@allUserIDs = @{ $self->{allUserIDs} }; # do we need this one?
  390   my @visibleUserIDs = @{ $self->{visibleUserIDs} };
  391   my @prevVisibleUserIDs = @{ $self->{prevVisibleUserIDs} };
  392   my @selectedUserIDs = @{ $self->{selectedUserIDs} };
  393   my $editMode = $self->{editMode};
  394   my $passwordMode = $self->{passwordMode};
  395   my $primarySortField = $self->{primarySortField};
  396   my $secondarySortField = $self->{secondarySortField};
  397   my $ternarySortField = $self->{ternarySortField};
  398 
  399   #warn "visibleUserIDs=@visibleUserIDs\n";
  400   #warn "prevVisibleUserIDs=@prevVisibleUserIDs\n";
  401   #warn "selectedUserIDs=@selectedUserIDs\n";
  402   #warn "editMode=$editMode\n";
  403   #warn "passwordMode=$passwordMode\n";
  404 
  405   ########## get required users
  406 
  407   my @Users = grep { defined $_ } @visibleUserIDs ? $db->getUsers(@visibleUserIDs) : ();
  408 
  409   my %sortSubs = %{ SORT_SUBS() };
  410   my $primarySortSub = $sortSubs{$primarySortField};
  411   my $secondarySortSub = $sortSubs{$secondarySortField};
  412   my $ternarySortSub = $sortSubs{$ternarySortField};
  413 
  414 
  415 # # don't forget to sort in opposite order of importance
  416 # @Users = sort $secondarySortSub @Users;
  417 # @Users = sort $primarySortSub @Users;
  418 # #@Users = sort byLnFnUid @Users;
  419 
  420 #   Always have a definite sort order even if first three sorts don't determine things
  421   @Users = sort {
  422     &$primarySortSub
  423       ||
  424     &$secondarySortSub
  425       ||
  426     &$ternarySortSub
  427       ||
  428     byLastName
  429       ||
  430     byFirstName
  431       ||
  432     byUserID
  433     }
  434     @Users;
  435 
  436   my @PermissionLevels;
  437 
  438   for (my $i = 0; $i < @Users; $i++) {
  439     my $User = $Users[$i];
  440     my $PermissionLevel = $db->getPermissionLevel($User->user_id); # checked
  441 
  442     unless ($PermissionLevel) {
  443       # uh oh! no permission level record found!
  444       warn "added missing permission level for user ", $User->user_id, "\n";
  445 
  446       # create a new permission level record
  447       $PermissionLevel = $db->newPermissionLevel;
  448       $PermissionLevel->user_id($User->user_id);
  449       $PermissionLevel->permission(0);
  450 
  451       # add it to the database
  452       $db->addPermissionLevel($PermissionLevel);
  453     }
  454 
  455     $PermissionLevels[$i] = $PermissionLevel;
  456   }
  457 
  458   ########## print beginning of form
  459 
  460   print CGI::start_form({method=>"post", action=>$self->systemLink($urlpath,authen=>0), name=>"userlist"});
  461   print $self->hidden_authen_fields();
  462 
  463   ########## print state data
  464 
  465   print "\n<!-- state data here -->\n";
  466 
  467   if (@visibleUserIDs) {
  468     print CGI::hidden(-name=>"visible_users", -value=>\@visibleUserIDs);
  469   } else {
  470     print CGI::hidden(-name=>"no_visible_users", -value=>"1");
  471   }
  472 
  473   if (@prevVisibleUserIDs) {
  474     print CGI::hidden(-name=>"prev_visible_users", -value=>\@prevVisibleUserIDs);
  475   } else {
  476     print CGI::hidden(-name=>"no_prev_visible_users", -value=>"1");
  477   }
  478 
  479   print CGI::hidden(-name=>"editMode", -value=>$editMode);
  480 
  481   print CGI::hidden(-name=>"passwordMode", -value=>$passwordMode);
  482 
  483   print CGI::hidden(-name=>"primarySortField", -value=>$primarySortField);
  484   print CGI::hidden(-name=>"secondarySortField", -value=>$secondarySortField);
  485   print CGI::hidden(-name=>"ternarySortField", -value=>$ternarySortField);
  486 
  487   print "\n<!-- state data here -->\n";
  488 
  489   ########## print action forms
  490 
  491   print CGI::start_table({});
  492   print CGI::Tr({}, CGI::td({-colspan=>2}, "Select an action to perform:"));
  493 
  494   my @formsToShow;
  495   if ($editMode) {
  496     @formsToShow = @{ EDIT_FORMS() };
  497   }elsif ($passwordMode) {
  498     @formsToShow = @{ PASSWORD_FORMS() };
  499   } else {
  500     @formsToShow = @{ VIEW_FORMS() };
  501   }
  502 
  503   my $i = 0;
  504   foreach my $actionID (@formsToShow) {
  505     # Check permissions
  506     next if FORM_PERMS()->{$actionID} and not $authz->hasPermissions($user, FORM_PERMS()->{$actionID});
  507     my $actionForm = "${actionID}_form";
  508     my $onChange = "document.userlist.action[$i].checked=true";
  509     my %actionParams = $self->getActionParams($actionID);
  510 
  511     print CGI::Tr({-valign=>"top"},
  512       CGI::td({}, CGI::input({-type=>"radio", -name=>"action", -value=>$actionID})),
  513       CGI::td({}, $self->$actionForm($onChange, %actionParams))
  514     );
  515 
  516     $i++;
  517   }
  518 
  519   print CGI::Tr({}, CGI::td({-colspan=>2, -align=>"center"},
  520     CGI::submit(-value=>"Take Action!"))
  521   );
  522   print CGI::end_table();
  523 
  524   ########## print table
  525 
  526   print CGI::p("Showing ", scalar @Users, " out of ", scalar @allUserIDs, " users.");
  527 
  528   print CGI::p("If a password field is left blank, the student's current password will be maintained.") if $passwordMode;
  529 
  530   $self->printTableHTML(\@Users, \@PermissionLevels, \%prettyFieldNames,
  531     editMode => $editMode,
  532     passwordMode => $passwordMode,
  533     selectedUserIDs => \@selectedUserIDs,
  534   );
  535 
  536 
  537   ########## print end of form
  538 
  539   print CGI::end_form();
  540 
  541   return "";
  542 }
  543 
  544 ################################################################################
  545 # extract particular params and put them in a hash (values are ARRAYREFs!)
  546 ################################################################################
  547 
  548 sub getActionParams {
  549   my ($self, $actionID) = @_;
  550   my $r = $self->{r};
  551 
  552   my %actionParams;
  553   foreach my $param ($r->param) {
  554     next unless $param =~ m/^action\.$actionID\./;
  555     $actionParams{$param} = [ $r->param($param) ];
  556   }
  557   return %actionParams;
  558 }
  559 
  560 sub getTableParams {
  561   my ($self) = @_;
  562   my $r = $self->{r};
  563 
  564   my %tableParams;
  565   foreach my $param ($r->param) {
  566     next unless $param =~ m/^(?:user|permission)\./;
  567     $tableParams{$param} = [ $r->param($param) ];
  568   }
  569   return %tableParams;
  570 }
  571 
  572 ################################################################################
  573 # actions and action triggers
  574 ################################################################################
  575 
  576 # filter, edit, cancelEdit, and saveEdit should stay with the display module and
  577 # not be real "actions". that way, all actions are shown in view mode and no
  578 # actions are shown in edit mode.
  579 
  580 sub filter_form {
  581   my ($self, $onChange, %actionParams) = @_;
  582   #return CGI::table({}, CGI::Tr({-valign=>"top"},
  583   # CGI::td({},
  584 
  585   my %prettyFieldNames = %{ $self->{prettyFieldNames} };
  586 
  587   return join("",
  588       "Show ",
  589       CGI::popup_menu(
  590         -name => "action.filter.scope",
  591         -values => [qw(all none selected match_regex)],
  592         -default => $actionParams{"action.filter.scope"}->[0] || "match_regex",
  593         -labels => {
  594           all => "all users",
  595           none => "no users",
  596           selected => "users checked below",
  597 #         match_ids => "users with matching user IDs:",
  598           match_regex => "users who match:",
  599 #         match_section => "users in selected section",
  600 #         match_recitation => "users in selected recitation",
  601         },
  602         -onchange => $onChange,
  603       ),
  604       " ",
  605       CGI::textfield(
  606         -name => "action.filter.user_ids",
  607         -value => $actionParams{"action.filter.user_ids"}->[0] || "",,
  608         -width => "50",
  609         -onchange => $onChange,
  610       ),
  611 #     " (separate multiple IDs with commas)",
  612 #     CGI::br(),
  613 #     "sections: ",
  614 #     CGI::popup_menu(
  615 #       -name => "action.filter.section",
  616 #       -values => [ keys %{ $self->{sections} } ],
  617 #       -default => $actionParams{"action.filter.section"}->[0] || "",
  618 #       -labels => { $self->menuLabels($self->{sections}) },
  619 #       -onchange => $onChange,
  620 #     ),
  621 #     " recitations: ",
  622 #     CGI::popup_menu(
  623 #       -name => "action.filter.recitation",
  624 #       -values => [ keys %{ $self->{recitations} } ],
  625 #       -default => $actionParams{"action.filter.recitation"}->[0] || "",
  626 #       -labels => { $self->menuLabels($self->{recitations}) },
  627 #       -onchange => $onChange,
  628 #     ),
  629       " in their ",
  630       CGI::popup_menu(
  631         -name => "action.filter.field",
  632         -value => [ keys %{ FIELD_PROPERTIES() } ],
  633         -default => $actionParams{"action.filter.field"}->[0] || "user_id",
  634         -labels => \%prettyFieldNames,
  635         -onchange => $onChange,
  636       ),
  637   );
  638   # ),
  639   #));
  640 }
  641 
  642 # this action handler modifies the "visibleUserIDs" field based on the contents
  643 # of the "action.filter.scope" parameter and the "selected_users"
  644 sub filter_handler {
  645   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  646 
  647   my $r = $self->r;
  648   my $db = $r->db;
  649 
  650   my $result;
  651 
  652   my $scope = $actionParams->{"action.filter.scope"}->[0];
  653   if ($scope eq "all") {
  654     $result = "showing all users";
  655     $self->{visibleUserIDs} = $self->{allUserIDs};
  656   } elsif ($scope eq "none") {
  657     $result = "showing no users";
  658     $self->{visibleUserIDs} = [];
  659   } elsif ($scope eq "selected") {
  660     $result = "showing selected users";
  661     $self->{visibleUserIDs} = $genericParams->{selected_users}; # an arrayref
  662   } elsif ($scope eq "match_regex") {
  663     $result = "showing matching users";
  664     my $regex = $actionParams->{"action.filter.user_ids"}->[0];
  665     my $field = $actionParams->{"action.filter.field"}->[0];
  666     my @userRecords = $db->getUsers(@{$self->{allUserIDs}});
  667     my @userIDs;
  668     foreach my $record (@userRecords) {
  669       next unless $record;
  670 
  671       # add permission level to user record hash so we can match it if necessary
  672       if ($field eq "permission") {
  673         my $permissionLevel = $db->getPermissionLevel($record->user_id);
  674                           $record->{permission} = $permissionLevel->permission;
  675       }
  676       push @userIDs, $record->user_id if $record->{$field} =~ /^$regex/i;
  677     }
  678     $self->{visibleUserIDs} = \@userIDs;
  679   } elsif ($scope eq "match_ids") {
  680     my @userIDs = split /\s*,\s*/, $actionParams->{"action.filter.user_ids"}->[0];
  681     $self->{visibleUserIDs} = \@userIDs;
  682   } elsif ($scope eq "match_section") {
  683     my $section = $actionParams->{"action.filter.section"}->[0];
  684     $self->{visibleUserIDs} = $self->{sections}->{$section}; # an arrayref
  685   } elsif ($scope eq "match_recitation") {
  686     my $recitation = $actionParams->{"action.filter.recitation"}->[0];
  687     $self->{visibleUserIDs} = $self->{recitations}->{$recitation}; # an arrayref
  688   }
  689 
  690   return $result;
  691 }
  692 
  693 sub sort_form {
  694   my ($self, $onChange, %actionParams) = @_;
  695   return join ("",
  696     "Sort by ",
  697     CGI::popup_menu(
  698       -name => "action.sort.primary",
  699 #     -values => [qw(user_id first_name last_name email_address student_id status section recitation comment permission)], ## This isn't defined and I don't have time to fix it right now AKP
  700       -values => [qw(user_id first_name last_name email_address student_id status section recitation comment)],
  701       -default => $actionParams{"action.sort.primary"}->[0] || "last_name",
  702       -labels => {
  703         user_id   => "Login Name",
  704         first_name  => "First Name",
  705         last_name => "Last Name",
  706         email_address => "Email address",
  707         student_id  => "Student ID",
  708         status    => "Enrollment Status",
  709         section   => "Section",
  710         recitation  => "Recitation",
  711         comment   => "Comment",
  712 #       permission  => "Perm. Level"  ## This isn't defined and I don't have time to fix it right now AKP
  713       },
  714       -onchange => $onChange,
  715     ),
  716     ", then by ",
  717     CGI::popup_menu(
  718       -name => "action.sort.secondary",
  719 #     -values => [qw(user_id first_name last_name email_address student_id status section recitation comment permission)], ## This isn't defined and I don't have time to fix it right now AKP
  720       -values => [qw(user_id first_name last_name email_address student_id status section recitation comment)],
  721       -default => $actionParams{"action.sort.secondary"}->[0] || "first_name",
  722       -labels => {
  723         user_id   => "Login Name",
  724         first_name  => "First Name",
  725         last_name => "Last Name",
  726         email_address => "Email address",
  727         student_id  => "Student ID",
  728         status    => "Enrollment Status",
  729         section   => "Section",
  730         recitation  => "Recitation",
  731         comment   => "Comment",
  732 #       permission  => "Perm. Level"
  733       },
  734       -onchange => $onChange,
  735     ),
  736     ", then by ",
  737     CGI::popup_menu(
  738       -name => "action.sort.ternary",
  739 #     -values => [qw(user_id first_name last_name email_address student_id status section recitation comment permission)], ## This isn't defined and I don't have time to fix it right now AKP
  740       -values => [qw(user_id first_name last_name email_address student_id status section recitation comment)],
  741       -default => $actionParams{"action.sort.ternary"}->[0] || "user_id",
  742       -labels => {
  743         user_id   => "Login Name",
  744         first_name  => "First Name",
  745         last_name => "Last Name",
  746         email_address => "Email address",
  747         student_id  => "Student ID",
  748         status    => "Enrollment Status",
  749         section   => "Section",
  750         recitation  => "Recitation",
  751         comment   => "Comment",
  752 #       permission  => "Perm. Level"
  753       },
  754       -onchange => $onChange,
  755     ),
  756 
  757     ".",
  758   );
  759 }
  760 
  761 sub sort_handler {
  762   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  763 
  764   my $primary = $actionParams->{"action.sort.primary"}->[0];
  765   my $secondary = $actionParams->{"action.sort.secondary"}->[0];
  766   my $ternary = $actionParams->{"action.sort.ternary"}->[0];
  767 
  768   $self->{primarySortField} = $primary;
  769   $self->{secondarySortField} = $secondary;
  770   $self->{ternarySortField} = $ternary;
  771 
  772   my %names = (
  773         user_id   => "Login Name",
  774         first_name  => "First Name",
  775         last_name => "Last Name",
  776         email_address => "Email address",
  777         student_id  => "Student ID",
  778         status    => "Enrollment Status",
  779         section   => "Section",
  780         recitation  => "Recitation",
  781         comment   => "Comment",
  782         permission  => "Perm. Level"
  783   );
  784 
  785   return "Users sorted by $names{$primary}, then by $names{$secondary}, then by $names{$ternary}.";
  786 }
  787 
  788 sub edit_form {
  789   my ($self, $onChange, %actionParams) = @_;
  790 
  791   return join("",
  792     "Edit ",
  793     CGI::popup_menu(
  794       -name => "action.edit.scope",
  795       -values => [qw(all visible selected)],
  796       -default => $actionParams{"action.edit.scope"}->[0] || "selected",
  797       -labels => {
  798         all => "all users",
  799         visible => "visible users",
  800         selected => "selected users"
  801       },
  802       -onchange => $onChange,
  803     ),
  804   );
  805 }
  806 
  807 sub edit_handler {
  808   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  809 
  810   my $result;
  811 
  812   my $scope = $actionParams->{"action.edit.scope"}->[0];
  813   if ($scope eq "all") {
  814     $result = "editing all users";
  815     $self->{visibleUserIDs} = $self->{allUserIDs};
  816   } elsif ($scope eq "visible") {
  817     $result = "editing visible users";
  818     # leave visibleUserIDs alone
  819   } elsif ($scope eq "selected") {
  820     $result = "editing selected users";
  821     $self->{visibleUserIDs} = $genericParams->{selected_users}; # an arrayref
  822   }
  823   $self->{editMode} = 1;
  824 
  825   return $result;
  826 }
  827 
  828 
  829 sub password_form {
  830   my ($self, $onChange, %actionParams) = @_;
  831 
  832   return join("",
  833     "Give new password to ",
  834     CGI::popup_menu(
  835       -name => "action.password.scope",
  836       -values => [qw(all visible selected)],
  837       -default => $actionParams{"action.password.scope"}->[0] || "selected",
  838       -labels => {
  839         all => "all users",
  840         visible => "visible users",
  841         selected => "selected users"
  842       },
  843       -onchange => $onChange,
  844     ),
  845   );
  846 }
  847 
  848 sub password_handler {
  849   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  850 
  851   my $result;
  852 
  853   my $scope = $actionParams->{"action.password.scope"}->[0];
  854   if ($scope eq "all") {
  855     $result = "giving new passwords to all users";
  856     $self->{visibleUserIDs} = $self->{allUserIDs};
  857   } elsif ($scope eq "visible") {
  858     $result = "giving new passwords to visible users";
  859     # leave visibleUserIDs alone
  860   } elsif ($scope eq "selected") {
  861     $result = "giving new passwords to selected users";
  862     $self->{visibleUserIDs} = $genericParams->{selected_users}; # an arrayref
  863   }
  864   $self->{passwordMode} = 1;
  865 
  866   return $result;
  867 }
  868 
  869 sub delete_form {
  870   my ($self, $onChange, %actionParams) = @_;
  871 
  872   return join("",
  873         CGI::div({class=>"ResultsWithError"},
  874     "Delete ",
  875     CGI::popup_menu(
  876       -name => "action.delete.scope",
  877       -values => [qw(none selected)],
  878       -default => $actionParams{"action.delete.scope"}->[0] || "none",
  879       -labels => {
  880           none     => "no users.",
  881         #visible  => "visible users.",
  882         selected => "selected users."
  883       },
  884       -onchange => $onChange,
  885     ),
  886     CGI::em(" Deletion destroys all user-related data and is not undoable!"),
  887     ),
  888   );
  889 }
  890 
  891 sub delete_handler {
  892   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  893   my $r         = $self->r;
  894   my $db        = $r->db;
  895   my $user      = $r->param('user');
  896   my $scope = $actionParams->{"action.delete.scope"}->[0];
  897 
  898   my @userIDsToDelete = ();
  899   #if ($scope eq "visible") {
  900   # @userIDsToDelete = @{ $self->{visibleUserIDs} };
  901   #} elsif ($scope eq "selected") {
  902   if ($scope eq "selected") {
  903     @userIDsToDelete = @{ $self->{selectedUserIDs} };
  904   }
  905 
  906   my %allUserIDs = map { $_ => 1 } @{ $self->{allUserIDs} };
  907   my %visibleUserIDs = map { $_ => 1 } @{ $self->{visibleUserIDs} };
  908   my %selectedUserIDs = map { $_ => 1 } @{ $self->{selectedUserIDs} };
  909 
  910   my $error = "";
  911   my $num = 0;
  912   foreach my $userID (@userIDsToDelete) {
  913     if ($user eq $userID) { # don't delete yourself!!
  914       $error = "You cannot delete yourself!";
  915       next;
  916     }
  917     delete $allUserIDs{$userID};
  918     delete $visibleUserIDs{$userID};
  919     delete $selectedUserIDs{$userID};
  920     $db->deleteUser($userID);
  921     $num++;
  922   }
  923 
  924   $self->{allUserIDs} = [ keys %allUserIDs ];
  925   $self->{visibleUserIDs} = [ keys %visibleUserIDs ];
  926   $self->{selectedUserIDs} = [ keys %selectedUserIDs ];
  927 
  928   return "deleted $num user" . ($num == 1 ? "" : "s.  ") . $error;
  929 }
  930 sub add_form {
  931   my ($self, $onChange, %actionParams) = @_;
  932 
  933     return "Add ", CGI::input({name=>'number_of_students', value=>1,size => 3}), " student(s). ";
  934 }
  935 
  936 sub add_handler {
  937   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  938   # This action is redirected to the addUser.pm module using ../instructor/add_user/...
  939   return "Nothing done by add student handler";
  940 }
  941 sub import_form {
  942   my ($self, $onChange, %actionParams) = @_;
  943   return join(" ",
  944     "Import users from file",
  945     CGI::popup_menu(
  946       -name => "action.import.source",
  947       -values => [ $self->getCSVList() ],
  948       -default => $actionParams{"action.import.source"}->[0] || "",
  949       -onchange => $onChange,
  950     ),
  951     "replacing",
  952     CGI::popup_menu(
  953       -name => "action.import.replace",
  954       -values => [qw(any visible selected none)],
  955       -default => $actionParams{"action.import.replace"}->[0] || "none",
  956       -labels => {
  957         any => "any",
  958         visible => "visible",
  959         selected => "selected",
  960         none => "no",
  961       },
  962       -onchange => $onChange,
  963     ),
  964     "existing users and adding",
  965     CGI::popup_menu(
  966       -name => "action.import.add",
  967       -values => [qw(any none)],
  968       -default => $actionParams{"action.import.add"}->[0] || "any",
  969       -labels => {
  970         any => "any",
  971         none => "no",
  972       },
  973       -onchange => $onChange,
  974     ),
  975     "new users",
  976   );
  977 }
  978 
  979 sub import_handler {
  980   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  981 
  982   my $source = $actionParams->{"action.import.source"}->[0];
  983   my $add = $actionParams->{"action.import.add"}->[0];
  984   my $replace = $actionParams->{"action.import.replace"}->[0];
  985 
  986   my $fileName = $source;
  987   my $createNew = $add eq "any";
  988   my $replaceExisting;
  989   my @replaceList;
  990   if ($replace eq "any") {
  991     $replaceExisting = "any";
  992   } elsif ($replace eq "none") {
  993     $replaceExisting = "none";
  994   } elsif ($replace eq "visible") {
  995     $replaceExisting = "listed";
  996     @replaceList = @{ $self->{visibleUserIDs} };
  997   } elsif ($replace eq "selected") {
  998     $replaceExisting = "listed";
  999     @replaceList = @{ $self->{selectedUserIDs} };
 1000   }
 1001 
 1002   my ($replaced, $added, $skipped)
 1003     = $self->importUsersFromCSV($fileName, $createNew, $replaceExisting, @replaceList);
 1004 
 1005   # make new users visible... do we really want to do this? probably.
 1006   push @{ $self->{visibleUserIDs} }, @$added;
 1007 
 1008   my $numReplaced = @$replaced;
 1009   my $numAdded = @$added;
 1010   my $numSkipped = @$skipped;
 1011 
 1012   return $numReplaced . " user" . ($numReplaced == 1 ? "" : "s") . " replaced, "
 1013     . $numAdded . " user" . ($numAdded == 1 ? "" : "s") . " added, "
 1014     . $numSkipped . " user" . ($numSkipped == 1 ? "" : "s") . " skipped"
 1015     . " (" . join (", ", @$skipped) . ") ";
 1016 }
 1017 
 1018 sub export_form {
 1019   my ($self, $onChange, %actionParams) = @_;
 1020   return join("",
 1021     "Export ",
 1022     CGI::popup_menu(
 1023       -name => "action.export.scope",
 1024       -values => [qw(all visible selected)],
 1025       -default => $actionParams{"action.export.scope"}->[0] || "visible",
 1026       -labels => {
 1027         all => "all users",
 1028         visible => "visible users",
 1029         selected => "selected users"
 1030       },
 1031       -onchange => $onChange,
 1032     ),
 1033     " to ",
 1034     CGI::popup_menu(
 1035       -name=>"action.export.target",
 1036       -values => [ "new", $self->getCSVList() ],
 1037       -labels => { new => "a new file named:" },
 1038       -default => $actionParams{"action.export.target"}->[0] || "",
 1039       -onchange => $onChange,
 1040     ),
 1041     #CGI::br(),
 1042     #"new file to create: ",
 1043     CGI::textfield(
 1044       -name => "action.export.new",
 1045       -value => $actionParams{"action.export.new"}->[0] || "",,
 1046       -width => "50",
 1047       -onchange => $onChange,
 1048     ),
 1049     CGI::tt(".lst"),
 1050   );
 1051 }
 1052 
 1053 sub export_handler {
 1054   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1055 
 1056   my $scope = $actionParams->{"action.export.scope"}->[0];
 1057   my $target = $actionParams->{"action.export.target"}->[0];
 1058   my $new = $actionParams->{"action.export.new"}->[0];
 1059 
 1060   my $fileName;
 1061   if ($target eq "new") {
 1062     $fileName = $new;
 1063   } else {
 1064     $fileName = $target;
 1065   }
 1066 
 1067   $fileName .= ".lst" unless $fileName =~ m/\.lst$/;
 1068 
 1069   my @userIDsToExport;
 1070   if ($scope eq "all") {
 1071     @userIDsToExport = @{ $self->{allUserIDs} };
 1072   } elsif ($scope eq "visible") {
 1073     @userIDsToExport = @{ $self->{visibleUserIDs} };
 1074   } elsif ($scope eq "selected") {
 1075     @userIDsToExport = @{ $self->{selectedUserIDs} };
 1076   }
 1077 
 1078   $self->exportUsersToCSV($fileName, @userIDsToExport);
 1079 
 1080   return scalar @userIDsToExport . " users exported";
 1081 }
 1082 
 1083 sub cancelEdit_form {
 1084   my ($self, $onChange, %actionParams) = @_;
 1085   return "Abandon changes";
 1086 }
 1087 
 1088 sub cancelEdit_handler {
 1089   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1090   my $r      = $self->r;
 1091 
 1092   #$self->{selectedUserIDs} = $self->{visibleUserIDs};
 1093     # only do the above if we arrived here via "edit selected users"
 1094   if (defined $r->param("prev_visible_users")) {
 1095     $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ];
 1096   } elsif (defined $r->param("no_prev_visible_users")) {
 1097     $self->{visibleUserIDs} = [];
 1098   } else {
 1099     # leave it alone
 1100   }
 1101   $self->{editMode} = 0;
 1102 
 1103   return "changes abandoned";
 1104 }
 1105 
 1106 sub saveEdit_form {
 1107   my ($self, $onChange, %actionParams) = @_;
 1108   return "Save changes";
 1109 }
 1110 
 1111 sub saveEdit_handler {
 1112   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1113   my $r           = $self->r;
 1114   my $db          = $r->db;
 1115 
 1116   my @visibleUserIDs = @{ $self->{visibleUserIDs} };
 1117   foreach my $userID (@visibleUserIDs) {
 1118     my $User = $db->getUser($userID); # checked
 1119     die "record for visible user $userID not found" unless $User;
 1120     my $PermissionLevel = $db->getPermissionLevel($userID); # checked
 1121     die "permissions for $userID not defined" unless defined $PermissionLevel;
 1122     foreach my $field ($User->NONKEYFIELDS()) {
 1123       my $param = "user.${userID}.${field}";
 1124       if (defined $tableParams->{$param}->[0]) {
 1125         $User->$field($tableParams->{$param}->[0]);
 1126       }
 1127     }
 1128 
 1129     foreach my $field ($PermissionLevel->NONKEYFIELDS()) {
 1130       my $param = "permission.${userID}.${field}";
 1131       if (defined $tableParams->{$param}->[0]) {
 1132         $PermissionLevel->$field($tableParams->{$param}->[0]);
 1133       }
 1134     }
 1135 
 1136     $db->putUser($User);
 1137     $db->putPermissionLevel($PermissionLevel);
 1138   }
 1139 
 1140   if (defined $r->param("prev_visible_users")) {
 1141     $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ];
 1142   } elsif (defined $r->param("no_prev_visible_users")) {
 1143     $self->{visibleUserIDs} = [];
 1144   } else {
 1145     # leave it alone
 1146   }
 1147 
 1148   $self->{editMode} = 0;
 1149 
 1150   return "changes saved";
 1151 }
 1152 
 1153 sub cancelPassword_form {
 1154   my ($self, $onChange, %actionParams) = @_;
 1155   return "Abandon changes";
 1156 }
 1157 
 1158 sub cancelPassword_handler {
 1159   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1160   my $r      = $self->r;
 1161 
 1162   #$self->{selectedUserIDs} = $self->{visibleUserIDs};
 1163     # only do the above if we arrived here via "edit selected users"
 1164   if (defined $r->param("prev_visible_users")) {
 1165     $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ];
 1166   } elsif (defined $r->param("no_prev_visible_users")) {
 1167     $self->{visibleUserIDs} = [];
 1168   } else {
 1169     # leave it alone
 1170   }
 1171   $self->{passwordMode} = 0;
 1172 
 1173   return "changes abandoned";
 1174 }
 1175 
 1176 sub savePassword_form {
 1177   my ($self, $onChange, %actionParams) = @_;
 1178   return "Save changes";
 1179 }
 1180 
 1181 sub savePassword_handler {
 1182   my ($self, $genericParams, $actionParams, $tableParams) = @_;
 1183   my $r           = $self->r;
 1184   my $db          = $r->db;
 1185 
 1186   my @visibleUserIDs = @{ $self->{visibleUserIDs} };
 1187   foreach my $userID (@visibleUserIDs) {
 1188     my $User = $db->getUser($userID); # checked
 1189     die "record for visible user $userID not found" unless $User;
 1190     my $param = "user.${userID}.new_password";
 1191       if ((defined $tableParams->{$param}->[0]) and ($tableParams->{$param}->[0])) {
 1192         my $newP = $tableParams->{$param}->[0];
 1193         my $Password = eval {$db->getPassword($User->user_id)}; # checked
 1194         my  $cryptPassword = cryptPassword($newP);
 1195         $Password->password(cryptPassword($newP));
 1196         eval { $db->putPassword($Password) };
 1197       }
 1198   }
 1199 
 1200   if (defined $r->param("prev_visible_users")) {
 1201     $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ];
 1202   } elsif (defined $r->param("no_prev_visible_users")) {
 1203     $self->{visibleUserIDs} = [];
 1204   } else {
 1205     # leave it alone
 1206   }
 1207 
 1208   $self->{passwordMode} = 0;
 1209 
 1210   return "new passwords saved";
 1211 }
 1212 
 1213 
 1214 ################################################################################
 1215 # sorts
 1216 ################################################################################
 1217 
 1218 sub byUserID       { lc $a->user_id       cmp lc $b->user_id       }
 1219 sub byFirstName    {  (defined $a->first_name && defined $b->first_name) ?  lc $a->first_name cmp lc $b->first_name  : 0;  }
 1220 sub byLastName     {  (defined $a->last_name  && defined $b->last_name ) ?  lc $a->last_name  cmp lc $b->last_name   : 0;  }
 1221 sub byEmailAddress { lc $a->email_address cmp lc $b->email_address }
 1222 sub byStudentID    { lc $a->student_id    cmp lc $b->student_id    }
 1223 sub byStatus       { lc $a->status        cmp lc $b->status        }
 1224 sub bySection      { lc $a->section       cmp lc $b->section       }
 1225 sub byRecitation   { lc $a->recitation    cmp lc $b->recitation    }
 1226 sub byComment      { lc $a->comment       cmp lc $b->comment       }
 1227 #sub byPermission   { $a->permission        <=> $b->permission        }
 1228 
 1229 # sub byLnFnUid { &byLastName || &byFirstName || &byUserID }
 1230 
 1231 ################################################################################
 1232 # utilities
 1233 ################################################################################
 1234 
 1235 # generate labels for section/recitation popup menus
 1236 sub menuLabels {
 1237   my ($self, $hashRef) = @_;
 1238   my %hash = %$hashRef;
 1239 
 1240   my %result;
 1241   foreach my $key (keys %hash) {
 1242     my $count = @{ $hash{$key} };
 1243     my $displayKey = $key || "<none>";
 1244     $result{$key} = "$displayKey ($count users)";
 1245   }
 1246   return %result;
 1247 }
 1248 
 1249 sub importUsersFromCSV {
 1250   my ($self, $fileName, $createNew, $replaceExisting, @replaceList) = @_;
 1251   my $r     = $self->r;
 1252   my $ce    = $r->ce;
 1253   my $db    = $r->db;
 1254   my $dir   = $ce->{courseDirs}->{templates};
 1255   my $user  = $r->param('user');
 1256 
 1257   die "illegal character in input: '/'" if $fileName =~ m|/|;
 1258   die "won't be able to read from file $dir/$fileName: does it exist? is it readable?"
 1259     unless -r "$dir/$fileName";
 1260 
 1261   my %allUserIDs = map { $_ => 1 } @{ $self->{allUserIDs} };
 1262   my %replaceOK;
 1263   if ($replaceExisting eq "none") {
 1264     %replaceOK = ();
 1265   } elsif ($replaceExisting eq "listed") {
 1266     %replaceOK = map { $_ => 1 } @replaceList;
 1267   } elsif ($replaceExisting eq "any") {
 1268     %replaceOK = %allUserIDs;
 1269   }
 1270 
 1271   my (@replaced, @added, @skipped);
 1272 
 1273   # get list of hashrefs representing lines in classlist file
 1274   my @classlist = parse_classlist("$dir/$fileName");
 1275 
 1276   foreach my $record (@classlist) {
 1277     my %record = %$record;
 1278     my $user_id = $record{user_id};
 1279 
 1280     if ($user_id eq $user) { # don't replace yourself!!
 1281       push @skipped, $user_id;
 1282       next;
 1283     }
 1284 
 1285     if (exists $allUserIDs{$user_id} and not exists $replaceOK{$user_id}) {
 1286       push @skipped, $user_id;
 1287       next;
 1288     }
 1289 
 1290     if (not exists $allUserIDs{$user_id} and not $createNew) {
 1291       push @skipped, $user_id;
 1292       next;
 1293     }
 1294 
 1295     my $User = $db->newUser(%record);
 1296     my $PermissionLevel = $db->newPermissionLevel(user_id => $user_id, permission => 0);
 1297     my $Password = $db->newPassword(user_id => $user_id, password => cryptPassword($record{student_id}));
 1298 
 1299     # use password and permission from record if there
 1300     if (exists $record{permission}) {
 1301       $PermissionLevel->permission($record{permission});
 1302     }
 1303 
 1304     if (exists $record{password}) {
 1305       $Password->password($record{password});
 1306     }
 1307 
 1308     if (exists $allUserIDs{$user_id}) {
 1309       $db->putUser($User);
 1310       $db->putPermissionLevel($PermissionLevel);
 1311       $db->putPassword($Password);
 1312       push @replaced, $user_id;
 1313     } else {
 1314       $db->addUser($User);
 1315       $db->addPermissionLevel($PermissionLevel);
 1316       $db->addPassword($Password);
 1317       push @added, $user_id;
 1318     }
 1319   }
 1320 
 1321   return \@replaced, \@added, \@skipped;
 1322 }
 1323 
 1324 sub exportUsersToCSV {
 1325   my ($self, $fileName, @userIDsToExport) = @_;
 1326   my $r       = $self->r;
 1327   my $ce      = $r->ce;
 1328   my $db      = $r->db;
 1329   my $dir     = $ce->{courseDirs}->{templates};
 1330 
 1331   die "illegal character in input: '/'" if $fileName =~ m|/|;
 1332 
 1333   my @records;
 1334 
 1335   my @Users = $db->getUsers(@userIDsToExport);
 1336   my @Passwords = $db->getPasswords(@userIDsToExport);
 1337   my @PermissionLevels = $db->getPermissionLevels(@userIDsToExport);
 1338   foreach my $i (0 .. $#userIDsToExport) {
 1339     my $User = $Users[$i];
 1340     my $Password = $Passwords[$i];
 1341     my $PermissionLevel = $PermissionLevels[$i];
 1342     next unless defined $User;
 1343     my %record = (
 1344       defined $PermissionLevel ? $PermissionLevel->toHash : (),
 1345       defined $Password ? $Password->toHash : (),
 1346       $User->toHash,
 1347     );
 1348     push @records, \%record;
 1349   }
 1350 
 1351   write_classlist("$dir/$fileName", @records);
 1352 }
 1353 
 1354 ################################################################################
 1355 # "display" methods
 1356 ################################################################################
 1357 
 1358 sub fieldEditHTML {
 1359   my ($self, $fieldName, $value, $properties) = @_;
 1360   my $size = $properties->{size};
 1361   my $type = $properties->{type};
 1362   my $access = $properties->{access};
 1363   my $items = $properties->{items};
 1364   my $synonyms = $properties->{synonyms};
 1365 
 1366   if ($access eq "readonly") {
 1367     return $value;
 1368   }
 1369 
 1370   if ($type eq "number" or $type eq "text") {
 1371     return CGI::input({type=>"text", name=>$fieldName, value=>$value, size=>$size});
 1372   }
 1373 
 1374   if ($type eq "enumerable") {
 1375     my $matched = undef; # Whether a synonym match has occurred
 1376 
 1377     # Process synonyms for enumerable objects
 1378     foreach my $synonym (keys %$synonyms) {
 1379       if ($synonym ne "*" and $value =~ m/$synonym/) {
 1380         $value = $synonyms->{$synonym};
 1381         $matched = 1;
 1382       }
 1383     }
 1384 
 1385     if (!$matched and exists $synonyms->{"*"}) {
 1386       $value = $synonyms->{"*"};
 1387     }
 1388 
 1389     return CGI::popup_menu({
 1390       name => $fieldName,
 1391       values => [keys %$items],
 1392       default => $value,
 1393       labels => $items,
 1394     });
 1395   }
 1396 }
 1397 
 1398 sub recordEditHTML {
 1399   my ($self, $User, $PermissionLevel, %options) = @_;
 1400   my $r           = $self->r;
 1401   my $urlpath     = $r->urlpath;
 1402   my $db          = $r->db;
 1403   my $ce          = $r->ce;
 1404   my $authz = $r->authz;
 1405   my $user  = $r->param('user');
 1406   my $root        = $ce->{webworkURLs}->{root};
 1407   my $courseName  = $urlpath->arg("courseID");
 1408 
 1409   my $editMode = $options{editMode};
 1410   my $passwordMode = $options{passwordMode};
 1411   my $userSelected = $options{userSelected};
 1412 
 1413   my $statusClass = $ce->{siteDefaults}->{status}->{$User->{status}};
 1414 
 1415   my $sets = $db->countUserSets($User->user_id);
 1416   my $totalSets = $self->{totalSets};
 1417 
 1418   my $changeEUserURL = $self->systemLink($urlpath->new(type=>'set_list',args=>{courseID=>$courseName}),
 1419                        params => {effectiveUser => $User->user_id}
 1420   );
 1421 
 1422   my $setsAssignedToUserURL = $self->systemLink($urlpath->new(type=>'instructor_sets_assigned_to_user',
 1423                                                               args=>{courseID => $courseName,
 1424                                                                      userID   => $User->user_id
 1425                                                                      }),
 1426                        params => {effectiveUser => $User->user_id}
 1427   );
 1428 
 1429   my $userListURL = $self->systemLink($urlpath->new(type=>'instructor_user_list', args=>{courseID => $courseName} )) . "&editMode=1&visible_users=" . $User->user_id;
 1430 
 1431   my $imageURL = $ce->{webworkURLs}->{htdocs}."/images/edit.gif";
 1432         my $imageLink = CGI::a({href => $userListURL}, CGI::img({src=>$imageURL, border=>0}));
 1433 
 1434   my @tableCells;
 1435 
 1436   # Select
 1437   if ($editMode or $passwordMode) {
 1438     # column not there
 1439   } else {
 1440     # selection checkbox
 1441     push @tableCells, CGI::checkbox(
 1442       -name => "selected_users",
 1443       -value => $User->user_id,
 1444       -checked => $userSelected,
 1445       -label => "",
 1446     );
 1447   }
 1448 
 1449   # Act As
 1450   if ($editMode or $passwordMode) {
 1451     # column not there
 1452   } else {
 1453     # selection checkbox
 1454     if ( FIELD_PERMS()->{act_as} and not $authz->hasPermissions($user, FIELD_PERMS()->{act_as}) ){
 1455       push @tableCells, $User->user_id . $imageLink;
 1456     } else {
 1457       push @tableCells, CGI::a({href=>$changeEUserURL}, $User->user_id) . $imageLink;
 1458     }
 1459   }
 1460 
 1461   # Login Status
 1462   if ($editMode or $passwordMode) {
 1463     # column not there
 1464   } else {
 1465     # check to see if a user is currently logged in
 1466     my $Key = $db->getKey($User->user_id);
 1467     push @tableCells, ($Key and WeBWorK::Authen::checkKey($self, $User->user_id, $Key->key)) ? CGI::b("active") : CGI::em("inactive");
 1468   }
 1469 
 1470   # change password (only in password mode)
 1471   if ($passwordMode) {
 1472     if ($User->user_id eq $user) {
 1473       push @tableCells, ''   # don't allow a professor to change their own password from this form
 1474     }
 1475     else {
 1476       my $fieldName = 'user.' . $User->user_id . '.' . 'new_password';
 1477       push @tableCells, CGI::input({type=>"text", name=>$fieldName, size=>14});;
 1478     }
 1479   }
 1480   # User ID (edit mode) or Assigned Sets (otherwise)
 1481   if ($editMode  or $passwordMode) {
 1482     # straight user ID
 1483     push @tableCells, CGI::div({class=>$statusClass}, $User->user_id);
 1484   } else {
 1485     # "edit sets assigned to user" link
 1486     #push @tableCells, CGI::a({href=>$setsAssignedToUserURL}, "Edit sets");
 1487     if ( FIELD_PERMS()->{sets} and not $authz->hasPermissions($user, FIELD_PERMS()->{sets}) ) {
 1488       push @tableCells, "$sets/$totalSets";
 1489     } else {
 1490       push @tableCells, CGI::a({href=>$setsAssignedToUserURL}, "$sets/$totalSets");
 1491     }
 1492   }
 1493 
 1494   # User Fields
 1495   foreach my $field ($User->NONKEYFIELDS) {
 1496     my $fieldName = "user." . $User->user_id . "." . $field,
 1497     my $fieldValue = $User->$field;
 1498     my %properties = %{ FIELD_PROPERTIES()->{$field} };
 1499     $properties{access} = "readonly" unless $editMode;
 1500     $fieldValue = $self->nbsp($fieldValue) unless $editMode;
 1501     push @tableCells, CGI::div({class=>$statusClass}, $self->fieldEditHTML($fieldName, $fieldValue, \%properties));
 1502   }
 1503 
 1504   # PermissionLevel Fields
 1505   foreach my $field ($PermissionLevel->NONKEYFIELDS) {
 1506     my $fieldName = "permission." . $PermissionLevel->user_id . "." . $field,
 1507     my $fieldValue = $PermissionLevel->$field;
 1508     my %properties = %{ FIELD_PROPERTIES()->{$field} };
 1509     $properties{access} = "readonly" unless $editMode;
 1510     $fieldValue = $self->nbsp($fieldValue) unless $editMode;
 1511     push @tableCells, CGI::div({class=>$statusClass}, $self->fieldEditHTML($fieldName, $fieldValue, \%properties));
 1512   }
 1513 
 1514   return CGI::Tr({}, CGI::td({nowrap=>1}, \@tableCells));
 1515 }
 1516 
 1517 sub printTableHTML {
 1518   my ($self, $UsersRef, $PermissionLevelsRef, $fieldNamesRef, %options) = @_;
 1519   my $r                       = $self->r;
 1520   my $userTemplate            = $self->{userTemplate};
 1521   my $permissionLevelTemplate = $self->{permissionLevelTemplate};
 1522   my @Users                   = @$UsersRef;
 1523   my @PermissionLevels        = @$PermissionLevelsRef;
 1524   my %fieldNames              = %$fieldNamesRef;
 1525 
 1526   my $editMode                = $options{editMode};
 1527   my $passwordMode            = $options{passwordMode};
 1528   my %selectedUserIDs         = map { $_ => 1 } @{ $options{selectedUserIDs} };
 1529   my $currentSort             = $options{currentSort};
 1530 
 1531   # names of headings:
 1532   my @realFieldNames = (
 1533       $userTemplate->KEYFIELDS,
 1534       $userTemplate->NONKEYFIELDS,
 1535       $permissionLevelTemplate->NONKEYFIELDS,
 1536   );
 1537 
 1538   my %sortSubs = %{ SORT_SUBS() };
 1539   #my @stateParams = @{ STATE_PARAMS() };
 1540   #my $hrefPrefix = $r->uri . "?" . $self->url_args(@stateParams); # $self->url_authen_args
 1541   my @tableHeadings;
 1542   foreach my $field (@realFieldNames) {
 1543     my $result = $fieldNames{$field};
 1544     push @tableHeadings, $result;
 1545   };
 1546 
 1547   # prepend selection checkbox? only if we're NOT editing!
 1548   unless($editMode or $passwordMode) {
 1549     shift @tableHeadings; # Remove user id
 1550     unshift @tableHeadings, "Select", "Act As", "Login Status", "Assigned Sets";
 1551         }
 1552   if($passwordMode) {
 1553     unshift @tableHeadings, "New Password";
 1554         }
 1555 
 1556   # print the table
 1557   if ($editMode or $passwordMode) {
 1558     print CGI::start_table({});
 1559   } else {
 1560     print CGI::start_table({-border=>1, -nowrap=>1});
 1561   }
 1562 
 1563   print CGI::Tr({}, CGI::th({}, \@tableHeadings));
 1564 
 1565 
 1566   for (my $i = 0; $i < @Users; $i++) {
 1567     my $User = $Users[$i];
 1568     my $PermissionLevel = $PermissionLevels[$i];
 1569 
 1570     print $self->recordEditHTML($User, $PermissionLevel,
 1571       editMode => $editMode,
 1572       passwordMode => $passwordMode,
 1573       userSelected => exists $selectedUserIDs{$User->user_id}
 1574     );
 1575   }
 1576 
 1577   print CGI::end_table();
 1578     #########################################
 1579   # if there are no users shown print message
 1580   #
 1581   ##########################################
 1582 
 1583   print CGI::p(
 1584                 CGI::i("No students shown.  Choose one of the options above to
 1585                 list the students in the course.")
 1586   ) unless @Users;
 1587 }
 1588 
 1589 1;
 1590 

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9