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

View of /trunk/webwork2/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2109 - (download) (as text) (annotate)
Fri May 14 18:26:11 2004 UTC (9 years ago) by toenail
File size: 35415 byte(s)
added text classes for students with status Audit, Drop, Enrolled

    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.48 2004/05/11 20:13:22 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 Delete users:
   40   - visible
   41   - selected
   42 Import users:
   43   - replace:
   44     - any users
   45     - visible users
   46     - selected users
   47     - no users
   48   - add:
   49     - any users
   50     - no users
   51 Export users:
   52   - export:
   53     - all
   54     - visible
   55     - selected
   56   - to:
   57     - existing file on server (overwrite): [ list of files ]
   58     - new file on server (create): [ filename ]
   59 
   60 =cut
   61 
   62 use strict;
   63 use warnings;
   64 use CGI qw();
   65 use WeBWorK::Utils qw(readFile readDirectory);
   66 use Apache::Constants qw(:common REDIRECT DONE);  #FIXME  -- this should be called higher up in the object tree.
   67 use constant HIDE_USERS_THRESHHOLD => 20;
   68 use constant EDIT_FORMS => [qw(cancelEdit saveEdit)];
   69 use constant VIEW_FORMS => [qw(filter edit  import export add delete)];
   70 use constant STATE_PARAMS => [qw(user effectiveUser key visible_users no_visible_users prev_visible_users no_prev_visible_users editMode sortField)];
   71 
   72 use constant SORT_SUBS => {
   73   user_id       => \&byUserID,
   74   first_name    => \&byFirstName,
   75   last_name     => \&byLastName,
   76   email_address => \&byEmailAddress,
   77   student_id    => \&byStudentID,
   78   status        => \&byStatus,
   79   section       => \&bySection,
   80   recitation    => \&byRecitation,
   81   comment       => \&byComment,
   82 };
   83 
   84 use constant  FIELD_PROPERTIES => {
   85   user_id => {
   86     type => "text",
   87     size => 8,
   88     access => "readonly",
   89   },
   90   first_name => {
   91     type => "text",
   92     size => 10,
   93     access => "readwrite",
   94   },
   95   last_name => {
   96     type => "text",
   97     size => 10,
   98     access => "readwrite",
   99   },
  100   email_address => {
  101     type => "text",
  102     size => 20,
  103     access => "readwrite",
  104   },
  105   student_id => {
  106     type => "text",
  107     size => 11,
  108     access => "readwrite",
  109   },
  110   status => {
  111     type => "enumerable",
  112     size => 4,
  113     access => "readwrite",
  114     items => {
  115       "C" => "Enrolled",
  116       "D" => "Drop",
  117       "A" => "Audit",
  118     },
  119     synonyms => {
  120       qr/^[ce]/i => "C",
  121       qr/^[dw]/i => "D",
  122       qr/^a/i => "A",
  123       "*" => "C",
  124     }
  125   },
  126   section => {
  127     type => "text",
  128     size => 4,
  129     access => "readwrite",
  130   },
  131   recitation => {
  132     type => "text",
  133     size => 4,
  134     access => "readwrite",
  135   },
  136   comment => {
  137     type => "text",
  138     size => 20,
  139     access => "readwrite",
  140   },
  141   permission => {
  142     type => "number",
  143     size => 2,
  144     access => "readwrite",
  145   }
  146 };
  147 sub pre_header_initialize {
  148   my $self          = shift;
  149   my $r             = $self->r;
  150   my $urlpath       = $r->urlpath;
  151   my $ce            = $r->ce;
  152   my $courseName    = $urlpath->arg("courseID");
  153   # Handle redirects, if any.
  154   ##############################
  155   # Redirect to the addUser page
  156   ##################################
  157 
  158   defined($r->param('action')) && $r->param('action') eq 'add' && do {
  159     # fix url and redirect
  160     my $root              = $ce->{webworkURLs}->{root};
  161 
  162     my $numberOfStudents  = $r->param('number_of_students');
  163     warn "number of students not defined " unless defined $numberOfStudents;
  164 
  165     my $uri=$self->systemLink( $urlpath->newFromModule('WeBWorK::ContentGenerator::Instructor::AddUsers',courseID=>$courseName),
  166                                params=>{
  167                                     number_of_students=>$numberOfStudents,
  168                                        }
  169     );
  170     #FIXME  does the display mode need to be defined?
  171     #FIXME  url_authen_args also includes an effective user, so the new one must come first.
  172     # even that might not work with every browser since there are two effective User assignments.
  173     $r->header_out(Location => $uri);
  174     $self->{noContent} =  1;  # forces redirect
  175     return;
  176   };
  177 }
  178 # FIXME  -- this should be moved up to instructor or contentgenerator
  179 sub header {
  180   my $self = shift;
  181   return REDIRECT if $self->{noContent};
  182   my $r    = $self->r;
  183   $r->content_type('text/html');
  184   $r->send_http_header();
  185   return OK;
  186 }
  187 
  188 #FIXME -- this should probably be moved up to instructor or contentgenerator as well
  189 #sub nbsp {
  190 # my $str = shift;
  191 #        ($str =~/\S/) ? $str : ' '  ;  # returns non-breaking space for empty strings
  192 #                                            # tricky cases:   $str =0;
  193 #                                            #  $str is a complex number
  194 #}
  195 # moved to ContentGenerator.pm
  196 
  197 sub initialize {
  198   my ($self) = @_;
  199   my $r      = $self->r;
  200   my $db     = $r->db;
  201   my $ce     = $r->ce;
  202   my $authz  = $r->authz;
  203   my $user   = $r->param('user');
  204 
  205   unless ($authz->hasPermissions($user, "modify_student_data")) {
  206     $self->addmessage(CGI::div({class=>"ResultsWithError"}, CGI::p("You are not authorized to modify student data")));
  207     return;
  208   }
  209 
  210   #if (defined($r->param('addStudent'))) {
  211   # my $newUser = $db->newUser;
  212   # my $newPermissionLevel = $db->newPermissionLevel;
  213   # my $newPassword = $db->newPassword;
  214   # $newUser->user_id($r->param('newUserID'));
  215   # $newPermissionLevel->user_id($r->param('newUserID'));
  216   # $newPassword->user_id($r->param('newUserID'));
  217   # $newUser->status('C');
  218   # $newPermissionLevel->permission(0);
  219   # $db->addUser($newUser);
  220   # $db->addPermissionLevel($newPermissionLevel);
  221   # $db->addPassword($newPassword);
  222   #}
  223 }
  224 
  225 
  226 
  227 sub body {
  228   my ($self)       = @_;
  229   my $r            = $self->r;
  230   my $urlpath      = $r->urlpath;
  231   my $db           = $r->db;
  232   my $ce           = $r->ce;
  233   my $authz        = $r->authz;
  234   my $courseName   = $urlpath->arg("courseID");
  235   my $setID        = $urlpath->arg("setID");
  236   my $user         = $r->param('user');
  237 
  238   my $root = $ce->{webworkURLs}->{root};
  239 
  240   # templates for getting field names
  241   my $userTemplate            = $self->{userTemplate}            = $db->newUser;
  242   my $permissionLevelTemplate = $self->{permissionLevelTemplate} = $db->newPermissionLevel;
  243 
  244   return CGI::em("You are not authorized to access the Instructor tools.")
  245     unless $authz->hasPermissions($user, "access_instructor_tools");
  246 
  247   # This table can be consulted when display-ready forms of field names are needed.
  248   my %prettyFieldNames = map { $_ => $_ }
  249     $userTemplate->FIELDS(),
  250     $permissionLevelTemplate->FIELDS();
  251 
  252   @prettyFieldNames{qw(
  253     user_id
  254     first_name
  255     last_name
  256     email_address
  257     student_id
  258     status
  259     section
  260     recitation
  261     comment
  262     permission
  263   )} = (
  264     "Assigned sets",
  265     "First Name",
  266     "Last Name",
  267     "E-mail",
  268     "Student ID",
  269     "Status",
  270     "Section",
  271     "Recitation",
  272     "Comment",
  273     "Perm. Level"
  274   );
  275 
  276   ########## set initial values for state fields
  277 
  278   my @allUserIDs = $db->listUsers;
  279   $self->{allUserIDs} = \@allUserIDs;
  280 
  281   if (defined $r->param("visible_users")) {
  282     $self->{visibleUserIDs} = [ $r->param("visible_users") ];
  283   } elsif (defined $r->param("no_visible_users")) {
  284     $self->{visibleUserIDs} = [];
  285   } else {
  286     if (@allUserIDs > HIDE_USERS_THRESHHOLD) {
  287       $self->{visibleUserIDs} = [];
  288     } else {
  289       $self->{visibleUserIDs} = [ @allUserIDs ];
  290     }
  291   }
  292 
  293   $self->{prevVisibleUserIDs} = $self->{visibleUserIDs};
  294 
  295   if (defined $r->param("selected_users")) {
  296     $self->{selectedUserIDs} = [ $r->param("selected_users") ];
  297   } else {
  298     $self->{selectedUserIDs} = [];
  299   }
  300 
  301   $self->{editMode} = $r->param("editMode") || 0;
  302 
  303   $self->{sortField} = $r->param("sortField") || "last_name";
  304 
  305   my @allUsers = $db->getUsers(@allUserIDs);
  306   my (%sections, %recitations);
  307   foreach my $User (@allUsers) {
  308     push @{$sections{defined $User->section ? $User->section : ""}}, $User->user_id;
  309     push @{$recitations{defined $User->recitation ? $User->recitation : ""}}, $User->user_id;
  310   }
  311   $self->{sections} = \%sections;
  312   $self->{recitations} = \%recitations;
  313 
  314   ########## call action handler
  315 
  316   my $actionID = $r->param("action");
  317   if ($actionID) {
  318     unless (grep { $_ eq $actionID } @{ VIEW_FORMS() }, @{ EDIT_FORMS() }) {
  319       die "Action $actionID not found";
  320     }
  321     my $actionHandler = "${actionID}_handler";
  322     my %genericParams;
  323     foreach my $param (qw(selected_users)) {
  324       $genericParams{$param} = [ $r->param($param) ];
  325     }
  326     my %actionParams = $self->getActionParams($actionID);
  327     my %tableParams = $self->getTableParams();
  328     print CGI::p(
  329         '<div style="color:green">',
  330       "Result of last action performed: ",
  331       CGI::i($self->$actionHandler(\%genericParams, \%actionParams, \%tableParams)),
  332       '</div>',
  333       CGI::hr()
  334 
  335     );
  336   }
  337 
  338   ########## retrieve possibly changed values for member fields
  339 
  340   #@allUserIDs = @{ $self->{allUserIDs} }; # do we need this one?
  341   my @visibleUserIDs = @{ $self->{visibleUserIDs} };
  342   my @prevVisibleUserIDs = @{ $self->{prevVisibleUserIDs} };
  343   my @selectedUserIDs = @{ $self->{selectedUserIDs} };
  344   my $editMode = $self->{editMode};
  345   my $sortField = $self->{sortField};
  346 
  347   #warn "visibleUserIDs=@visibleUserIDs\n";
  348   #warn "prevVisibleUserIDs=@prevVisibleUserIDs\n";
  349   #warn "selectedUserIDs=@selectedUserIDs\n";
  350   #warn "editMode=$editMode\n";
  351 
  352   ########## get required users
  353 
  354   my @Users = grep { defined $_ } @visibleUserIDs ? $db->getUsers(@visibleUserIDs) : ();
  355 
  356   # presort users
  357   my %sortSubs = %{ SORT_SUBS() };
  358   my $sortSub = $sortSubs{$sortField};
  359   #@Users = sort $sortSub @Users;
  360   @Users = sort byLnFnUid @Users;
  361 
  362   my @PermissionLevels;
  363 
  364   for (my $i = 0; $i < @Users; $i++) {
  365     my $User = $Users[$i];
  366     my $PermissionLevel = $db->getPermissionLevel($User->user_id); # checked
  367 
  368     unless ($PermissionLevel) {
  369       # uh oh! no permission level record found!
  370       warn "added missing permission level for user ", $User->user_id, "\n";
  371 
  372       # create a new permission level record
  373       $PermissionLevel = $db->newPermissionLevel;
  374       $PermissionLevel->user_id($User->user_id);
  375       $PermissionLevel->permission(0);
  376 
  377       # add it to the database
  378       $db->addPermissionLevel($PermissionLevel);
  379     }
  380 
  381     $PermissionLevels[$i] = $PermissionLevel;
  382   }
  383 
  384   ########## print beginning of form
  385 
  386   print CGI::start_form({method=>"post", action=>$self->systemLink($urlpath,authen=>0), name=>"userlist"});
  387   print $self->hidden_authen_fields();
  388 
  389   ########## print state data
  390 
  391   print "\n<!-- state data here -->\n";
  392 
  393   if (@visibleUserIDs) {
  394     print CGI::hidden(-name=>"visible_users", -value=>\@visibleUserIDs);
  395   } else {
  396     print CGI::hidden(-name=>"no_visible_users", -value=>"1");
  397   }
  398 
  399   if (@prevVisibleUserIDs) {
  400     print CGI::hidden(-name=>"prev_visible_users", -value=>\@prevVisibleUserIDs);
  401   } else {
  402     print CGI::hidden(-name=>"no_prev_visible_users", -value=>"1");
  403   }
  404 
  405   print CGI::hidden(-name=>"editMode", -value=>$editMode);
  406 
  407   print CGI::hidden(-name=>"sortField", -value=>$sortField);
  408 
  409   print "\n<!-- state data here -->\n";
  410 
  411   ########## print action forms
  412 
  413   print CGI::start_table({});
  414   print CGI::Tr({}, CGI::td({-colspan=>2}, "Select an action to perform:"));
  415 
  416   my @formsToShow;
  417   if ($editMode) {
  418     @formsToShow = @{ EDIT_FORMS() };
  419   } else {
  420     @formsToShow = @{ VIEW_FORMS() };
  421   }
  422 
  423   my $i = 0;
  424   foreach my $actionID (@formsToShow) {
  425     my $actionForm = "${actionID}_form";
  426     my $onChange = "document.userlist.action[$i].checked=true";
  427     my %actionParams = $self->getActionParams($actionID);
  428 
  429     print CGI::Tr({-valign=>"top"},
  430       CGI::td({}, CGI::input({-type=>"radio", -name=>"action", -value=>$actionID})),
  431       CGI::td({}, $self->$actionForm($onChange, %actionParams))
  432     );
  433 
  434     $i++;
  435   }
  436 
  437   print CGI::Tr({}, CGI::td({-colspan=>2, -align=>"center"},
  438     CGI::submit(-value=>"Take Action!"))
  439   );
  440   print CGI::end_table();
  441 
  442   ########## print table
  443 
  444   print CGI::p("Showing ", scalar @visibleUserIDs, " out of ", scalar @allUserIDs, " users.");
  445 
  446   $self->printTableHTML(\@Users, \@PermissionLevels, \%prettyFieldNames,
  447     editMode => $editMode,
  448     selectedUserIDs => \@selectedUserIDs,
  449   );
  450 
  451 
  452   ########## print end of form
  453 
  454   print CGI::end_form();
  455 
  456   return "";
  457 }
  458 
  459 ################################################################################
  460 # extract particular params and put them in a hash (values are ARRAYREFs!)
  461 ################################################################################
  462 
  463 sub getActionParams {
  464   my ($self, $actionID) = @_;
  465   my $r = $self->{r};
  466 
  467   my %actionParams;
  468   foreach my $param ($r->param) {
  469     next unless $param =~ m/^action\.$actionID\./;
  470     $actionParams{$param} = [ $r->param($param) ];
  471   }
  472   return %actionParams;
  473 }
  474 
  475 sub getTableParams {
  476   my ($self) = @_;
  477   my $r = $self->{r};
  478 
  479   my %tableParams;
  480   foreach my $param ($r->param) {
  481     next unless $param =~ m/^(?:user|permission)\./;
  482     $tableParams{$param} = [ $r->param($param) ];
  483   }
  484   return %tableParams;
  485 }
  486 
  487 ################################################################################
  488 # actions and action triggers
  489 ################################################################################
  490 
  491 # filter, edit, cancelEdit, and saveEdit should stay with the display module and
  492 # not be real "actions". that way, all actions are shown in view mode and no
  493 # actions are shown in edit mode.
  494 
  495 sub filter_form {
  496   my ($self, $onChange, %actionParams) = @_;
  497   #return CGI::table({}, CGI::Tr({-valign=>"top"},
  498   # CGI::td({},
  499   return join("",
  500       "Show ",
  501       CGI::popup_menu(
  502         -name => "action.filter.scope",
  503         -values => [qw(all none selected match_ids match_section match_recitation)],
  504         -default => $actionParams{"action.filter.scope"}->[0] || "match_ids",
  505         -labels => {
  506           all => "all users",
  507           none => "no users",
  508           selected => "users checked below",
  509           match_ids => "users with matching user IDs:",
  510           match_section => "users in selected section",
  511           match_recitation => "users in selected recitation",
  512         },
  513         -onchange => $onChange,
  514       ),
  515       " ",
  516       CGI::textfield(
  517         -name => "action.filter.user_ids",
  518         -value => $actionParams{"action.filter.user_ids"}->[0] || "",,
  519         -width => "50",
  520         -onchange => $onChange,
  521       ),
  522       " (separate multiple IDs with commas)",
  523       CGI::br(),
  524       "sections: ",
  525       CGI::popup_menu(
  526         -name => "action.filter.section",
  527         -values => [ keys %{ $self->{sections} } ],
  528         -default => $actionParams{"action.filter.section"}->[0] || "",
  529         -labels => { $self->menuLabels($self->{sections}) },
  530         -onchange => $onChange,
  531       ),
  532       " recitations: ",
  533       CGI::popup_menu(
  534         -name => "action.filter.recitation",
  535         -values => [ keys %{ $self->{recitations} } ],
  536         -default => $actionParams{"action.filter.recitation"}->[0] || "",
  537         -labels => { $self->menuLabels($self->{recitations}) },
  538         -onchange => $onChange,
  539       ),
  540   );
  541   # ),
  542   #));
  543 }
  544 
  545 # this action handler modifies the "visibleUserIDs" field based on the contents
  546 # of the "action.filter.scope" parameter and the "selected_users"
  547 sub filter_handler {
  548   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  549 
  550   my $result;
  551 
  552   my $scope = $actionParams->{"action.filter.scope"}->[0];
  553   if ($scope eq "all") {
  554     $result = "showing all users";
  555     $self->{visibleUserIDs} = $self->{allUserIDs};
  556   } elsif ($scope eq "none") {
  557     $result = "showing no users";
  558     $self->{visibleUserIDs} = [];
  559   } elsif ($scope eq "selected") {
  560     $result = "showing selected users";
  561     $self->{visibleUserIDs} = $genericParams->{selected_users}; # an arrayref
  562   } elsif ($scope eq "match_ids") {
  563     my @userIDs = split /\s*,\s*/, $actionParams->{"action.filter.user_ids"}->[0];
  564     $self->{visibleUserIDs} = \@userIDs;
  565   } elsif ($scope eq "match_section") {
  566     my $section = $actionParams->{"action.filter.section"}->[0];
  567     $self->{visibleUserIDs} = $self->{sections}->{$section}; # an arrayref
  568   } elsif ($scope eq "match_recitation") {
  569     my $recitation = $actionParams->{"action.filter.recitation"}->[0];
  570     $self->{visibleUserIDs} = $self->{recitations}->{$recitation}; # an arrayref
  571   }
  572 
  573   return $result;
  574 }
  575 
  576 sub edit_form {
  577   my ($self, $onChange, %actionParams) = @_;
  578   return join("",
  579     "Edit ",
  580     CGI::popup_menu(
  581       -name => "action.edit.scope",
  582       -values => [qw(all visible selected)],
  583       -default => $actionParams{"action.edit.scope"}->[0] || "selected",
  584       -labels => {
  585         all => "all users",
  586         visible => "visible users",
  587         selected => "selected users"
  588       },
  589       -onchange => $onChange,
  590     ),
  591   );
  592 }
  593 
  594 sub edit_handler {
  595   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  596 
  597   my $result;
  598 
  599   my $scope = $actionParams->{"action.edit.scope"}->[0];
  600   if ($scope eq "all") {
  601     $result = "editing all users";
  602     $self->{visibleUserIDs} = $self->{allUserIDs};
  603   } elsif ($scope eq "visible") {
  604     $result = "editing visible users";
  605     # leave visibleUserIDs alone
  606   } elsif ($scope eq "selected") {
  607     $result = "editing selected users";
  608     $self->{visibleUserIDs} = $genericParams->{selected_users}; # an arrayref
  609   }
  610   $self->{editMode} = 1;
  611 
  612   return $result;
  613 }
  614 
  615 sub delete_form {
  616   my ($self, $onChange, %actionParams) = @_;
  617   return join("",
  618       qq!\n<div style="background-color:red">!,
  619     "Delete ",
  620     CGI::popup_menu(
  621       -name => "action.delete.scope",
  622       -values => [qw(none visible selected)],
  623       -default => $actionParams{"action.delete.scope"}->[0] || "none",
  624       -labels => {
  625           none     => "no users.",
  626         #visible  => "visible users.",
  627         selected => "selected users."
  628       },
  629       -onchange => $onChange,
  630     ),
  631     CGI::em(" Deletion destroys all user-related data and is not undoable!"),
  632     "</div>\n",
  633   );
  634 }
  635 
  636 sub delete_handler {
  637   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  638   my $r         = $self->r;
  639   my $db        = $r->db;
  640   my $scope = $actionParams->{"action.delete.scope"}->[0];
  641 
  642   my @userIDsToDelete = ();
  643   #if ($scope eq "visible") {
  644   # @userIDsToDelete = @{ $self->{visibleUserIDs} };
  645   #} elsif ($scope eq "selected") {
  646   if ($scope eq "selected") {
  647     @userIDsToDelete = @{ $self->{selectedUserIDs} };
  648   }
  649 
  650   my %allUserIDs = map { $_ => 1 } @{ $self->{allUserIDs} };
  651   my %visibleUserIDs = map { $_ => 1 } @{ $self->{visibleUserIDs} };
  652   my %selectedUserIDs = map { $_ => 1 } @{ $self->{selectedUserIDs} };
  653 
  654   foreach my $userID (@userIDsToDelete) {
  655     delete $allUserIDs{$userID};
  656     delete $visibleUserIDs{$userID};
  657     delete $selectedUserIDs{$userID};
  658     $db->deleteUser($userID);
  659   }
  660 
  661   $self->{allUserIDs} = [ keys %allUserIDs ];
  662   $self->{visibleUserIDs} = [ keys %visibleUserIDs ];
  663   $self->{selectedUserIDs} = [ keys %selectedUserIDs ];
  664 
  665   my $num = @userIDsToDelete;
  666   return "deleted $num user" . ($num == 1 ? "" : "s");
  667 }
  668 sub add_form {
  669   my ($self, $onChange, %actionParams) = @_;
  670 
  671     return "Add ", CGI::input({name=>'number_of_students', value=>1,size => 3}), " student(s). ";
  672 }
  673 
  674 sub add_handler {
  675   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  676   # This action is redirected to the addUser.pm module using ../instructor/add_user/...
  677   return "Nothing done by add student handler";
  678 }
  679 sub import_form {
  680   my ($self, $onChange, %actionParams) = @_;
  681   return join(" ",
  682     "Import users from file",
  683     CGI::popup_menu(
  684       -name => "action.import.source",
  685       -values => [ "", $self->getCSVList() ],
  686       -default => $actionParams{"action.import.source"}->[0] || "",
  687       -onchange => $onChange,
  688     ),
  689     "replacing",
  690     CGI::popup_menu(
  691       -name => "action.import.replace",
  692       -values => [qw(any visible selected none)],
  693       -default => $actionParams{"action.import.replace"}->[0] || "none",
  694       -labels => {
  695         any => "any",
  696         visible => "visible",
  697         selected => "selected",
  698         none => "no",
  699       },
  700       -onchange => $onChange,
  701     ),
  702     "existing users and adding",
  703     CGI::popup_menu(
  704       -name => "action.import.add",
  705       -values => [qw(any none)],
  706       -default => $actionParams{"action.import.add"}->[0] || "any",
  707       -labels => {
  708         any => "any",
  709         none => "no",
  710       },
  711       -onchange => $onChange,
  712     ),
  713     "new users",
  714   );
  715 }
  716 
  717 sub import_handler {
  718   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  719 
  720   my $source = $actionParams->{"action.import.source"}->[0];
  721   my $add = $actionParams->{"action.import.add"}->[0];
  722   my $replace = $actionParams->{"action.import.replace"}->[0];
  723 
  724   my $fileName = $source;
  725   my $createNew = $add eq "any";
  726   my $replaceExisting;
  727   my @replaceList;
  728   if ($replace eq "any") {
  729     $replaceExisting = "any";
  730   } elsif ($replace eq "none") {
  731     $replaceExisting = "none";
  732   } elsif ($replace eq "visible") {
  733     $replaceExisting = "listed";
  734     @replaceList = @{ $self->{visibleUserIDs} };
  735   } elsif ($replace eq "selected") {
  736     $replaceExisting = "listed";
  737     @replaceList = @{ $self->{selectedUserIDs} };
  738   }
  739 
  740   my ($replaced, $added, $skipped)
  741     = $self->importUsersFromCSV($fileName, $createNew, $replaceExisting, @replaceList);
  742 
  743   # make new users visible... do we really want to do this? probably.
  744   push @{ $self->{visibleUserIDs} }, @$added;
  745 
  746   my $numReplaced = @$replaced;
  747   my $numAdded = @$added;
  748   my $numSkipped = @$skipped;
  749 
  750   return $numReplaced . " user" . ($numReplaced == 1 ? "" : "s") . " replaced, "
  751     . $numAdded . " user" . ($numAdded == 1 ? "" : "s") . " added, "
  752     . $numSkipped . " user" . ($numSkipped == 1 ? "" : "s") . " skipped.";
  753 }
  754 
  755 sub export_form {
  756   my ($self, $onChange, %actionParams) = @_;
  757   return join("",
  758     "Export ",
  759     CGI::popup_menu(
  760       -name => "action.export.scope",
  761       -values => [qw(all visible selected)],
  762       -default => $actionParams{"action.export.scope"}->[0] || "visible",
  763       -labels => {
  764         all => "all users",
  765         visible => "visible users",
  766         selected => "selected users"
  767       },
  768       -onchange => $onChange,
  769     ),
  770     " to ",
  771     CGI::popup_menu(
  772       -name=>"action.export.target",
  773       -values => [ "new", $self->getCSVList() ],
  774       -labels => { new => "a new file named:" },
  775       -default => $actionParams{"action.export.target"}->[0] || "",
  776       -onchange => $onChange,
  777     ),
  778     #CGI::br(),
  779     #"new file to create: ",
  780     CGI::textfield(
  781       -name => "action.export.new",
  782       -value => $actionParams{"action.export.new"}->[0] || "",,
  783       -width => "50",
  784       -onchange => $onChange,
  785     ),
  786     CGI::tt(".lst"),
  787   );
  788 }
  789 
  790 sub export_handler {
  791   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  792 
  793   my $scope = $actionParams->{"action.export.scope"}->[0];
  794   my $target = $actionParams->{"action.export.target"}->[0];
  795   my $new = $actionParams->{"action.export.new"}->[0];
  796 
  797   my $fileName;
  798   if ($target eq "new") {
  799     $fileName = $new;
  800   } else {
  801     $fileName = $target;
  802   }
  803 
  804   $fileName .= ".lst" unless $fileName =~ m/\.lst$/;
  805 
  806   my @userIDsToExport;
  807   if ($scope eq "all") {
  808     @userIDsToExport = @{ $self->{allUserIDs} };
  809   } elsif ($scope eq "visible") {
  810     @userIDsToExport = @{ $self->{visibleUserIDs} };
  811   } elsif ($scope eq "selected") {
  812     @userIDsToExport = @{ $self->{selectedUserIDs} };
  813   }
  814 
  815   $self->exportUsersToCSV($fileName, @userIDsToExport);
  816 
  817   return scalar @userIDsToExport . " users exported";
  818 }
  819 
  820 sub cancelEdit_form {
  821   my ($self, $onChange, %actionParams) = @_;
  822   return "Abandon changes";
  823 }
  824 
  825 sub cancelEdit_handler {
  826   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  827   my $r      = $self->r;
  828 
  829   #$self->{selectedUserIDs} = $self->{visibleUserIDs};
  830     # only do the above if we arrived here via "edit selected users"
  831   if (defined $r->param("prev_visible_users")) {
  832     $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ];
  833   } elsif (defined $r->param("no_prev_visible_users")) {
  834     $self->{visibleUserIDs} = [];
  835   } else {
  836     # leave it alone
  837   }
  838   $self->{editMode} = 0;
  839 
  840   return "changes abandoned";
  841 }
  842 
  843 sub saveEdit_form {
  844   my ($self, $onChange, %actionParams) = @_;
  845   return "Save changes";
  846 }
  847 
  848 sub saveEdit_handler {
  849   my ($self, $genericParams, $actionParams, $tableParams) = @_;
  850   my $r           = $self->r;
  851   my $db          = $r->db;
  852 
  853   my @visibleUserIDs = @{ $self->{visibleUserIDs} };
  854   foreach my $userID (@visibleUserIDs) {
  855     my $User = $db->getUser($userID); # checked
  856     die "record for visible user $userID not found" unless $User;
  857     my $PermissionLevel = $db->getPermissionLevel($userID); # checked
  858     die "permissions for $userID not defined" unless defined $PermissionLevel;
  859     foreach my $field ($User->NONKEYFIELDS()) {
  860       my $param = "user.${userID}.${field}";
  861       if (defined $tableParams->{$param}->[0]) {
  862         $User->$field($tableParams->{$param}->[0]);
  863       }
  864     }
  865 
  866     foreach my $field ($PermissionLevel->NONKEYFIELDS()) {
  867       my $param = "permission.${userID}.${field}";
  868       if (defined $tableParams->{$param}->[0]) {
  869         $PermissionLevel->$field($tableParams->{$param}->[0]);
  870       }
  871     }
  872 
  873     $db->putUser($User);
  874     $db->putPermissionLevel($PermissionLevel);
  875   }
  876 
  877   if (defined $r->param("prev_visible_users")) {
  878     $self->{visibleUserIDs} = [ $r->param("prev_visible_users") ];
  879   } elsif (defined $r->param("no_prev_visible_users")) {
  880     $self->{visibleUserIDs} = [];
  881   } else {
  882     # leave it alone
  883   }
  884 
  885   $self->{editMode} = 0;
  886 
  887   return "changes saved";
  888 }
  889 
  890 ################################################################################
  891 # sorts
  892 ################################################################################
  893 
  894 sub byUserID       { $a->user_id       cmp $b->user_id       }
  895 sub byFirstName    { $a->first_name    cmp $b->first_name    }
  896 sub byLastName     { $a->last_name     cmp $b->last_name     }
  897 sub byEmailAddress { $a->email_address cmp $b->email_address }
  898 sub byStudentID    { $a->student_id    cmp $b->student_id    }
  899 sub byStatus       { $a->status        cmp $b->status        }
  900 sub bySection      { $a->section       cmp $b->section       }
  901 sub byRecitation   { $a->recitation    cmp $b->recitation    }
  902 sub byComment      { $a->comment       cmp $b->comment       }
  903 
  904 sub byLnFnUid { &byLastName || &byFirstName || &byUserID }
  905 
  906 ################################################################################
  907 # utilities
  908 ################################################################################
  909 
  910 # generate labels for section/recitation popup menus
  911 sub menuLabels {
  912   my ($self, $hashRef) = @_;
  913   my %hash = %$hashRef;
  914 
  915   my %result;
  916   foreach my $key (keys %hash) {
  917     my $count = @{ $hash{$key} };
  918     my $displayKey = $key || "<none>";
  919     $result{$key} = "$displayKey ($count users)";
  920   }
  921   return %result;
  922 }
  923 
  924 sub importUsersFromCSV {
  925   my ($self, $fileName, $createNew, $replaceExisting, @replaceList) = @_;
  926   my $r     = $self->r;
  927   my $ce    = $r->ce;
  928   my $db    = $r->db;
  929   my $dir   = $ce->{courseDirs}->{templates};
  930 
  931   die "illegal character in input: \"/\"" if $fileName =~ m|/|;
  932   die "won't be able to read from file $dir/$fileName: does it exist? is it readable?"
  933     unless -r "$dir/$fileName";
  934 
  935   my %allUserIDs = map { $_ => 1 } @{ $self->{allUserIDs} };
  936   my %replaceOK;
  937   if ($replaceExisting eq "none") {
  938     %replaceOK = ();
  939   } elsif ($replaceExisting eq "listed") {
  940     %replaceOK = map { $_ => 1 } @replaceList;
  941   } elsif ($replaceExisting eq "any") {
  942     %replaceOK = %allUserIDs;
  943   }
  944 
  945   my (@replaced, @added, @skipped);
  946 
  947   my @contents = split /\n/, readFile("$dir/$fileName");
  948   foreach my $string (@contents) {
  949     $string =~ s/^\s+//;
  950     $string =~ s/\s+$//;
  951     my (
  952       $student_id, $last_name, $first_name, $status, $comment,
  953       $section, $recitation, $email_address, $user_id
  954     ) = split /\s*,\s*/, $string;
  955 
  956     if (exists $allUserIDs{$user_id} and not exists $replaceOK{$user_id}) {
  957       push @skipped, $user_id;
  958       next;
  959     }
  960 
  961     if (not exists $allUserIDs{$user_id} and not $createNew) {
  962       push @skipped, $user_id;
  963       next;
  964     }
  965 
  966     my $User = $db->newUser;
  967     $User->user_id($user_id);
  968     $User->first_name($first_name);
  969     $User->last_name($last_name);
  970     $User->email_address($email_address);
  971     $User->student_id($student_id);
  972     $User->status($status);
  973     $User->section($section);
  974     $User->recitation($recitation);
  975     $User->comment($comment);
  976 
  977     my $PermissionLevel = $db->newPermissionLevel;
  978     $PermissionLevel->user_id($user_id);
  979     $PermissionLevel->permission(0);
  980 
  981     my $Password = $db->newPassword;
  982     $Password->user_id($user_id);
  983     $Password->password(cryptPassword($student_id));
  984 
  985     if (exists $allUserIDs{$user_id}) {
  986       $db->putUser($User);
  987       $db->putPermissionLevel($PermissionLevel);
  988       $db->putPassword($Password);
  989       push @replaced, $user_id;
  990     } else {
  991       $db->addUser($User);
  992       $db->addPermissionLevel($PermissionLevel);
  993       $db->addPassword($Password);
  994       push @added, $user_id;
  995     }
  996   }
  997 
  998   return \@replaced, \@added, \@skipped;
  999 }
 1000 
 1001 sub exportUsersToCSV {
 1002   my ($self, $fileName, @userIDsToExport) = @_;
 1003   my $r       = $self->r;
 1004   my $ce      = $r->ce;
 1005   my $db      = $r->db;
 1006   my $dir     = $ce->{courseDirs}->{templates};
 1007 
 1008   die "illegal character in input: \"/\"" if $fileName =~ m|/|;
 1009 
 1010   open my $fh, ">", "$dir/$fileName"
 1011     or die "failed to open file $dir/$fileName for writing: $!\n";
 1012 
 1013   foreach my $userID (@userIDsToExport) {
 1014     my $User = $db->getUser($userID); # checked
 1015     die "record for user $userID not found." unless $User;
 1016     my @fields = (
 1017       $User->student_id,
 1018       $User->last_name,
 1019       $User->first_name,
 1020       $User->status,
 1021       $User->comment,
 1022       $User->section,
 1023       $User->recitation,
 1024       $User->email_address,
 1025       $User->user_id,
 1026     );
 1027     my $string = join ",", @fields;
 1028     print $fh "$string\n";
 1029   }
 1030 
 1031   close $fh;
 1032 }
 1033 
 1034 ################################################################################
 1035 # "display" methods
 1036 ################################################################################
 1037 
 1038 sub fieldEditHTML {
 1039   my ($self, $fieldName, $value, $properties) = @_;
 1040   my $size = $properties->{size};
 1041   my $type = $properties->{type};
 1042   my $access = $properties->{access};
 1043   my $items = $properties->{items};
 1044   my $synonyms = $properties->{synonyms};
 1045 
 1046   if ($access eq "readonly") {
 1047     return $value;
 1048   }
 1049 
 1050   if ($type eq "number" or $type eq "text") {
 1051     return CGI::input({type=>"text", name=>$fieldName, value=>$value, size=>$size});
 1052   }
 1053 
 1054   if ($type eq "enumerable") {
 1055     my $matched = undef; # Whether a synonym match has occurred
 1056 
 1057     # Process synonyms for enumerable objects
 1058     foreach my $synonym (keys %$synonyms) {
 1059       if ($synonym ne "*" and $value =~ m/$synonym/) {
 1060         $value = $synonyms->{$synonym};
 1061         $matched = 1;
 1062       }
 1063     }
 1064 
 1065     if (!$matched and exists $synonyms->{"*"}) {
 1066       $value = $synonyms->{"*"};
 1067     }
 1068 
 1069     return CGI::popup_menu({
 1070       name => $fieldName,
 1071       values => [keys %$items],
 1072       default => $value,
 1073       labels => $items,
 1074     });
 1075   }
 1076 }
 1077 
 1078 sub recordEditHTML {
 1079   my ($self, $User, $PermissionLevel, %options) = @_;
 1080   my $r           = $self->r;
 1081   my $urlpath     = $r->urlpath;
 1082   my $ce          = $r->ce;
 1083   my $root        = $ce->{webworkURLs}->{root};
 1084   my $courseName  = $urlpath->arg("courseID");
 1085 
 1086   my $editMode = $options{editMode};
 1087   my $userSelected = $options{userSelected};
 1088 
 1089   my $statusClass = $ce->{siteDefaults}->{status}->{$User->{status}};
 1090 
 1091   my $changeEUserURL = $self->systemLink($urlpath->new(type=>'set_list',args=>{courseID=>$courseName}),
 1092                        params => {effectiveUser => $User->user_id}
 1093   );
 1094 
 1095   my $setsAssignedToUserURL = $self->systemLink($urlpath->new(type=>'instructor_sets_assigned_to_user',
 1096                                                               args=>{courseID => $courseName,
 1097                                                                      userID   => $User->user_id
 1098                                                                      }),
 1099                        params => {effectiveUser => $User->user_id}
 1100   );
 1101 
 1102   my @tableCells;
 1103 
 1104   # Select
 1105   if ($editMode) {
 1106     # column not there
 1107   } else {
 1108     # selection checkbox
 1109     push @tableCells, CGI::checkbox(
 1110       -name => "selected_users",
 1111       -value => $User->user_id,
 1112       -checked => $userSelected,
 1113       -label => "",
 1114     );
 1115   }
 1116 
 1117   # Act As
 1118   if ($editMode) {
 1119     # column not there
 1120   } else {
 1121     # selection checkbox
 1122     push @tableCells, CGI::a({href=>$changeEUserURL}, $User->user_id);
 1123   }
 1124 
 1125   # User ID
 1126   if ($editMode) {
 1127     # straight user ID
 1128     push @tableCells, CGI::div({class=>$statusClass}, $User->user_id);
 1129   } else {
 1130     # "edit sets assigned to user" link
 1131     push @tableCells, CGI::a({href=>$setsAssignedToUserURL}, "Edit sets");
 1132   }
 1133 
 1134   # User Fields
 1135   foreach my $field ($User->NONKEYFIELDS) {
 1136     my $fieldName = "user." . $User->user_id . "." . $field,
 1137     my $fieldValue = $User->$field;
 1138     my %properties = %{ FIELD_PROPERTIES()->{$field} };
 1139     $properties{access} = "readonly" unless $editMode;
 1140     $fieldValue = $self->nbsp($fieldValue) unless $editMode;
 1141     push @tableCells, CGI::div({class=>$statusClass}, $self->fieldEditHTML($fieldName, $fieldValue, \%properties));
 1142   }
 1143 
 1144   # PermissionLevel Fields
 1145   foreach my $field ($PermissionLevel->NONKEYFIELDS) {
 1146     my $fieldName = "permission." . $PermissionLevel->user_id . "." . $field,
 1147     my $fieldValue = $PermissionLevel->$field;
 1148     my %properties = %{ FIELD_PROPERTIES()->{$field} };
 1149     $properties{access} = "readonly" unless $editMode;
 1150     $fieldValue = $self->nbsp($fieldValue) unless $editMode;
 1151     push @tableCells, CGI::div({class=>$statusClass}, $self->fieldEditHTML($fieldName, $fieldValue, \%properties));
 1152   }
 1153 
 1154   return CGI::Tr({}, CGI::td({}, \@tableCells));
 1155 }
 1156 
 1157 sub printTableHTML {
 1158   my ($self, $UsersRef, $PermissionLevelsRef, $fieldNamesRef, %options) = @_;
 1159   my $r                       = $self->r;
 1160   my $userTemplate            = $self->{userTemplate};
 1161   my $permissionLevelTemplate = $self->{permissionLevelTemplate};
 1162   my @Users                   = @$UsersRef;
 1163   my @PermissionLevels        = @$PermissionLevelsRef;
 1164   my %fieldNames              = %$fieldNamesRef;
 1165 
 1166   my $editMode                = $options{editMode};
 1167   my %selectedUserIDs         = map { $_ => 1 } @{ $options{selectedUserIDs} };
 1168   my $currentSort             = $options{currentSort};
 1169 
 1170   # names of headings:
 1171   my @realFieldNames = (
 1172       $userTemplate->KEYFIELDS,
 1173       $userTemplate->NONKEYFIELDS,
 1174       $permissionLevelTemplate->NONKEYFIELDS,
 1175   );
 1176 
 1177   my %sortSubs = %{ SORT_SUBS() };
 1178   #my @stateParams = @{ STATE_PARAMS() };
 1179   #my $hrefPrefix = $r->uri . "?" . $self->url_args(@stateParams); # $self->url_authen_args
 1180   my @tableHeadings;
 1181   foreach my $field (@realFieldNames) {
 1182     my $result = $fieldNames{$field};
 1183     push @tableHeadings, $result;
 1184   };
 1185 
 1186   # prepend selection checkbox? only if we're NOT editing!
 1187   unshift @tableHeadings, "Select", "Act As" unless $editMode;
 1188 
 1189   # print the table
 1190   if ($editMode) {
 1191     print CGI::start_table({});
 1192   } else {
 1193     print CGI::start_table({-border=>1});
 1194   }
 1195 
 1196   print CGI::Tr({}, CGI::th({}, \@tableHeadings));
 1197 
 1198 
 1199   for (my $i = 0; $i < @Users; $i++) {
 1200     my $User = $Users[$i];
 1201     my $PermissionLevel = $PermissionLevels[$i];
 1202 
 1203     print $self->recordEditHTML($User, $PermissionLevel,
 1204       editMode => $editMode,
 1205       userSelected => exists $selectedUserIDs{$User->user_id}
 1206     );
 1207   }
 1208 
 1209   print CGI::end_table();
 1210     #########################################
 1211   # if there are no users shown print message
 1212   #
 1213   ##########################################
 1214 
 1215   print CGI::p(
 1216                 CGI::i("No students shown.  Choose one of the options above to
 1217                 list the students in the course.")
 1218   ) unless @Users;
 1219 }
 1220 
 1221 1;
 1222 

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9