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

View of /branches/rel-2-3-dev/webwork2/lib/WeBWorK/ContentGenerator/Instructor.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4631 - (download) (as text) (annotate)
Mon Nov 6 16:35:27 2006 UTC (6 years, 6 months ago) by sh002i
File size: 19058 byte(s)
backport (sh002i): standardize routines for reading scoring files, resolves bug #932.

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader$
    5 #
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of either: (a) the GNU General Public License as published by the
    8 # Free Software Foundation; either version 2, or (at your option) any later
    9 # version, or (b) the "Artistic License" which comes with this package.
   10 #
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   14 # Artistic License for more details.
   15 ################################################################################
   16 
   17 package WeBWorK::ContentGenerator::Instructor;
   18 use base qw(WeBWorK::ContentGenerator);
   19 
   20 =head1 NAME
   21 
   22 WeBWorK::ContentGenerator::Instructor - Abstract superclass for the Instructor
   23 tools, providing useful utility functions.
   24 
   25 =cut
   26 
   27 use strict;
   28 use warnings;
   29 #use CGI qw(-nosticky );
   30 use WeBWorK::CGI;
   31 use File::Find;
   32 use WeBWorK::DB::Utils qw(initializeUserProblem);
   33 use WeBWorK::Debug;
   34 use WeBWorK::Utils;
   35 
   36 =head1 METHODS
   37 
   38 =cut
   39 
   40 ################################################################################
   41 # Primary assignment methods
   42 ################################################################################
   43 
   44 =head2 Primary assignment methods
   45 
   46 =over
   47 
   48 =item assignSetToUser($userID, $GlobalSet)
   49 
   50 Assigns the given set and all problems contained therein to the given user. If
   51 the set (or any problems in the set) are already assigned to the user, a list of
   52 failure messages is returned.
   53 
   54 =cut
   55 
   56 sub assignSetToUser {
   57   my ($self, $userID, $GlobalSet) = @_;
   58   my $setID = $GlobalSet->set_id;
   59   my $db = $self->{db};
   60 
   61   my $UserSet = $db->newUserSet;
   62   $UserSet->user_id($userID);
   63   $UserSet->set_id($setID);
   64 
   65   my @results;
   66   my $set_assigned = 0;
   67 
   68   eval { $db->addUserSet($UserSet) };
   69   if ($@) {
   70     if ($@ =~ m/user set exists/) {
   71       push @results, "set $setID is already assigned to user $userID.";
   72       $set_assigned = 1;
   73     } else {
   74       die $@;
   75     }
   76   }
   77 
   78   my @GlobalProblems = grep { defined $_ } $db->getAllGlobalProblems($setID);
   79   foreach my $GlobalProblem (@GlobalProblems) {
   80     my @result = $self->assignProblemToUser($userID, $GlobalProblem);
   81     push @results, @result if @result and not $set_assigned;
   82   }
   83 
   84   return @results;
   85 }
   86 
   87 sub assignSetVersionToUser {
   88     my ( $self, $userID, $GlobalSet ) = @_;
   89 # in:  ($self,) $userID = the userID of the user to which to assign the set,
   90 #      $GlobalSet = the global set object.
   91 # out: a new set version is assigned to the user.
   92 # note: we assume that the global set and user are well defined.  I think this
   93 #    is a safe assumption.  it would be nice to just call assignSetToUser,
   94 #    but we run into trouble doing that because of the distinction between
   95 #    the setID and the setVersionID
   96 
   97     my $setID = $GlobalSet->set_id;
   98     my $db = $self->{db};
   99 
  100 # figure out what version we're on, reset setID, get a new user set
  101     my $setVersionNum = $db->getUserSetVersionNumber( $userID, $setID );
  102     $setVersionNum++;
  103     my $setVersionID = "$setID,v$setVersionNum";
  104     my $userSet = $db->newUserSet;
  105     $userSet->user_id( $userID );
  106     $userSet->set_id( $setVersionID );
  107 
  108     my @results = ();
  109     my $set_assigned = 0;
  110 
  111 # add the set to the database
  112     eval( $db->addVersionedUserSet( $userSet ) );
  113     if ( $@ ) {
  114   if ( $@ =~ m/user set exists/ ) {
  115       push( @results, "set $setVersionID is already assigned to user " .
  116       "$userID" );
  117       $set_assigned = 1;
  118   } else {
  119       die $@;
  120   }
  121     }
  122 
  123 # populate set with problems
  124     my @GlobalProblems = grep { defined $_ } $db->getAllGlobalProblems($setID);
  125 
  126 # keep track of problems assigned from groups so that we can have multiple
  127 #    problems from a given group, without duplicates
  128     my %groupProblems = ();
  129 
  130     foreach my $GlobalProblem ( @GlobalProblems ) {
  131   $GlobalProblem->set_id( $setVersionID );
  132 # this is getting called from within ContentGenerator, so that $self
  133 #    isn't an Instructor object---therefore, calling $self->assign...
  134 #    doesn't work.  the following is an ugly workaround that works b/c
  135 #    both Instructor and ContentGenerator objects have $self->{db}
  136 # FIXME  it would be nice to have a better solution to this
  137 # my @result =
  138 #     $self->assignProblemToUser( $userID, $GlobalProblem );
  139   my @result =
  140       assignProblemToUserSetVersion( $self, $userID, $userSet,
  141                        $GlobalProblem, \%groupProblems );
  142   push( @results, @result ) if ( @result && not $set_assigned );
  143     }
  144 
  145     return @results;
  146 }
  147 
  148 
  149 =item unassignSetFromUser($userID, $setID, $problemID)
  150 
  151 Unassigns the given set and all problems therein from the given user.
  152 
  153 =cut
  154 
  155 sub unassignSetFromUser {
  156   my ($self, $userID, $setID) = @_;
  157   my $db = $self->{db};
  158 
  159   $db->deleteUserSet($userID, $setID);
  160 }
  161 
  162 =item assignProblemToUser($userID, $GlobalProblem)
  163 
  164 Assigns the given problem to the given user. If the problem is already assigned
  165 to the user, an error string is returned.
  166 
  167 =cut
  168 
  169 sub assignProblemToUser {
  170   my ($self, $userID, $GlobalProblem) = @_;
  171   my $db = $self->{db};
  172 
  173   my $UserProblem = $db->newUserProblem;
  174   $UserProblem->user_id($userID);
  175   $UserProblem->set_id($GlobalProblem->set_id);
  176   $UserProblem->problem_id($GlobalProblem->problem_id);
  177   initializeUserProblem($UserProblem);
  178 
  179   eval { $db->addUserProblem($UserProblem) };
  180   if ($@) {
  181     if ($@ =~ m/user problem exists/) {
  182       return "problem " . $GlobalProblem->problem_id
  183         . " in set " . $GlobalProblem->set_id
  184         . " is already assigned to user $userID.";
  185     } else {
  186       die $@;
  187     }
  188   }
  189 
  190   return ();
  191 }
  192 
  193 sub assignProblemToUserSetVersion {
  194   my ($self, $userID, $userSet, $GlobalProblem, $groupProbRef) = @_;
  195   my $db = $self->{db};
  196 
  197 # conditional to allow selection of problems from a group of problems,
  198 # defined in a set.
  199 
  200     # problem groups are indicated by source files "group:problemGroupName"
  201   if ( $GlobalProblem->source_file() =~ /^group:(.+)$/ ) {
  202       my $problemGroupName = $1;
  203 
  204     # get list of problems in group
  205       my @problemList = $db->listGlobalProblems($problemGroupName);
  206         # sanity check: if the group set hasn't been defined or doesn't
  207         # actually contain problems (oops), then we can't very well assign
  208         # this problem to the user.  we could go on and assign all other
  209         # problems, but that results in a partial set.  so we die here if
  210         # this happens.  philosophically we're requiring that the instructor
  211         # set up the sets correctly or have to deal with the carnage after-
  212         # wards.  I'm not sure that this is the best long-term solution.
  213         # FIXME: this means that we may have created a set version that
  214         # doesn't have any problems.  this is bad.  but it's hard to see
  215         # where else to deal with it---fixing the problem requires checking
  216         # at the set version-creation level that all the problems in the
  217         # set are well defined.  FIXME
  218       die("Error in set version creation: no problems are available " .
  219     "in problem group $problemGroupName.  Set " .
  220     $userSet->set_id . " has been created for $userID, but " .
  221     "does not contain the right problems.\n") if (! @problemList);
  222 
  223       my $nProb = @problemList;
  224       my $whichProblem = int(rand($nProb));
  225 
  226     # we allow selection of multiple problems from a group, but want them to
  227     #   be different.  there's probably a better way to do this
  228       if ( defined( $groupProbRef->{$problemGroupName} ) &&
  229      $groupProbRef->{$problemGroupName} =~ /\b$whichProblem\b/ ) {
  230     my $nAvail = $nProb -
  231         ( $groupProbRef->{$problemGroupName} =~ tr/,// ) - 1;
  232 
  233     die("Too many problems selected from group.") if ( ! $nAvail );
  234 
  235     $whichProblem = int(rand($nProb));
  236     while ( $groupProbRef->{$problemGroupName} =~ /\b$whichProblem\b/ ) {
  237         $whichProblem = ( $whichProblem + 1 )%$nProb;
  238     }
  239       }
  240       if ( defined( $groupProbRef->{$problemGroupName} ) ) {
  241     $groupProbRef->{$problemGroupName} .= ",$whichProblem";
  242       } else {
  243     $groupProbRef->{$problemGroupName} = "$whichProblem";
  244       }
  245 
  246       my $prob = $db->getGlobalProblem($problemGroupName,
  247                $problemList[$whichProblem]);
  248       $GlobalProblem->source_file($prob->source_file());
  249   }
  250 
  251 # all set; do problem assignment
  252   my $UserProblem = $db->newUserProblem;
  253   $UserProblem->user_id($userID);
  254   $UserProblem->set_id($userSet->set_id);
  255   $UserProblem->problem_id($GlobalProblem->problem_id);
  256   $UserProblem->source_file($GlobalProblem->source_file);
  257   initializeUserProblem($UserProblem);
  258 
  259   eval { $db->addUserProblem($UserProblem) };
  260   if ($@) {
  261     if ($@ =~ m/user problem exists/) {
  262       return "problem " . $GlobalProblem->problem_id
  263         . " in set " . $GlobalProblem->set_id
  264         . " is already assigned to user $userID.";
  265     } else {
  266       die $@;
  267     }
  268   }
  269 
  270   return();
  271 }
  272 
  273 =item unassignProblemFromUser($userID, $setID, $problemID)
  274 
  275 Unassigns the given problem from the given user.
  276 
  277 =cut
  278 
  279 sub unassignProblemFromUser {
  280   my ($self, $userID, $setID, $problemID) = @_;
  281   my $db = $self->{db};
  282 
  283   $db->deleteUserProblem($userID, $setID, $problemID);
  284 }
  285 
  286 =back
  287 
  288 =cut
  289 
  290 ################################################################################
  291 # Secondary set assignment methods
  292 ################################################################################
  293 
  294 =head2 Secondary assignment methods
  295 
  296 =over
  297 
  298 =item assignSetToAllUsers($setID)
  299 
  300 Assigns the set specified and all problems contained therein to all users in
  301 the course. This is more efficient than repeatedly calling assignSetToUser().
  302 If any assignments fail, a list of failure messages is returned.
  303 
  304 =cut
  305 
  306 sub assignSetToAllUsers {
  307   my ($self, $setID) = @_;
  308   my $db = $self->{db};
  309   my @userIDs = $db->listUsers;
  310 
  311   debug("$setID: getting user list");
  312   my @userRecords = $db->getUsers(@userIDs);
  313   debug("$setID: (done with that)");
  314 
  315   debug("$setID: getting problem list");
  316   my @GlobalProblems = grep { defined $_ } $db->getAllGlobalProblems($setID);
  317   debug("$setID: (done with that)");
  318 
  319   my @results;
  320 
  321   foreach my $User (@userRecords) {
  322     next unless $self->r->ce->status_abbrev_has_behavior($User->status, "include_in_assignment");
  323     my $UserSet = $db->newUserSet;
  324     my $userID = $User->user_id;
  325     $UserSet->user_id($userID);
  326     $UserSet->set_id($setID);
  327     debug("$setID: adding UserSet for $userID");
  328     eval { $db->addUserSet($UserSet) };
  329     if ($@) {
  330       next if $@ =~ m/user set exists/;
  331       die $@;
  332     }
  333     debug("$setID: (done with that)");
  334 
  335     debug("$setID: adding UserProblems for $userID");
  336     foreach my $GlobalProblem (@GlobalProblems) {
  337       my @result = $self->assignProblemToUser($userID, $GlobalProblem);
  338       push @results, @result if @result;
  339     }
  340     debug("$setID: (done with that)");
  341   }
  342 
  343   return @results;
  344 }
  345 
  346 =item unassignSetFromAllUsers($setID)
  347 
  348 Unassigns the specified sets and all problems contained therein from all users.
  349 
  350 =cut
  351 
  352 sub unassignSetFromAllUsers {
  353   my ($self, $setID) = @_;
  354   my $db = $self->{db};
  355 
  356   my @userIDs = $db->listSetUsers($setID);
  357 
  358   foreach my $userID (@userIDs) {
  359     $self->unassignSetFromUser($userID, $setID);
  360   }
  361 }
  362 
  363 =item assignAllSetsToUser($userID)
  364 
  365 Assigns all sets in the course and all problems contained therein to the
  366 specified user. This is more efficient than repeatedly calling
  367 assignSetToUser(). If any assignments fail, a list of failure messages is
  368 returned.
  369 
  370 =cut
  371 
  372 sub assignAllSetsToUser {
  373   my ($self, $userID) = @_;
  374   my $db = $self->{db};
  375 
  376   # assign only sets that are not already assigned
  377   #my %userSetIDs = map { $_ => 1 } $db->listUserSets($userID);
  378   #my @globalSetIDs = grep { not exists $userSetIDs{$_} } $db->listGlobalSets;
  379   #my @GlobalSets = $db->getGlobalSets(@globalSetIDs);
  380   # FIXME: i don't think we need to do the above, since asignSetToUser fails
  381   # silently if a UserSet already exists. instead we do this:
  382   my @globalSetIDs = $db->listGlobalSets;
  383   my @GlobalSets = $db->getGlobalSets(@globalSetIDs);
  384 
  385   my @results;
  386 
  387   my $i = 0;
  388   foreach my $GlobalSet (@GlobalSets) {
  389     if (not defined $GlobalSet) {
  390       warn "record not found for global set $globalSetIDs[$i]";
  391     } else {
  392       my @result = $self->assignSetToUser($userID, $GlobalSet);
  393       push @results, @result if @result;
  394     }
  395     $i++;
  396   }
  397 
  398   return @results;
  399 }
  400 
  401 =item unassignAllSetsFromUser($userID)
  402 
  403 Unassigns all sets and all problems contained therein from the specified user.
  404 
  405 =cut
  406 
  407 sub unassignAllSetsFromUser {
  408   my ($self, $userID) = @_;
  409   my $db = $self->{db};
  410 
  411   my @setIDs = $db->listUserSets($userID);
  412 
  413   foreach my $setID (@setIDs) {
  414     $self->unassignSetFromUser($userID, $setID);
  415   }
  416 }
  417 
  418 =back
  419 
  420 =cut
  421 
  422 ################################################################################
  423 # Utility assignment methods
  424 ################################################################################
  425 
  426 =head2 Utility assignment methods
  427 
  428 =over
  429 
  430 =item assignSetsToUsers($setIDsRef, $userIDsRef)
  431 
  432 Assign each of the given sets to each of the given users. If any assignments
  433 fail, a list of failure messages is returned.
  434 
  435 =cut
  436 
  437 sub assignSetsToUsers {
  438   my ($self, $setIDsRef, $userIDsRef) = @_;
  439   my $db = $self->{db};
  440 
  441   my @setIDs = @$setIDsRef;
  442   my @userIDs = @$userIDsRef;
  443   my @GlobalSets = $db->getGlobalSets(@setIDs);
  444 
  445   my @results;
  446 
  447   foreach my $GlobalSet (@GlobalSets) {
  448     foreach my $userID (@userIDs) {
  449       my @result = $self->assignSetToUser($userID, $GlobalSet);
  450       push @results, @result if @result;
  451     }
  452   }
  453 
  454   return @results;
  455 }
  456 
  457 =item unassignSetsFromUsers($setIDsRef, $userIDsRef)
  458 
  459 Unassign each of the given sets from each of the given users.
  460 
  461 =cut
  462 
  463 sub unassignSetsFromUsers {
  464   my ($self, $setIDsRef, $userIDsRef) = @_;
  465   my @setIDs = @$setIDsRef;
  466   my @userIDs = @$userIDsRef;
  467 
  468   foreach my $setID (@setIDs) {
  469     foreach my $userID (@userIDs) {
  470       $self->unassignSetFromUser($userID, $setID);
  471     }
  472   }
  473 }
  474 
  475 =item assignProblemToAllSetUsers($GlobalProblem)
  476 
  477 Assigns the problem specified to all users to whom the problem's set is
  478 assigned. If any assignments fail, a list of failure messages is returned.
  479 
  480 =cut
  481 
  482 sub assignProblemToAllSetUsers {
  483   my ($self, $GlobalProblem) = @_;
  484   my $db = $self->{db};
  485   my $setID = $GlobalProblem->set_id;
  486   my @userIDs = $db->listSetUsers($setID);
  487 
  488   my @results;
  489 
  490   foreach my $userID (@userIDs) {
  491     my @result = $self->assignProblemToUser($userID, $GlobalProblem);
  492     push @results, @result if @result;
  493   }
  494 
  495   return @results;
  496 }
  497 
  498 =back
  499 
  500 =cut
  501 
  502 ################################################################################
  503 # Utility method for adding problems to a set
  504 ################################################################################
  505 
  506 =head2 Utility method for adding problems to a set
  507 
  508 =over
  509 
  510 =cut
  511 
  512 sub addProblemToSet {
  513   my ($self, %args) = @_;
  514   my $db = $self->r->db;
  515 
  516   die "addProblemToSet called without specifying the set name." if $args{setName} eq "";
  517   my $setName = $args{setName};
  518 
  519   my $sourceFile = $args{sourceFile} or
  520     die "addProblemToSet called without specifying the sourceFile.";
  521 
  522   # The rest of the arguments are optional
  523   my $value = $args{value} || 1;
  524   my $maxAttempts = $args{maxAttempts} || -1;
  525   my $problemID = $args{problemID};
  526 
  527   unless ($problemID) {
  528     $problemID = WeBWorK::Utils::max($db->listGlobalProblems($setName)) + 1;
  529   }
  530 
  531   my $problemRecord = $db->newGlobalProblem;
  532   $problemRecord->problem_id($problemID);
  533   $problemRecord->set_id($setName);
  534   $problemRecord->source_file($sourceFile);
  535   $problemRecord->value($value);
  536   $problemRecord->max_attempts($maxAttempts);
  537   $db->addGlobalProblem($problemRecord);
  538 
  539   return $problemRecord;
  540 }
  541 
  542 =back
  543 
  544 =cut
  545 
  546 ################################################################################
  547 # Utility methods
  548 ################################################################################
  549 
  550 =head2 Utility methods
  551 
  552 =over
  553 
  554 =cut
  555 
  556 sub hiddenEditForUserFields {
  557   my ($self, @editForUser) = @_;
  558   my $return = "";
  559   foreach my $editUser (@editForUser) {
  560     $return .= CGI::input({type=>"hidden", name=>"editForUser", value=>$editUser});
  561   }
  562 
  563   return $return;
  564 }
  565 
  566 sub userCountMessage {
  567   my ($self, $count, $numUsers) = @_;
  568 
  569   my $message;
  570   if ($count == 0) {
  571     $message = CGI::em("no students");
  572   } elsif ($count == $numUsers) {
  573     $message = "all students";
  574   } elsif ($count == 1) {
  575     $message = "1 student";
  576   } elsif ($count > $numUsers || $count < 0) {
  577     $message = CGI::em("an impossible number of users: $count out of $numUsers");
  578   } else {
  579     $message = "$count students out of $numUsers";
  580   }
  581 
  582   return $message;
  583 }
  584 
  585 sub setCountMessage {
  586   my ($self, $count, $numSets) = @_;
  587 
  588   my $message;
  589   if ($count == 0) {
  590     $message = CGI::em("no sets");
  591   } elsif ($count == $numSets) {
  592     $message = "all sets";
  593   } elsif ($count == 1) {
  594     $message = "1 set";
  595   } elsif ($count > $numSets || $count < 0) {
  596     $message = CGI::em("an impossible number of sets: $count out of $numSets");
  597   } else {
  598     $message = "$count sets";
  599   }
  600 
  601   return $message;
  602 }
  603 
  604 sub read_dir {  # read a directory
  605   my $self      = shift;
  606   my $directory = shift;
  607   my $pattern   = shift;
  608   my @files = grep /$pattern/, WeBWorK::Utils::readDirectory($directory);
  609   return sort @files;
  610 }
  611 
  612 =back
  613 
  614 =cut
  615 
  616 ################################################################################
  617 # Methods for listing various types of files
  618 ################################################################################
  619 
  620 =head2 Methods for listing various types of files
  621 
  622 =over
  623 
  624 =cut
  625 
  626 # list classlist files
  627 sub getCSVList {
  628   my ($self) = @_;
  629   my $ce = $self->{ce};
  630   my $dir = $ce->{courseDirs}->{templates};
  631   return grep { not m/^\./ and m/\.lst$/ and -f "$dir/$_" } WeBWorK::Utils::readDirectory($dir);
  632 }
  633 
  634 sub getDefList {
  635   my ($self) = @_;
  636   my $ce = $self->{ce};
  637   my $dir = $ce->{courseDirs}->{templates};
  638   return $self->read_dir($dir, qr/.*\.def/);
  639 }
  640 
  641 sub getScoringFileList {
  642   my ($self) = @_;
  643   my $ce = $self->{ce};
  644   my $dir = $ce->{courseDirs}->{scoring};
  645   return $self->read_dir($dir, qr/.*\.csv/);
  646 }
  647 
  648 sub getTemplateFileList {  # find all .pg files under the template tree (time consuming)
  649   my ($self) = shift;
  650   my $subDir = shift;
  651   my $ce = $self->{ce};
  652   $subDir = '' unless defined $subDir;
  653   my $dir = $ce->{courseDirs}->{templates}."/$subDir";
  654   # FIXME  currently allows one to see most files in the templates directory.
  655   # a better facility for handling auxiliary files would be nice.
  656   return $self->read_dir($dir, qr/\.pg$|.*\.html|\.png|\.gif|\.txt|\.pl/);
  657 }
  658 sub getTemplateDirList {  # find all .pg files under the template tree (time consuming)
  659   my ($self) = @_;
  660   my $ce = $self->{ce};
  661   my $dir = $ce->{courseDirs}->{templates};
  662   my @list = ();
  663   my $wanted = sub { if (-d $_ ) {
  664                           my $current = $_;
  665                           return if $current =~/CVS/;
  666                           return if -l $current;   # don't list links
  667                           my $name = $File::Find::name;
  668                           $name = " Top" if $current =/^\./; #  top directory
  669               $name =~ s/^$dir\///;
  670               push @list, $name
  671              }
  672   };
  673   File::Find::find($wanted, $dir);
  674   return sort @list;
  675 }
  676 
  677 =back
  678 
  679 =cut
  680 
  681 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9