Diff for /loncom/interface/lonquickgrades.pm between versions 1.54 and 1.113

version 1.54, 2010/12/03 15:21:35 version 1.113, 2017/12/31 15:49:03
Line 29 Line 29
 package Apache::lonquickgrades;  package Apache::lonquickgrades;
   
 use strict;  use strict;
 use Apache::Constants qw(:common :http);  use Apache::Constants qw(:common :http REDIRECT);
 use POSIX;  use POSIX;
 use Apache::loncommon;  use Apache::loncommon;
 use Apache::lonlocal;  use Apache::lonlocal;
 use Apache::lonnet;  use Apache::lonnet;
 use Apache::grades;  use Apache::grades;
   use Apache::loncoursedata;
   use Apache::lonstudentassessment;
   use Apache::lonuserstate;
   
   use Time::HiRes;
   use Spreadsheet::WriteExcel;
   use Spreadsheet::WriteExcel::Utility();
   #
   # Excel data
   #
   my $excel_sheet;
   my $excel_workbook;
   my $filename;
   my $format;
   my $request_aborted;
   my $header_row;
   my $cols_output;
   my %prog_state;
   
   
 sub handler {  sub handler {
     my $r = shift;      my $r = shift;
Line 57  sub real_handler { Line 76  sub real_handler {
         return OK;          return OK;
     }      }
   
     # Send header, don't cache this page      my $cangrade=&Apache::lonnet::allowed('mgr');
     &Apache::loncommon::no_cache($r);      my $showPoints =
     $r->send_http_header;          (($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'standard')
         || ($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'categories'));
   
       my $reinitresult;
   
       unless ($cangrade) {
           # Check for critical messages and redirect if present.
           my ($redirect,$url) = &Apache::loncommon::critical_redirect(300,'grades');
           if ($redirect) {
               &Apache::loncommon::content_type($r,'text/html');
               $r->header_out(Location => $url);
               return REDIRECT;
           }
   
     my $showPoints =           # Check if course needs to be re-initialized
         $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'standard';          my $loncaparev = $r->dir_config('lonVersion');
     my $notshowSPRSlink =           ($reinitresult,my @reinit) = &Apache::loncommon::needs_coursereinit($loncaparev);
   
           if ($reinitresult eq 'switch') {
               &Apache::loncommon::content_type($r,'text/html');
               $r->send_http_header;
               $r->print(&Apache::loncommon::check_release_result(@reinit));
               return OK;
           } elsif ($reinitresult eq 'update') {
               my $cid = $env{'request.course.id'};
               my $cnum = $env{'course.'.$cid.'.num'};
               my $cdom = $env{'course.'.$cid.'.domain'};
               &Apache::loncommon::content_type($r,'text/html');
               $r->send_http_header;
               &startpage($r,$showPoints);
               my $preamble = '<div id="LC_update_'.$cid.'" class="LC_info">'.
                              '<br />'.
                              &mt('Your course session is being updated because of recent changes by course personnel.').
                              ' '.&mt('Please be patient.').'<br /></div>'.
                              '<div style="padding:0;clear:both;margin:0;border:0"></div>';
               %prog_state = &Apache::lonhtmlcommon::Create_PrgWin($r,undef,$preamble);
               &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,&mt('Updating course'));
               $r->rflush();
               my ($furl,$ferr) = &Apache::lonuserstate::readmap("$cdom/$cnum");
               &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,&mt('Finished')); 
               &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
               my $closure = <<ENDCLOSE;
   <script type="text/javascript">
   // <![CDATA[
   \$("#LC_update_$cid").hide('slow');
   // ]]>
   </script>
   ENDCLOSE
               if ($ferr) {
                   $r->print($closure.&Apache::loncommon::end_page());
                   my $requrl = $r->uri;
                   $env{'user.error.msg'}="$requrl:bre:0:0:Course not initialized";
                   $env{'user.reinit'} = 1;
                   return HTTP_NOT_ACCEPTABLE;
               } else {
                  $r->print($closure);
               }
           } elsif ((&Apache::loncommon::course_type() eq 'Placement') &&
                    (!$env{'request.role.adv'})) {
               my $furl = &Apache::lonpageflip::first_accessible_resource();
               &Apache::loncommon::content_type($r,'text/html');
               $r->header_out(Location => $furl);
               return REDIRECT;
           }
       }
   
       unless ($reinitresult eq 'update') {
           # Send header, don't cache this page
           &Apache::loncommon::no_cache($r);
           $r->send_http_header;
           &startpage($r,$showPoints);
       }
       $r->rflush();
   
       &startGradeScreen($r,'quick');
   
   #
   # Pick student
   #
       my $uname;
       my $udom;
       my $stdid;
       if ($cangrade) {
           $r->print("<h2>".&mt("Download Multiple")."</h2>".
                     '<table cellspacing="5">'."\n".
                     '<tr>'.
                     '<td align="center"><b>'.&mt('Sections').'</b>'.
                     &Apache::loncommon::help_open_topic("Chart_Sections").
                     '</td>'.
                     '<td align="center"><b>'.&mt('Groups').'</b>'.
                     '</td>'.
                     '<td align="center"><b>'.&mt('Student Data').'</b>'.
                     &Apache::loncommon::help_open_topic("Chart_Student_Data").
                     '</td>'.
                     '<td align="center"><b>'.&mt('Access Status').'</b>'.
                     &Apache::loncommon::help_open_topic("Chart_Enrollment_Status").
                     '</td>'.
                     '<td align="center"><b>'.&mt('Output Format').'</b>'.
                     &Apache::loncommon::help_open_topic("Chart_Output_Formats").
                     '</td><td>&nbsp;</td></tr>'."\n".
                     '<tr><td align="center">'."\n".
                     &Apache::lonstatistics::SectionSelect('Section','multiple',5).
                     '</td><td align="center">'.
                     &Apache::lonstatistics::GroupSelect('Group','multiple',5).
                     '</td><td align="center">'.
                     &Apache::lonstatistics::StudentDataSelect('StudentData','multiple',5,undef).
                     '</td><td>'."\n".
                     &Apache::lonhtmlcommon::StatusOptions(undef,undef,5).
                     '</td><td>'."\n".
                     &Apache::lonstudentassessment::CreateAndParseOutputSelector().
                     '</td><td>'.
                     '<input type="submit" name="download" value="'.&mt('Display/Download Multiple Students').'" />'.
                     '</td></tr>'."\n".
                     '</table>'."\n"
                    );
           $r->print("<hr /><h2>".&mt("Display Individual")."</h2>");
           if ($env{'form.uname'}) { $uname=$env{'form.uname'}; }
           if ($env{'form.udom'}) { $udom=$env{'form.udom'}; }
           if ($env{'form.id'}) { $stdid=$env{'form.id'}; }
           if (($stdid) && ($udom)) {
               $uname=(&Apache::lonnet::idget($udom,[$stdid],'ids'))[1];
           }
           if (($stdid) && (!$uname)) {
               $r->print('<p><span class="LC_warning">'.&mt("Unknown Student/Employee ID: [_1]",$stdid).'</span></p>');
               $stdid='';
           }
           $r->print('<form method="post" name="quickform" action="/adm/quickgrades">');
           my $chooseopt=&Apache::loncommon::select_dom_form($udom,'udom').' '.
              &Apache::loncommon::selectstudent_link('quickform','uname','udom');
           $r->print("<p>\n".&Apache::loncommon::studentbrowser_javascript()."\n");
           $r->print(&mt('For User [_1] or Student/Employee ID [_2] at Domain [_3]'
                    ,'<input type="text" value="'.$uname.'" size="12" name="uname" />'
                    ,'<input type="text" value="'.$stdid.'" size="12" name="id" /> '
                    ,$chooseopt).
                    '&nbsp;&nbsp;<input type="submit" name="display" value="'.&mt('Display Individual Student').'" /></p>');
           if (($uname) && ($udom)) {
               $r->print('<p>'.&mt('Full Name: [_1]',&Apache::loncommon::plainname($uname,$udom)).'</p>');
           }
       }
       $r->rflush();
   
       my $notshowSPRSlink =
         (($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'external')          (($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'external')
       || ($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'externalnototals'));        || ($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'externalnototals'));
     my $notshowTotals=      my $notshowTotals=
Line 71  sub real_handler { Line 227  sub real_handler {
     my $showCategories=      my $showCategories=
         $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'categories';          $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'categories';
   
       my ($navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted)=
          &getData($showPoints,$uname,$udom);
   
       if ($showCategories) {
          &outputCategories($r,$showPoints,$notshowTotals,
                    $navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted);
       } else {
          &outputTable($r,$showPoints,$notshowTotals,
                    $navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted);
       }
       if ($cangrade) { $r->print("\n</form>\n"); }
       &endGradeScreen($r);
       return OK;
   
   }
   
   sub getStudentCatGrade {
       my ($uname,$udom,%categories)=@_;
       my ($navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted)=
          &getData(1,$uname,$udom);
       return &output_category_table(undef,0,$navmap,0,%categories);
   }
   
   sub getAllStudentData {
       my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
       my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
   
       my %categories=&Apache::lonnet::dump('grading_categories',$cdom,$cnum);
   
       my $classlist = &Apache::loncoursedata::get_classlist();
   
       my $statusidx   = &Apache::loncoursedata::CL_STATUS();
       my $usernameidx = &Apache::loncoursedata::CL_SNAME();
       my $domainidx   = &Apache::loncoursedata::CL_SDOM();
       my $fullnameidx = &Apache::loncoursedata::CL_FULLNAME();
   
       foreach my $key (keys(%{$classlist})) {
           my $student = $classlist->{$key};
           my $perc=&getStudentCatGrade($classlist->{$student}->[$usernameidx],
                                        $classlist->{$student}->[$domainidx],
                                        %categories);
       }
   }
   
     # Header  sub startpage {
       my ($r,$showPoints) = @_;
     my $title = "Grading and Statistics";#$showPoints ? "Points Display" : "Completed Problems Display";      my $title = "Grading and Statistics";#$showPoints ? "Points Display" : "Completed Problems Display";
     my $brcrum = [{href=>"/adm/quickgrades",text => "Points Display"}];      my $brcrum = [{href=>"/adm/quickgrades",text => "Points Display"}];
     $r->print(&Apache::loncommon::start_page($title,undef,      $r->print(&Apache::loncommon::start_page($title,undef,
                                             {'bread_crumbs' => $brcrum})                                              {'bread_crumbs' => $brcrum})
              );               );
   }
   
     $r->print(&Apache::lonhtmlcommon::coursepreflink(&mt('Grade display settings'),'grading'));  sub startGradeScreen {
       my ($r,$mode)=@_;
   
     if (!$showPoints && !$notshowSPRSlink ) {      my $showPoints =
         $r->print('<p>'          $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'standard';
                  .&mt('This screen shows how many problems (or problem parts) you have completed'      my $notshowSPRSlink =
                      .', and how many you have not yet done.'          (($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'external')
                      .' You can also look at [_1]a detailed score sheet[_2].'        || ($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'externalnototals')
                      ,'<a href="/adm/studentcalc">','</a>')        || ($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'categories'));
                  .'</p>');      my $notshowTotals=
     }          $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'externalnototals';
       my $showCategories=
           $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'categories';
   
     $r->print('<p class="LC_info">'.&mt('This may take a few moments to display.').'</p>');      my $allowed_to_view =  &Apache::lonnet::allowed('vgr',$env{'request.course.id'});
       my $allowed_to_edit =  &Apache::lonnet::allowed('mgr',$env{'request.course.id'});
   
     $r->rflush();      if ($allowed_to_view) {
          my @notes;
          push(@notes,&mt('Students do not see total points.')) if ($notshowTotals);
          push(@notes,&mt('Students do not see link to spreadsheet.')) if ($notshowSPRSlink);
          push(@notes,&mt('Students will see points based on problem weights.')) if ($showPoints);
          push(@notes,&mt('Students will see points based on categories.')) if ($showCategories);
          push(@notes, &Apache::lonhtmlcommon::coursepreflink(&mt('Grade display settings'),'grading'));
          $r->print(&Apache::loncommon::head_subbox(join('&nbsp;&nbsp;',@notes)));
       }
   
 #    my $uname='korte';  
 #    my $udom='gerd';  
   
     my $uname;      $r->print("\n".'<ul class="LC_TabContentBigger" id="main">');
     my $udom;      $r->print("\n".'<li'.($mode eq 'quick'?' class="active"':'').'><a href="/adm/quickgrades"><b>&nbsp;&nbsp;&nbsp;&nbsp;'.
                                             ($showPoints?&mt('Individual Points Overview'):($showCategories?&mt('Grades Overview'):&mt('Completion Overview'))).
                                             '&nbsp;&nbsp;&nbsp;&nbsp;</b></a></li>');
   
       if (!($showPoints || $notshowSPRSlink) || ($allowed_to_view)) {
          $r->print("\n".'<li'.($mode eq 'spreadsheet'?' class="active"':'').'><a href="/adm/'.($allowed_to_view?'classcalc':'studentcalc').'"><b>'.
                                                                    &mt('Spreadsheet (Detailed)').'</b></a></li>');
       }
       if ($allowed_to_view) {
          $r->print("\n".'<li'.($mode eq 'statistics'?' class="active"':'').'><a href="/adm/statistics"><b>'.
                                                                    &mt('Statistics and Reports').'</b></a></li>');
   
     my ($navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted)=         $r->print("\n".'<li'.($mode eq 'chart'?' class="active"':'').'><a href="/adm/statistics?reportSelected=student_assessment"><b>'.
        &getData($showPoints,$uname,$udom);                                                                   &mt('Assessment Chart').'</b></a></li>');
   
     if ($showCategories) {  
        &outputCategories($r,$showPoints,$notshowTotals,  
                  $navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted);  
     } else {  
        &outputTable($r,$showPoints,$notshowTotals,  
                  $navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted);  
     }      }
     return OK;      if ($allowed_to_edit) {
          $r->print("\n".'<li'.($mode eq 'grading'?' class="active"':'').'><a href="/adm/grades"><b>&nbsp;&nbsp;&nbsp;&nbsp;'.
                                                                    &mt('Content Grading').'&nbsp;&nbsp;&nbsp;&nbsp;</b></a></li>');
          if ($env{'form.symb'}) {
             $r->print("\n".'<li'.($mode eq 'probgrading'?' class="active"':'').'><a href="/adm/grades?symb='.
                                                 &Apache::lonhtmlcommon::entity_encode($env{'form.symb'}).
                                                 '&amp;command=gradingmenu"><b>&nbsp;&nbsp;&nbsp;&nbsp;'.
                                                 &mt('Problem Grading').'&nbsp;&nbsp;&nbsp;&nbsp;</b></a></li>');
   
          }
       }
       $r->print("\n".'</ul>'."\n");
       $r->print('<div class="LC_Box" style="clear:both;margin:0;"><div id="maincoursedoc" style="margin:0 0;padding:0 0;"><div class="LC_ContentBox" id="mainCourseDocuments" style="display: block;">');
   }
   
   sub endGradeScreen {
      my ($r)=@_;
      $r->print('</div></div></div>'.&Apache::loncommon::end_page());
   }
   
   # -----------
   
   
   sub excel_cleanup {
       undef ($excel_sheet);
       undef ($excel_workbook);
       undef ($filename);
       undef ($format);
   }
   
   
   sub excel_initialize {
       my ($r) = @_;
   
       &excel_cleanup();
   
       # Create sheet
       ($excel_workbook,$filename,$format)=
           &Apache::loncommon::create_workbook($r);
       return if (! defined($excel_workbook));
      #
      # Add a worksheet
       my $sheetname = $env{'course.'.$env{'request.course.id'}.'.description'};
       $sheetname = &Apache::loncommon::clean_excel_name($sheetname);
       $excel_sheet = $excel_workbook->addworksheet($sheetname);
      #
      # Put the course description in the header
       $excel_sheet->write($header_row,$cols_output++,
                      $env{'course.'.$env{'request.course.id'}.'.description'},
                           $format->{'h1'});
   } 
   
   sub excel_finish {
       my ($r) = @_;
       if ($request_aborted || ! defined($excel_sheet)) {
           &excel_cleanup();
           return;
       }
       #
       # Write the excel file
       $excel_workbook->close();
       #
       # Close the progress window
       &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
       #
       # Tell the user where to get their excel file
       $r->print('<br />'.
                 '<a href="'.$filename.'">'.&mt('Your Excel spreadsheet').'</a>'."\n");
       $r->rflush();
       &excel_cleanup();
       return;
 }  }
   
   
   #
   # CSV data
   #
   # -----------
   
   #
   # Go through the complete course and collect data
   #
   
 sub getData {  sub getData {
   
     my ($showPoints,$uname,$udom)=@_;      my ($showPoints,$uname,$udom)=@_;
   
     &Apache::lonnet::logthis("About to call with $uname $udom");  
   
     # Create the nav map      # Create the nav map
     my $navmap = Apache::lonnavmaps::navmap->new($uname,$udom);      my $navmap = Apache::lonnavmaps::navmap->new($uname,$udom);
   
Line 152  sub getData { Line 444  sub getData {
         if ($curRes == $iterator->BEGIN_MAP()) {$depth++;}          if ($curRes == $iterator->BEGIN_MAP()) {$depth++;}
         if ($curRes == $iterator->END_MAP()) { $depth--; }          if ($curRes == $iterator->END_MAP()) { $depth--; }
   
         if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout)          if (ref($curRes) && $curRes->is_gradable() && !$curRes->randomout)
         {          {
             # Get number of correct, incorrect parts              # Get number of correct, incorrect parts
             my $parts = $curRes->parts();              my $parts = $curRes->parts();
Line 162  sub getData { Line 454  sub getData {
             my $stack = $iterator->getStack();              my $stack = $iterator->getStack();
                           
             for my $part (@{$parts}) {              for my $part (@{$parts}) {
  my $completionStatus = $curRes->getCompletionStatus($part);  
  my $dateStatus = $curRes->getDateStatus($part);   my $dateStatus = $curRes->getDateStatus($part);
                   my $weight = $curRes->weight($part);
                 if ($completionStatus == $curRes->EXCUSED()) {                  my $problemstatus = $curRes->problemstatus($part);
   
                   if ($curRes->solved($part) eq 'excused') {
                     next;                      next;
                 }                  }
  if ($showPoints) {   if ($showPoints) {
     my $score = 0;      my $score = 0;
     # If we're not telling status and the answer date isn't passed yet,       # If we're not telling status and the answer date isn't passed yet, 
     # it's an "attempted" point      # it's an "attempted" point
     if ((($curRes->problemstatus($part) eq 'no') ||      if ((($problemstatus eq 'no') ||
                         ($curRes->problemstatus($part) eq 'no_feedback_ever')) &&                           ($problemstatus eq 'no_feedback_ever')) &&
  ($dateStatus != $curRes->ANSWER_OPEN)) {   ($dateStatus != $curRes->ANSWER_OPEN)) {
  my $status = $curRes->simpleStatus($part);   my $status = $curRes->simpleStatus($part);
  if ($status == $curRes->ATTEMPTED) {   if ($status == $curRes->ATTEMPTED) {
     $partsAttempted += $curRes->weight($part);      $partsAttempted += $weight;
     $totalAttempted += $partsAttempted;      $totalAttempted += $partsAttempted;
  }   }
     } else {      } else {
  $score = &Apache::grades::compute_points($curRes->weight($part), $curRes->awarded($part));   $score = &Apache::grades::compute_points($weight, $curRes->awarded($part));
     }      }
     $partsRight += $score;      $partsRight += $score;
     $totalRight += $score;      $totalRight += $score;
     $partsCount += $curRes->weight($part);      $partsCount += $weight;
   
                       $curRes->{DATA}->{PROB_SCORE}  += $score;
                       $curRes->{DATA}->{PROB_WEIGHT} += $weight;
   
     if ($curRes->opendate($part) < $now) {      if ($curRes->opendate($part) < $now) {
  $totalPossible += $curRes->weight($part);   $totalPossible += $weight;
                           $curRes->{DATA}->{PROB_POSSIBLE} += $weight;
     }      }
     $totalParts += $curRes->weight($part);      $totalParts += $weight;
  } else {   } else {
     my $status = $curRes->simpleStatus($part);      my $status = $curRes->simpleStatus($part);
     my $thisright = 0;      my $thisright = 0;
Line 207  sub getData { Line 504  sub getData {
  $totalAttempted++;   $totalAttempted++;
     }      }
           
     my $dateStatus = $curRes->getDateStatus($part);  
     $totalParts++;      $totalParts++;
     if ($curRes->opendate($part) < $now) {      if ($curRes->opendate($part) < $now) {
  $totalPossible++;   $totalPossible++;
Line 321  sub outputTable { Line 617  sub outputTable {
     # If there were any problems at the top level, print an extra "catchall"      # If there were any problems at the top level, print an extra "catchall"
     if ($topLevelParts > 0) {      if ($topLevelParts > 0) {
         my $ratio = $topLevelRight / $topLevelParts;          my $ratio = $topLevelRight / $topLevelParts;
         my $color = mixColors(\@start, \@end, $ratio);          my $color = &mixColors(\@start, \@end, $ratio);
         $r->print(&Apache::loncommon::start_data_table_row()          $r->print(&Apache::loncommon::start_data_table_row()
                  .'<td style="background-color:'.$color.';">');                   .'<td style="background-color:'.$color.';">');
         $r->print(&mt("Problems Not Contained In A Folder")."</td><td>");          $r->print(&mt("Problems Not Contained In A Folder")."</td><td>");
Line 333  sub outputTable { Line 629  sub outputTable {
 # show totals (if applicable), close table  # show totals (if applicable), close table
 #  #
     if ($showPoints) {      if ($showPoints) {
         my $maxHelpLink = Apache::loncommon::help_open_topic("Quick_Grades_Possibly_Correct");          my $maxHelpLink = &Apache::loncommon::help_open_topic("Quick_Grades_Possibly_Correct");
   
         $title = $showPoints ? "Points" : "Parts Done";          $title = $showPoints ? "Points" : "Parts Done";
         my $totaltitle = $showPoints ? &mt("Awarded Total Points") : &mt("Total Parts Done");          my $totaltitle = $showPoints ? &mt("Awarded Total Points") : &mt("Total Parts Done");
Line 345  sub outputTable { Line 641  sub outputTable {
                  .&Apache::loncommon::end_data_table_row());                   .&Apache::loncommon::end_data_table_row());
     }      }
   
     $r->print(&Apache::loncommon::end_data_table()      $r->print(&Apache::loncommon::end_data_table());
              .&Apache::loncommon::end_page());  
   
 }  }
   
 #  #
 # Outputting category-based grades.  # === Outputting category-based grades.
   #
   # $category{'order'}: output order of categories by id
   # $category{'all'}: complete list of all categories 
   # $category{$id.'_name'}: display-name of category
 #  #
   
 sub outputCategories {  sub outputCategories {
   
     my ($r,$showPoints,$notshowTotals,      my ($r,$showPoints,$notshowTotals,
            $navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted)=@_;             $navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted)=@_;
   # Take care of storing and retrieving categories
   
       my $cangrade=&Apache::lonnet::allowed('mgr');
   
       my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
       my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
       my %categories=();
   # Loading old categories
       %categories=&Apache::lonnet::dump('grading_categories',$cdom,$cnum);
   # Storing
       if (($cangrade) && (($env{'form.storechanges'}) || ($env{'form.storemove'} ne '') || ($env{'form.cmd'} ne ''))) {
   # Process the changes
           %categories=&process_category_edits($r,$cangrade,%categories);
   # Actually store
           &Apache::lonnet::put('grading_categories',\%categories,$cdom,$cnum);
       }
   # new categories loaded now
       &output_category_table($r,$cangrade,$navmap,1,%categories);
   #
       if ($cangrade) {
           $r->print(&Apache::loncommon::resourcebrowser_javascript().
                     '<input type="hidden" name="storemove" value="" />'.
                     '<input type="hidden" name="cmd" value="" />'.
                     '<input type="hidden" name="resourcesymb" value="" />'.
                     '<input type="submit" name="storechanges" value="'.&mt("Save changes to grading categories").'" />'.
                     '<script>function storecmd (cmd) { document.quickform.cmd.value=cmd; document.quickform.submit(); }</script>');
       }
   }
   
   #
   # Get data for all symbs
   #
   
   sub dumpdata {
       my ($navmap)=@_;
       my %returndata=();
   
   # Run through the map and get all data
   
       my $iterator = $navmap->getIterator(undef, undef, undef, 1);
       my $depth = 1;
       $iterator->next(); # ignore first BEGIN_MAP
       my $curRes = $iterator->next();
   
       while ($depth > 0) {
           if ($curRes == $iterator->BEGIN_MAP()) {$depth++;}
           if ($curRes == $iterator->END_MAP()) { $depth--; }
           if (ref($curRes)) {
               if ($curRes->is_map()) {
                   $returndata{$curRes->symb()}='folder:'.$curRes->{DATA}->{CHILD_PARTS}.':'.$curRes->{DATA}->{CHILD_ATTEMPTED}.':'.$curRes->{DATA}->{CHILD_CORRECT};
               } else {
                   $returndata{$curRes->symb()}='res:'.$curRes->{DATA}->{PROB_WEIGHT}.':'.$curRes->{DATA}->{PROB_POSSIBLE}.':'.$curRes->{DATA}->{PROB_SCORE};
               } 
           }
           $curRes = $iterator->next();
       }
       return %returndata;
   }
   
   #
   # Process editing commands, update category hash
   #
   
   sub process_category_edits {
       my ($r,$cangrade,%categories)=@_;
       unless ($cangrade) { return %categories; }
   # First store everything
       foreach my $id (split(/\,/,$categories{'order'})) {
   # Set names, types, and weight (there is only one of each per category)
           %categories=&set_category_name($cangrade,$id,$env{'form.name_'.$id},%categories);
           %categories=&set_category_total($cangrade,$id,$env{'form.totaltype_'.$id},$env{'form.total_'.$id},%categories);
           %categories=&set_category_weight($cangrade,$id,$env{'form.weight_'.$id},%categories);
           %categories=&set_category_displayachieved($cangrade,$id,$env{'form.displayachieved_'.$id},%categories);
   # Set values for category rules (before names may change)
           %categories=&set_category_rules($cangrade,$id,%categories);
       }
   
   # Now deal with commands
       my $cmd=$env{'form.cmd'};
       if ($cmd eq 'createnewcat') {
           %categories=&make_new_category($r,$cangrade,undef,%categories);
       } elsif ($cmd=~/^up\_(.+)$/) {
           %categories=&move_up_category($1,$cangrade,%categories);
       } elsif ($cmd=~/^down\_(.+)$/) {
           %categories=&move_down_category($1,$cangrade,%categories);
       } elsif ($cmd=~/^delcat\_(.+)$/) {
           %categories=&del_category($1,$cangrade,%categories);
       } elsif ($cmd=~/^addcont\_(.+)$/) {
           %categories=&add_category_content($1,$cangrade,$env{'form.resourcesymb'},%categories);
       } elsif ($cmd=~/^delcont\_(.+)\_\_\_\_\_\_(.+)$/) {
           %categories=&del_category_content($1,$cangrade,$2,%categories);
       } elsif ($cmd=~/^newrule\_(.+)$/) {
           %categories=&add_calculation_rule($1,$cangrade,':',%categories);
       } elsif ($cmd=~/^delrule\_(.+)\_\_\_\_\_\_(.*)$/) {
           %categories=&del_calculation_rule($1,$cangrade,$2,%categories);
       }
   # Move to a new position
       my $moveid=$env{'form.storemove'};
       if ($moveid) {
           %categories=&move_category($moveid,$cangrade,$env{'form.newpos_'.$moveid},%categories);
       } 
       return %categories;
 }  }
   
   #
   # Output the table
   #
   
   sub output_category_table {
       my ($r,$cangrade,$navmaps,$output,%categories)=@_;
       
       my $totalweight=0;
       my $totalpoints=0;
   
       if ($output) { 
          $r->print(&Apache::loncommon::start_data_table());
   #
          &output_category_table_header($r,$cangrade);
       }
   #
       my @order=split(/\,/,$categories{'order'});
   #
       my %performance=&dumpdata($navmaps);
       my $maxpos=$#order;
       for (my $i=0;$i<=$maxpos;$i++) {
           my ($correct,$possible,$type,$weight)=&output_and_calc_category($r,$cangrade,$navmaps,$order[$i],$i,$maxpos,\%performance,$output,%categories);
           unless ($possible) { next; }
           $totalpoints+=$weight*$correct/$possible;
           $totalweight+=$weight;
       }
   #
       my $perc=0;
       if ($totalweight) { $perc=100.*$totalpoints/$totalweight; }
   
       if ($output) { 
           &bottom_line_category($r,$cangrade,$perc); 
           $r->print(&Apache::loncommon::end_data_table());
       }
       return $perc;
   }
   
   sub output_category_table_header {
       my ($r,$cangrade)=@_;
       $r->print(&Apache::loncommon::start_data_table_header_row());
       if ($cangrade) {
           $r->print('<th colspan="2">'.&mt("Move").'</th><th>'.&mt('Action').'</th>');
       }
       $r->print('<th>'.&mt('Category').'</th>'.
                 '<th>'.&mt('Contents').'</th>'.
                 '<th>'.&mt('Total Points').'</th>'.
                 '<th>'.&mt('Calculation').'</th>'.
                 '<th>'.&mt('Relative Weight').'</th>'.
                 '<th>'.&mt('Achieved').'</th>');
       $r->print(&Apache::loncommon::end_data_table_header_row());
   }
   
   
   #
   # Output one category to table
   #
   
   sub output_and_calc_category {
       my ($r,$cangrade,$navmaps,$id,$currentpos,$maxpos,$performance,$output,%categories)=@_;
       
       if ($output) { $r->print("\n".&Apache::loncommon::start_data_table_row()); }
   
       if ($output && $cangrade) {
           my $iconpath = &Apache::loncommon::lonhttpdurl($r->dir_config('lonIconsURL') . "/");
           my %lt=&Apache::lonlocal::texthash(
              'up' => 'Move Up',
              'dw' => 'Move Down');
   
           $r->print(<<ENDMOVE);
   <td>
   <div class="LC_docs_entry_move">
     <a href='javascript:storecmd("up_$id");'>
       <img src="${iconpath}move_up.gif" alt='$lt{'up'}' class="LC_icon" />
     </a>
   </div>
   <div class="LC_docs_entry_move">
     <a href='javascript:storecmd("down_$id");'>
       <img src="${iconpath}move_down.gif" alt='$lt{'dw'}' class="LC_icon" />
     </a>
   </div>
   </td>
   ENDMOVE
           $r->print("\n<td>\n<select name='newpos_$id' onchange='this.form.storemove.value=\"$id\";this.form.submit()'>");
           for (my $i=0;$i<=$maxpos;$i++) {
               if ($i==$currentpos) {
                   $r->print('<option value="" selected="selected">('.$i.')</option>');
               } else {
                   $r->print('<option value="'.$i.'">'.$i.'</option>');
               }
           }
           $r->print("\n</select>\n</td>\n");
           $r->print('<td><a href="javascript:storecmd(\'delcat_'.$id.'\');">'.&mt('Delete').'</a></td>');
           $r->print('<td><input type="text" name="name_'.$id.
                     '" value="'.&Apache::lonhtmlcommon::entity_encode($categories{$id.'_name'}).'" /></td>');
       } elsif ($output) {
           $r->print('<td>'.$categories{$id.'_name'}.'</td>');
       }
   # Content display and summing up of points
       my $totalpossible=0;
       my $totalcorrect=0;
       my @individual=();
       if ($output) { $r->print('<td><ul>'); }
       foreach my $contentid (split(/\,/,$categories{$id.'_content'})) {
           my ($type,$possible,$attempted,$correct)=split(/\:/,$$performance{$contentid});
           $totalpossible+=$possible;
           $totalcorrect+=$correct;
           if ($possible>0) { push(@individual,"$possible:$correct"); }
           if ($output) {
              $r->print('<li>');
              $r->print(&Apache::lonnet::gettitle($contentid).' ('.&numberout($correct).'/'.&numberout($possible).')');
              if ($cangrade) {
                 $r->print(' <a href="javascript:storecmd(\'delcont_'.$id.'______'.$contentid.'\');">'.&mt('Delete').'</a>');
              }
              $r->print('</li>');
           }
       }
       if ($output) {
          $r->print('</ul>');
          if ($cangrade) {
              $r->print('<br />'.&Apache::loncommon::selectresource_link('quickform','addcont_'.$id,&mt('Add Problem or Folder')).'<br />');
          }
          $r->print('<p><b>'.&mt('Total raw points: [_1]/[_2]',&numberout($totalcorrect),&numberout($totalpossible)).'</b></p>');
          $r->print('</td>'); 
       }
   # Total
       if ($output) { $r->print('<td>'); }
       if ($cangrade) {
          if ($output) { 
             $r->print(
                     '<select name="totaltype_'.$id.'">'.
                     '<option value="default"'.($categories{$id.'_totaltype'} eq 'default'?' selected="selected"':'').'>'.&mt('default').'</option>'.
                     '<option value="typein"'.($categories{$id.'_totaltype'} eq 'typein'?' selected="selected"':'').'>'.&mt('Type-in value').'</option>'.
                     '</select>'.
                     '<input type="text" size="4" name="total_'.$id.
                     '" value="'.&Apache::lonhtmlcommon::entity_encode($categories{$id.'_total'}).'" />'); 
          }
       } else {
          if ($output) {
             $r->print('<td>'.($categories{$id.'_totaltype'} eq 'default'?&mt('default'):$categories{$id.'_total'}));
          }
       }
   # Adjust total points
       if ($categories{$id.'_totaltype'} eq 'typein') {
          $totalpossible=1.*$categories{$id.'_total'};
       }
       if ($output) {
          $r->print('<p><b>'.&mt('Adjusted raw points: [_1]/[_2]',&numberout($totalcorrect),&numberout($totalpossible)).'</b></p>');
       }
   
   
   # Calculation
       if ($output) { $r->print('<td><ul>'); }
       foreach my $calcrule (split(/\,/,$categories{$id.'_calculations'})) {
           if ($output) { $r->print('<li>'); }
           my ($code,$value)=split(/\:/,$calcrule);
           if ($output) { $r->print(&pretty_prt_rule($cangrade,$id,$code,$value)); }
           if ($cangrade) {
              if ($output) { $r->print(' <a href="javascript:storecmd(\'delrule_'.$id.'______'.$code.'\');">'.&mt('Delete').'</a>'); }
           }
           if ($code eq 'capabove') {
               if ($totalpossible>0) {
                   if ($totalcorrect/$totalpossible>$value/100.) {
                       $totalcorrect=$totalpossible*$value/100.;
                   }
               }
           } elsif ($code eq 'capbelow') {
               if ($totalpossible>0) {
                   if ($totalcorrect/$totalpossible<$value/100.) {
                       $totalcorrect=$totalpossible*$value/100.;
                   }
               }
           } elsif ($code eq 'droplow') {
               ($totalpossible,$totalcorrect,@individual)=&drop(0,0,$value,@individual);
           } elsif ($code eq 'drophigh') {
               ($totalpossible,$totalcorrect,@individual)=&drop(1,0,$value,@individual);
           } elsif ($code eq 'droplowperc') {
               ($totalpossible,$totalcorrect,@individual)=&drop(0,1,$value,@individual);
           } elsif ($code eq 'drophighperc') {
               ($totalpossible,$totalcorrect,@individual)=&drop(1,1,$value,@individual);
           }
           if ($output) { $r->print('</li>'); }
       }
   # Re-adjust total points if force total
       if ($categories{$id.'_totaltype'} eq 'typein') {
          $totalpossible=1.*$categories{$id.'_total'};
       }
   
       if ($output) { 
           $r->print('</ul>'); 
           if ($cangrade) { $r->print('<br />'.&new_calc_rule_form($id)); }
           $r->print('<p><b>'.&mt('Calculated points: [_1]/[_2]',&numberout($totalcorrect),&numberout($totalpossible)).'</b></p>');
           $r->print('</td>'); 
       }
   #
   # Prepare for export
   #
   # Weight
       my $weight=$categories{$id.'_weight'};
       unless (1.*$weight>0) { $weight=0; }
       if ($cangrade) {
          if ($output) { 
             $r->print('<td>'.
                     '<input type="text" size="4" name="weight_'.$id.
                     '" value="'.&Apache::lonhtmlcommon::entity_encode($weight).'" /></td>');
          }
       } else {
          if ($output) {
             $r->print('<td>'.$weight.'</td>');
          }
       }
   # Achieved
       my $type=$categories{$id.'_displayachieved'};
       unless (($type eq 'percent') || ($type eq 'points')) { $type='points'; }
       if ($output) { $r->print('<td>'); }
       if ($cangrade) {
           if ($output) {
              $r->print('<select name="displayachieved_'.$id.'">'.
                     '<option value="percent"'.($type eq 'percent'?' selected="selected"':'').'>'.&mt('percent').'</option>'.
                     '<option value="points"'.($type eq 'points'?' selected="selected"':'').'>'.&mt('points').'</option>'.
                     '</select>');
           }
       }
       if ($output) {
           $r->print('<p><b>');
           if ($type eq 'percent') {
               my $perc='---';
               if ($totalpossible) {
                   $perc=100.*$totalcorrect/$totalpossible;
               }
               $r->print(&mt('[_1] percent',&numberout($perc)));
           } else {
               $r->print(&mt('[_1]/[_2] points',&numberout($totalcorrect),&numberout($totalpossible)));
           }
           $r->print('</b></p>');
       }
       if ($output) { $r->print('</td>'); }
   
       return ($totalcorrect,$totalpossible,$type,$weight);
   }
   
   #
   # Drop folders and problems
   #
   
   sub drop {
       my ($high,$percent,$n,@individual)=@_;
   # Sort assignments by points or percent
       my @newindividual=sort {
           my ($pa,$ca)=split(/\:/,$a);
           my ($pb,$cb)=split(/\:/,$b);
           if ($percent) {
               my $perca=0;
               if ($pa>0) { $perca=$ca/$pa; }
               my $percb=0;
               if ($pb>0) { $percb=$cb/$pb; }
               $perca<=>$percb;
           } else {
               $ca<=>$cb;
           }
       } @individual;
   # Drop the ones we don't want
       if ($#newindividual>=$n) {
           if ($high) {
              splice(@newindividual,$#newindividual+1-$n,$n);
           } else {
              splice(@newindividual,0,$n);
           }
       } else {
           @newindividual=();
       }
   # Re-calculate how many points possible and achieved
       my $newpossible=0;
       my $newcorrect=0;
       for my $score (@newindividual) {
           my ($thispossible,$thiscorrect)=(split(/\:/,$score));
           $newpossible+=$thispossible;
           $newcorrect+=$thiscorrect;
       }
       return ($newpossible,$newcorrect,@newindividual);
   } 
   #
   # Bottom line with grades
   #
   
   sub bottom_line_category {
       my ($r,$cangrade,$perc)=@_;
       $r->print(&Apache::loncommon::start_data_table_row());
       if ($cangrade) {
           $r->print('<td colspan="3"><a href="javascript:storecmd(\'createnewcat\');">'.&mt('Create New Category').'</a></td>');
       }
       $r->print('<td colspan="6"><b>'.&mt('Total: [_1] percent',&numberout($perc)).'</b></td>');
   }
   
   sub numberout {
       my ($number)=@_;
       my $printout=sprintf("%.3f", $number);
       $printout=~s/0+$//;
       $printout=~s/\.$//;
       return $printout;
   }
   #
   # Make one new category
   #
   
   sub make_new_category {
       my ($r,$cangrade,$ordernum,%categories)=@_;
       unless ($cangrade) { return %categories; }
   # Generate new ID
       my $id=time.'_'.$$.'_'.rand(10000);
   # Add new ID to list of all IDs ever created in this course
       $categories{'all'}.=','.$id;
       $categories{'all'}=~s/^\,//;
   # Add new ID to ordered list of displayed and evaluated categories
       $categories{'order'}.=','.$id;
       $categories{'order'}=~s/^\,//;
   # Move it into desired space
       if (defined($ordernum)) {
           %categories=&move_category($id,$cangrade,$ordernum,%categories);
       }
       $categories{$id.'_weight'}=0;
       $categories{$id.'_totaltype'}='default';
       $categories{$id.'_displayachieved'}='percent';
       return %categories;
   }
   
   
   # === Calculation Rule Editing
   
   sub category_rule_codes {
       return &Apache::lonlocal::texthash(
                   'droplowperc'  => 'Drop N lowest grade percentage problems/folders',
                   'drophighperc' => 'Drop N highest grade percentage problems/folderss',
                   'droplow'  => 'Drop N lowest point problems/folders',
                   'drophigh' => 'Drop N highest point problems/folders',
                   'capabove' => 'Cap percentage above N percent',
                   'capbelow' => 'Cap percentage below N percent');
   }
   
   sub pretty_prt_rule {
       my ($cangrade,$id,$code,$value)=@_;
       my $cid=$id.'_'.$code;
       my %lt=&category_rule_codes();
       my $ret='<span class="LC_nobreak">';
       if ($cangrade) {
           $ret.='<select name="sel_'.$cid.'">';
           foreach my $calc (''=>'',sort(keys(%lt))) {
               $ret.='<option value="'.$calc.'"'.($calc eq $code?' selected="selected"':'').' />'.$lt{$calc}.'</input>';
           }
           $ret.='</select> N=<input type="text" size="5" name="val_'.$cid.'" value="'.$value.'" /></span>';
       } else {
           $ret.=$lt{$code}.'; N='.$value;
       }
       $ret.='</span>';
       return $ret;
   }
   
   sub new_calc_rule_form {
       my ($id)=@_;
       return '<a href="javascript:storecmd(\'newrule_'.$id.'\');">'.&mt('New Calculation Rule').'</a>';
   }
   
   #
   # Add a calculation rule
   #
   
   sub add_calculation_rule {
       my ($id,$cangrade,$newcontent,%categories)=@_;
       unless ($cangrade) { return %categories; }
       my %newcontent=($newcontent => 1);
       foreach my $current (split(/\,/,$categories{$id.'_calculations'})) {
           $newcontent{$current}=1;
       }
       $categories{$id.'_calculations'}=join(',',sort(keys(%newcontent)));
       return %categories;
   }
   
   #
   # Delete a calculation rule
   #
   
   sub del_calculation_rule {
       my ($id,$cangrade,$delcontent,%categories)=@_;
       unless ($cangrade) { return %categories; }
       my @newcontent=();
       foreach my $current (split(/\,/,$categories{$id.'_calculations'})) {
           unless ($current=~/^\Q$delcontent\E\:/) {
               push(@newcontent,$current);
           }
       }
       $categories{$id.'_calculations'}=join(',',@newcontent);
       return %categories;
   }
   
   sub set_category_rules {
       my ($cangrade,$id,%categories)=@_;
       unless ($cangrade) { return %categories; }
       my %lt=&category_rule_codes();
       my @newrules=();
       foreach my $code ('',(keys(%lt))) {
           if ($env{'form.sel_'.$id.'_'.$code}) {
               push(@newrules,$env{'form.sel_'.$id.'_'.$code}.':'.$env{'form.val_'.$id.'_'.$code});
           }
       }
       $categories{$id.'_calculations'}=join(',',sort(@newrules));
       return %categories;
   }
   
   
   # === Category Editing
   
   #
   # Add to category content
   #
   
   sub add_category_content {
       my ($id,$cangrade,$newcontent,%categories)=@_;
       unless ($cangrade) { return %categories; }
       &Apache::lonnet::logthis("In here $newcontent");
       my %newcontent=($newcontent => 1);
       foreach my $current (split(/\,/,$categories{$id.'_content'})) {
           $newcontent{$current}=1;
       }
       $categories{$id.'_content'}=join(',',sort(keys(%newcontent)));
       return %categories;
   }
   
   #
   # Delete from category content
   #
   
   sub del_category_content {
       my ($id,$cangrade,$delcontent,%categories)=@_;
       unless ($cangrade) { return %categories; }
       my @newcontent=();
       foreach my $current (split(/\,/,$categories{$id.'_content'})) {
           unless ($current eq $delcontent) {
               push(@newcontent,$current);
           }
       }
       $categories{$id.'_content'}=join(',',@newcontent);
       return %categories;
   }
   
   #
   # Delete category
   #
   
   sub del_category {
       my ($id,$cangrade,%categories)=@_;
       unless ($cangrade) { return %categories; }
       my @neworder=();
       foreach my $currentid (split(/\,/,$categories{'order'})) {
           unless ($currentid eq $id) {
               push(@neworder,$currentid);
           }
       }
       $categories{'order'}=join(',',@neworder);
       return %categories;
   }
   
   #
   # Move category up
   #
   
   sub move_up_category {
       my ($id,$cangrade,%categories)=@_;
       my $currentpos=&current_pos_category($id,%categories);
       if ($currentpos<1) { return %categories; }
       return &move_category($id,$cangrade,$currentpos-1,%categories);
   }
   
   #
   # Move category down
   #
   
   sub move_down_category {
       my ($id,$cangrade,%categories)=@_;
       my $currentpos=&current_pos_category($id,%categories);
       my @order=split(/\,/,$categories{'order'});
       if ($currentpos>=$#order) { return %categories; }
       return &move_category($id,$cangrade,$currentpos+1,%categories);
   }
   
   #
   # Move a category to a desired position n the display order
   #
   
   sub move_category {
       my ($id,$cangrade,$ordernum,%categories)=@_;
       unless ($cangrade) { return %categories; }
       my @order=split(/\,/,$categories{'order'});
   # Where is the index currently?
       my $currentpos=&current_pos_category($id,%categories);
       if (defined($currentpos)) {
           if ($currentpos<$ordernum) {
   # This is moving to a higher index
   # ....X1234....
   # ....1234X....
               for (my $i=$currentpos;$i<$ordernum;$i++) {
                   $order[$i]=$order[$i+1];
               }
               $order[$ordernum]=$id;
           }
           if ($currentpos>$ordernum) {
   # This is moving to a lower index
   # ....1234X....
   # ....X1234....
               for (my $i=$currentpos;$i>$ordernum;$i--) {
                   $order[$i]=$order[$i-1];
               }
               $order[$ordernum]=$id;
           }
       }
       $categories{'order'}=join(',',@order);
       return %categories;
   }
   
   #
   #  Find current postion of a category in the order
   #
   
   sub current_pos_category {
       my ($id,%categories)=@_;
       my @order=split(/\,/,$categories{'order'});
       for (my $i=0;$i<=$#order;$i++) {
           if ($order[$i] eq $id) { return $i; }
       }
   # not found
       return undef;
   }
   
   #
   # Set name of a category
   #
   sub set_category_name {
       my ($cangrade,$id,$name,%categories)=@_;
       unless ($cangrade) { return %categories; }
       $categories{$id.'_name'}=$name;
       return %categories;
   }
   
   #
   # Set total of a category
   #
   sub set_category_total {
       my ($cangrade,$id,$totaltype,$total,%categories)=@_;
       unless ($cangrade) { return %categories; }
       if (($categories{$id.'_total'} eq '') && ($total=~/\d/)) {
           $totaltype='typein';
       }
       $categories{$id.'_totaltype'}=$totaltype;
       if ($totaltype eq 'default') {
           $categories{$id.'_total'}='';
       } else {
           $total=~s/\D//gs;
           unless ($total) { $total=0; }
           $categories{$id.'_total'}=$total;
       }
       return %categories;
   }
   
   sub set_category_weight {
       my ($cangrade,$id,$weight,%categories)=@_;
       unless ($cangrade) { return %categories; }
       $weight=~s/\D//gs;
       unless ($weight) { $weight=0; }
       $categories{$id.'_weight'}=$weight;
       return %categories;
   }
   
   sub set_category_displayachieved {
       my ($cangrade,$id,$value,%categories)=@_;
       unless ($cangrade) { return %categories; }
       unless (($value eq 'percent') || ($value eq 'points')) { $value='percent'; }
       $categories{$id.'_displayachieved'}=$value;
       return %categories;
   }
   
   
   #
   # === end category-related
   #
   #
 # Pass this two refs to arrays for the start and end color, and a number  # Pass this two refs to arrays for the start and end color, and a number
 # from 0 to 1 for how much of the latter you want to mix in. It will  # from 0 to 1 for how much of the latter you want to mix in. It will
 # return a string ready to show ("#FFC309");  # return a string ready to show ("#FFC309");

Removed from v.1.54  
changed lines
  Added in v.1.113


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>
500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.