[system] / branches / gage_dev / webwork2 / lib / WeBWorK / ContentGenerator / Grades.pm Repository:
ViewVC logotype

View of /branches/gage_dev/webwork2/lib/WeBWorK/ContentGenerator/Grades.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6943 - (download) (as text) (annotate)
Mon Jul 18 20:07:43 2011 UTC (23 months ago) by gage
File size: 21377 byte(s)
merging with localization files in trunk


    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Grades.pm,v 1.35 2007/07/10 14:41:54 glarose 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::Grades;
   18 use base qw(WeBWorK::ContentGenerator);
   19 
   20 =head1 NAME
   21 
   22 WeBWorK::ContentGenerator::Instructor::Stats - Display statistics by user or
   23 problem set.
   24 
   25 =cut
   26 
   27 use strict;
   28 use warnings;
   29 #use CGI qw(-nosticky );
   30 use WeBWorK::CGI;
   31 use WeBWorK::Debug;
   32 use WeBWorK::Utils qw(readDirectory list2hash max);
   33 use WeBWorK::Localize;
   34 sub initialize {
   35   my ($self) = @_;
   36   my $r = $self->r;
   37   my $db = $r->db;
   38   my $ce = $r->ce;
   39   my $authz = $r->authz;
   40 
   41   my $userName = $r->param('user');
   42   my $effectiveUserName = defined($r->param("effectiveUser") ) ? $r->param("effectiveUser") : $userName;
   43     $self->{userName} = $userName;
   44   $self->{studentName} = $effectiveUserName;
   45 }
   46 
   47 sub body {
   48   my ($self) = @_;
   49 
   50   $self->displayStudentStats($self->{studentName});
   51 
   52   print $self->scoring_info();
   53 
   54   return '';
   55 
   56 }
   57 
   58 ############################################
   59 # Borrowed from SendMail.pm and Instructor.pm
   60 ############################################
   61 
   62 sub getRecord {
   63   my $self    = shift;
   64   my $line    = shift;
   65   my $delimiter   = shift;
   66   $delimiter       = ',' unless defined($delimiter);
   67 
   68         #       Takes a delimited line as a parameter and returns an
   69         #       array.  Note that all white space is removed.  If the
   70         #       last field is empty, the last element of the returned
   71         #       array is also empty (unlike what the perl split command
   72         #       would return).  E.G. @lineArray=&getRecord(\$delimitedLine).
   73 
   74         my(@lineArray);
   75         $line.=$delimiter;                              # add 'A' to end of line so that
   76                                                         # last field is never empty
   77         @lineArray = split(/\s*${delimiter}\s*/,$line);
   78         $lineArray[0] =~s/^\s*// if defined($lineArray[0]);                       # remove white space from first element
   79         @lineArray;
   80 }
   81 
   82 sub scoring_info {
   83   my ($self) = @_;
   84   my $r = $self->r;
   85   my $db = $r->db;
   86   my $ce = $r->ce;
   87 
   88   my $userName          = $r->param('effectiveUser') || $r->param('user');
   89   my $userID              = $r->param('user');
   90     my $ur                = $db->getUser($userName);
   91   my $emailDirectory    = $ce->{courseDirs}->{email};
   92   my $filePath          = "$emailDirectory/report_grades.msg";
   93   my $merge_file         = "report_grades_data.csv";
   94   my $delimiter            = ',';
   95   my $scoringDirectory    = $ce->{courseDirs}->{scoring};
   96   return "There is no additional grade information. The spreadsheet file $filePath cannot be found." unless -e "$scoringDirectory/$merge_file";
   97   my $rh_merge_data   = $self->read_scoring_file("$merge_file", "$delimiter");
   98   my $text;
   99   my $header = '';
  100   local(*FILE);
  101   if (-e "$filePath" and -r "$filePath") {
  102     open FILE, "$filePath" || return("Can't open $filePath");
  103     while ($header !~ s/Message:\s*$//m and not eof(FILE)) {
  104       $header .= <FILE>;
  105     }
  106   } else {
  107     return("There is no additional grade information. <br> The message file $filePath cannot be found.")
  108   }
  109   $text = join( '', <FILE>);
  110   close(FILE);
  111 
  112   my $status_name = $ce->status_abbrev_to_name($ur->status);
  113   $status_name = $ur->status unless defined $status_name;
  114 
  115   my $SID           = $ur->student_id;
  116   my $FN            = $ur->first_name;
  117   my $LN            = $ur->last_name;
  118   my $SECTION       = $ur->section;
  119   my $RECITATION    = $ur->recitation;
  120   my $STATUS        = $status_name;
  121   my $EMAIL         = $ur->email_address;
  122   my $LOGIN         = $ur->user_id;
  123   my @COL           = defined($rh_merge_data->{$SID}) ? @{$rh_merge_data->{$SID} } : ();
  124   unshift(@COL,"");     ## this makes COL[1] the first column
  125 
  126   my $endCol        = @COL;
  127   # for safety, only evaluate special variables
  128   # FIXME /e is not required for simple variable interpolation
  129   my $msg = $text;
  130   $msg =~ s/(\$PAR)/<p>/ge;
  131   $msg =~ s/(\$BR)/<br>/ge;
  132 
  133   $msg =~ s/\$SID/$SID/ge;
  134   $msg =~ s/\$LN/$LN/ge;
  135   $msg =~ s/\$FN/$FN/ge;
  136   $msg =~ s/\$STATUS/$STATUS/ge;
  137   $msg =~ s/\$SECTION/$SECTION/ge;
  138   $msg =~ s/\$RECITATION/$RECITATION/ge;
  139   $msg =~ s/\$EMAIL/$EMAIL/ge;
  140   $msg =~ s/\$LOGIN/$LOGIN/ge;
  141   if (defined($COL[1])) {   # prevents extraneous error messages.
  142     $msg =~ s/\$COL\[(\-?\d+)\]/$COL[$1] if defined($COL[$1])/ge
  143   }
  144   else {            # prevents extraneous $COL's in email message
  145     $msg =~ s/\$COL\[(\-?\d+)\]//g
  146   }
  147 
  148 #   old version
  149 #   $msg =~ s/(\$SID)/eval($1)/ge;
  150 #   $msg =~ s/(\$LN)/eval($1)/ge;
  151 #   $msg =~ s/(\$FN)/eval($1)/ge;
  152 #   $msg =~ s/(\$STATUS)/eval($1)/ge;
  153 #   $msg =~ s/(\$SECTION)/eval($1)/ge;
  154 #   $msg =~ s/(\$RECITATION)/eval($1)/ge;
  155 #   $msg =~ s/(\$EMAIL)/eval($1)/ge;
  156 #   $msg =~ s/(\$LOGIN)/eval($1)/ge;
  157 #   $msg =~ s/\$COL\[ *-/\$COL\[$endCol-/g;
  158 #   $msg =~ s/(\$COL\[.*?\])/eval($1)/ge;
  159 
  160   $msg =~ s/\r//g;
  161   $msg = "<pre>$msg</pre>";
  162   $msg = qq!More scoring information goes here in [TMPL]/email/report_grades.msg. It
  163     is merged with the file [Scoring]/report_grades_data.csv. <br>These files can be edited
  164     using the "Email" link and the "Scoring Tools" link in the left margin.<p>!.$msg if ($r->authz->hasPermissions($userID, "access_instructor_tools"));
  165   return CGI::div(
  166     {style =>"background-color:#DDDDDD"}, $msg
  167   );
  168 }
  169 
  170 sub displayStudentStats {
  171   my ($self, $studentName) = @_;
  172   my $r = $self->r;
  173   my $db = $r->db;
  174   my $ce = $r->ce;
  175   my $authz = $r->authz;
  176 
  177   my $courseName = $ce->{courseName};
  178   my $studentRecord = $db->getUser($studentName); # checked
  179   die "record for user $studentName not found" unless $studentRecord;
  180   my $root = $ce->{webworkURLs}->{root};
  181 
  182 ######################################################################
  183 # Get all sets (including versions of gateway quizzes) assigned to this user
  184 ######################################################################
  185 
  186   # first get all non-versioned-sets; listUserSets will return all
  187   #    homework assignments, plus the template gateway sets.
  188   # DBFIXME use iterator instead of setIDs
  189   my @setIDs    = sort( $db->listUserSets($studentName) );
  190   # to figure out which of these are gateways (that is, versioned),
  191   #    we need to also have the actual (merged) set objects
  192   my @sets = $db->getMergedSets( map {[$studentName, $_]} @setIDs );
  193   # to be able to find the set objects later, make a handy hash
  194   my %setsByID = ( map {$_->set_id => $_} @sets );
  195 
  196 ######################################################
  197 # before going through the table generating loop, find all the
  198 #    set versions for the sets in our list
  199 #
  200 # info for refactoring:
  201 # input:  list of regular sets (from $db->getMergedSets(studentID, setID )
  202 # input:  $db
  203 # input: \%setsByID
  204 # output: \%setVersionsByID  ---  a pointer to a list of version names.
  205 # update: \%setsByID ---  indexed by full set name, value is the set record
  206 # output: \@allSetIDs   -- full names of sets (the gateway template and the versioned tests)
  207 #############################################
  208   my %setVersionsByID = ();
  209   my @allSetIDs = ();
  210   foreach my $set ( @sets ) {
  211     my $setName = $set->set_id();
  212     #
  213     # FIXME: Here, as in many other locations, we assume that
  214     #    there is a one-to-one matching between versioned sets
  215     #    and gateways.  we really should have two flags,
  216     #    $set->assignment_type and $set->versioned.  I'm not
  217     #    adding that yet, however, so this will continue to
  218     #    use assignment_type...
  219     #
  220     if ( defined($set->assignment_type) &&
  221          $set->assignment_type =~ /gateway/ ) {
  222       my @vList = $db->listSetVersions($studentName,$setName);
  223       # we have to have the merged set versions to
  224       #    know what each of their assignment types
  225       #    are (because proctoring can change)
  226       my @setVersions = $db->getMergedSetVersions( map {[$studentName, $setName, $_]} @vList );
  227 
  228       # add the set versions to our list of sets
  229       foreach ( @setVersions ) {
  230         $setsByID{$_->set_id . ",v" . $_->version_id} = $_;
  231       }
  232       # flag the existence of set versions for this set
  233       $setVersionsByID{$setName} = [ @vList ];
  234       # and save the set names for display
  235       push( @allSetIDs, $setName );
  236       push( @allSetIDs, map { "$setName,v$_" } @vList );
  237 
  238     } else {
  239       push( @allSetIDs, $setName );
  240       $setVersionsByID{$setName} = "None";
  241     }
  242   }
  243 
  244 
  245 #########################################################################################
  246   my $fullName = join("", $studentRecord->first_name," ", $studentRecord->last_name);
  247   my $effectiveUser = $studentRecord->user_id();
  248   my $act_as_student_url = "$root/$courseName/?user=".$r->param("user").
  249       "&effectiveUser=$effectiveUser&key=".$r->param("key");
  250 
  251 
  252   # FIXME: why is the following not "print CGI::h3($fullName);"?  Hmm.
  253   print CGI::h3($fullName ),
  254 
  255   ###############################################################
  256   #  Print table
  257   ###############################################################
  258 
  259   # FIXME I'm assuming the problems are all the same
  260   # FIXME what does this mean?
  261 
  262   my @rows;
  263   my $max_problems=0;
  264 
  265   foreach my $setName (@allSetIDs)   {
  266     my $act_as_student_set_url = "$root/$courseName/$setName/?user=".$r->param("user").
  267       "&effectiveUser=$effectiveUser&key=".$r->param("key");
  268     my $set = $setsByID{ $setName };
  269     my $setID = $set->set_id();  #FIXME   setName and setID should be the same
  270 
  271     # now, if the set is a template gateway set and there
  272     #    are no versions, we acknowledge that the set exists
  273     #    and the student hasn't attempted it; otherwise, we
  274     #    skip it and let the versions speak for themselves
  275     if ( defined( $set->assignment_type() ) &&
  276          $set->assignment_type() =~ /gateway/ &&
  277          ref( $setVersionsByID{ $setName } ) ) {
  278       if ( @{$setVersionsByID{$setName}} ) {
  279         next;
  280       } else {
  281         push( @rows, CGI::Tr({}, CGI::td(WeBWorK::ContentGenerator::underscore2nbsp($setID)),
  282                  CGI::td({colspan=>4}, CGI::em("No versions of this assignment have been taken."))) );
  283         next;
  284       }
  285     }
  286     # if the set has hide_score set, then we need to skip printing
  287     #    the score as well
  288     if ( defined( $set->hide_score ) &&
  289          ( ! $authz->hasPermissions($r->param("user"), "view_hidden_work") &&
  290            ( $set->hide_score eq 'Y' ||
  291        ($set->hide_score eq 'BeforeAnswerDate' && time < $set->answer_date) ) ) ) {
  292       push( @rows, CGI::Tr({}, CGI::td(WeBWorK::ContentGenerator::underscore2nbsp("${setID}_(test_" . $set->version_id . ")")),
  293                CGI::td({colspan=>4}, CGI::em("Display of scores for this set is not allowed."))) );
  294       next;
  295     }
  296 
  297     # otherwise, if it's a gateway, adjust the act-as url
  298     my $setIsVersioned = 0;
  299     if ( defined( $set->assignment_type() ) &&
  300          $set->assignment_type() =~ /gateway/ ) {
  301       $setIsVersioned = 1;
  302       if ( $set->assignment_type() eq 'proctored_gateway' ) {
  303         $act_as_student_set_url =~ s/($courseName)\//$1\/proctored_quiz_mode\//;
  304       } else {
  305         $act_as_student_set_url =~ s/($courseName)\//$1\/quiz_mode\//;
  306       }
  307     }
  308      ##############################################
  309      # this segment requires @problemRecords, $db, $set
  310      # and $studentName, $setName,
  311      ##############################################
  312        my ($status,
  313            $longStatus,
  314            $string,
  315            $twoString,
  316            $totalRight,
  317            $total,
  318            $num_of_attempts,
  319            $num_of_problems);
  320 
  321           ($status,
  322            $longStatus,
  323            $string,
  324            $twoString,
  325            $totalRight,
  326            $total,
  327            $num_of_attempts,
  328            $num_of_problems
  329            )   = grade_set( $db, $set, $setName, $studentName, $setIsVersioned);
  330 
  331 #     warn "status $status  longStatus $longStatus string $string twoString
  332 #           $twoString totalRight $totalRight, total $total num_of_attempts $num_of_attempts
  333 #           num_of_problems $num_of_problems setName $setName";
  334 
  335     my $avg_num_attempts = ($num_of_problems) ? $num_of_attempts/$num_of_problems : 0;
  336     my $successIndicator = ($avg_num_attempts && $total) ? ($totalRight/$total)**2/$avg_num_attempts : 0 ;
  337 
  338     $max_problems = ($max_problems<$num_of_problems)? $num_of_problems:$max_problems;
  339 
  340     # prettify versioned set display
  341     $setName =~ s/(.+),v(\d+)$/${1}_(test_$2)/;
  342 
  343     push @rows, CGI::Tr({},
  344       CGI::td(CGI::a({-href=>$act_as_student_set_url}, WeBWorK::ContentGenerator::underscore2nbsp($setName))),
  345       CGI::td(sprintf("%0.2f",$totalRight)), # score
  346       CGI::td($total), # out of
  347       #CGI::td(sprintf("%0.0f",100*$successIndicator)),   # indicator -- leave this out
  348       CGI::td("<pre>$string\n$twoString</pre>"), # problems
  349       #CGI::td($studentRecord->section),
  350       #CGI::td($studentRecord->recitation),
  351       #CGI::td($studentRecord->user_id),
  352 
  353     );
  354 
  355   }
  356 
  357   my $problem_header = "";
  358   foreach (1 .. $max_problems) {
  359     $problem_header .= &threeSpaceFill($_);
  360   }
  361 
  362   my $table_header = join("\n",
  363     CGI::start_table({-border=>5,style=>'font-size:smaller'}),
  364     CGI::Tr({},
  365       CGI::th({ -align=>'center',},'Set'),
  366       CGI::th({ -align=>'center', },'Score'),
  367       CGI::th({ -align=>'center', },'Out'.CGI::br().'Of'),
  368       #CGI::th({ -align=>'center', },'Ind'),  #  -- leave out indicator column
  369       CGI::th({ -align=>'center', },'Problems'.CGI::br().CGI::pre($problem_header)),
  370       #CGI::th({ -align=>'center', },'Section'),
  371       #CGI::th({ -align=>'center', },'Recitation'),
  372       #CGI::th({ -align=>'center', },'login_name'),
  373       #CGI::th({ -align=>'center', },'ID'),
  374     )
  375   );
  376 
  377 
  378 
  379   print $table_header;
  380   print @rows;
  381   print CGI::end_table();
  382 
  383   return "";
  384 }
  385 ################
  386 # TASKS
  387 ###################
  388 
  389 #  grading utility
  390 # provides a formatted line for presenting grades (
  391 ###############################################################
  392 # 17-2-motion-velocity  0.00  7   0   .  .  .  .  .  .  .
  393 #                                        0  0  0  0  0  0  0
  394 ###############################################################
  395 # requires = grade_set( $db, $set, $setName, $studentName, $setIsVersioned);
  396 # returns   my ($status,
  397 #            $longStatus,
  398 #            $string,
  399 #            $twoString,
  400 #            $totalRight,
  401 #            $total,
  402 #            $num_of_attempts,
  403 #            $num_of_problems) = grade_set(...);
  404 #########################
  405 #       ########################################
  406 #       # Notes for factoring the calculation in this loop.
  407 #       #
  408 #       # Inputs include:
  409 #       #   @problemRecords
  410 #       # returns
  411 #       #   $num_of_attempts
  412 #       #   $status
  413 #       # updates
  414 #       #   $number_of_students_attempting_problem{$probID}++;
  415 #       #   @{ $attempts_list_for_problem{$probID} }
  416 #       #   $number_of_attempts_for_problem{$probID}
  417 #       #   $total_num_of_attempts_for_set
  418 #       #   $correct_answers_for_problem{$probID}
  419 #       #
  420 #       #   $string (formatting output)
  421 #       #   $twoString (more formatted output)
  422 #       #   $longtwo (a combination of $string and $twostring)
  423 #       #   $total
  424 #       #   $totalRight
  425 #       ###################################
  426 sub grade_set {
  427 
  428         my ($db, $set, $setName, $studentName, $setIsVersioned) = @_;
  429 
  430         my $setID = $set->set_id();  #FIXME   setName and setID should be the same
  431 
  432     my $status = 0;
  433     my $longStatus = '';
  434     my $string     = '';
  435     my $twoString  = '';
  436     my $totalRight = 0;
  437     my $total      = 0;
  438     my $num_of_attempts = 0;
  439 
  440     debug("Begin collecting problems for set $setName");
  441     # DBFIXME: to collect the problem records, we have to know
  442     #    which merge routines to call.  Should this really be an
  443     #    issue here?  That is, shouldn't the database deal with
  444     #    it invisibly by detecting what the problem types are?
  445     #    oh well.
  446 
  447     my @problemRecords = $db->getAllMergedUserProblems( $studentName, $setID );
  448     my $num_of_problems  = @problemRecords || 0;
  449     my $max_problems     = defined($num_of_problems) ? $num_of_problems : 0;
  450 
  451     if ( $setIsVersioned ) {
  452       @problemRecords = $db->getAllMergedProblemVersions( $studentName, $setID, $set->version_id );
  453     }   # use versioned problems instead (assume that each version has the same number of problems.
  454 
  455     debug("End collecting problems for set $setName");
  456 
  457   ####################
  458   # Resort records
  459   #####################
  460     @problemRecords = sort {$a->problem_id <=> $b->problem_id }  @problemRecords;
  461 
  462     # for gateway/quiz assignments we have to be careful about
  463     #    the order in which the problems are displayed, because
  464     #    they may be in a random order
  465     if ( $set->problem_randorder ) {
  466       my @newOrder = ();
  467       my @probOrder = (0..$#problemRecords);
  468       # we reorder using a pgrand based on the set psvn
  469       my $pgrand = PGrandom->new();
  470       $pgrand->srand( $set->psvn );
  471       while ( @probOrder ) {
  472         my $i = int($pgrand->rand(scalar(@probOrder)));
  473         push( @newOrder, $probOrder[$i] );
  474         splice(@probOrder, $i, 1);
  475       }
  476       # now $newOrder[i] = pNum-1, where pNum is the problem
  477       #    number to display in the ith position on the test
  478       #    for sorting, invert this mapping:
  479       my %pSort = map {($newOrder[$_]+1)=>$_} (0..$#newOrder);
  480 
  481       @problemRecords = sort {$pSort{$a->problem_id} <=> $pSort{$b->problem_id}} @problemRecords;
  482     }
  483 
  484 
  485     #######################################################
  486   # construct header
  487 
  488     foreach my $problemRecord (@problemRecords) {
  489       my $prob = $problemRecord->problem_id;
  490 
  491       unless (defined($problemRecord) ){
  492         # warn "Can't find record for problem $prob in set $setName for $student";
  493         # FIXME check the legitimate reasons why a student record might not be defined
  494         next;
  495       }
  496 
  497         $status           = $problemRecord->status || 0;
  498         my  $attempted    = $problemRecord->attempted;
  499       my $num_correct   = $problemRecord->num_correct || 0;
  500       my $num_incorrect = $problemRecord->num_incorrect   || 0;
  501       $num_of_attempts  += $num_correct + $num_incorrect;
  502 
  503 #######################################################
  504       # This is a fail safe mechanism that makes sure that
  505       # the problem is marked as attempted if the status has
  506       # been set or if the problem has been attempted
  507       # DBFIXME this should happen in the database layer, not here!
  508       if (!$attempted && ($status || $num_of_attempts)) {
  509         $attempted = 1;
  510         $problemRecord->attempted('1');
  511         # DBFIXME: this is another case where it
  512         #    seems we shouldn't have to check for
  513         #    which routine to use here...
  514         if ( $setIsVersioned ) {
  515           $db->putProblemVersion($problemRecord);
  516         } else {
  517           $db->putUserProblem($problemRecord );
  518         }
  519       }
  520 ######################################################
  521 
  522       # sanity check that the status (score) is
  523       # between 0 and 1
  524       my $valid_status = ($status>=0 && $status<=1)?1:0;
  525 
  526       ###########################################
  527       # Determine the string $longStatus which
  528       # will display the student's current score
  529       ###########################################
  530 
  531       if (!$attempted){
  532         $longStatus     = '.';
  533       } elsif   ($valid_status) {
  534         $longStatus     = int(100*$status+.5);
  535         $longStatus='C' if ($longStatus==100);
  536       } else  {
  537         $longStatus   = 'X';
  538       }
  539 
  540       $string          .= threeSpaceFill($longStatus);
  541       $twoString       .= threeSpaceFill($num_incorrect);
  542       my $probValue     = $problemRecord->value;
  543       $probValue        = 1 unless defined($probValue) and $probValue ne "";  # FIXME?? set defaults here?
  544       $total           += $probValue;
  545       $totalRight      += round_score($status*$probValue) if $valid_status;
  546 
  547 #
  548 #       # initialize the number of correct answers
  549 #       # for this problem if the value has not been
  550 #       # defined.
  551 #       $correct_answers_for_problem{$probID} = 0
  552 #         unless defined($correct_answers_for_problem{$probID});
  553 
  554 #
  555 #     # add on the scores for this problem
  556 #       if (defined($attempted) and $attempted) {
  557 #         $number_of_students_attempting_problem{$probID}++;
  558 #         push( @{ $attempts_list_for_problem{$probID} } ,     $num_of_attempts);
  559 #         $number_of_attempts_for_problem{$probID}             += $num_of_attempts;
  560 #         $h_problemData{$probID}                               = $num_incorrect;
  561 #         $total_num_of_attempts_for_set                       += $num_of_attempts;
  562 #         $correct_answers_for_problem{$probID}                += $status;
  563 #       }
  564 
  565     }  # end of problem record loop
  566 
  567 
  568 
  569     return($status,
  570          $longStatus,
  571          $string,
  572          $twoString,
  573          $totalRight,
  574          $total,
  575          $num_of_attempts,
  576          $num_of_problems
  577     );
  578 }
  579 #################################
  580 # Utility function NOT a method
  581 #################################
  582 sub threeSpaceFill {
  583   my $num = shift @_ || 0;
  584 
  585   if (length($num)<=1) {return "$num".'&nbsp;&nbsp;';}
  586   elsif (length($num)==2) {return "$num".'&nbsp;';}
  587   else {return "## ";}
  588 }
  589 sub round_score{
  590   return shift;
  591 }
  592 
  593 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9