--- loncom/interface/lonquickgrades.pm 2011/02/07 19:16:34 1.66
+++ loncom/interface/lonquickgrades.pm 2016/01/31 21:25:38 1.108
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Quick Student Grades Display
#
-# $Id: lonquickgrades.pm,v 1.66 2011/02/07 19:16:34 www Exp $
+# $Id: lonquickgrades.pm,v 1.108 2016/01/31 21:25:38 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -29,12 +29,31 @@
package Apache::lonquickgrades;
use strict;
-use Apache::Constants qw(:common :http);
+use Apache::Constants qw(:common :http REDIRECT);
use POSIX;
use Apache::loncommon;
use Apache::lonlocal;
use Apache::lonnet;
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 {
my $r = shift;
@@ -57,12 +76,46 @@ sub real_handler {
return OK;
}
+ my $cangrade=&Apache::lonnet::allowed('mgr');
+
+ unless ($cangrade) {
+ # Check for critical messages and redirect if present.
+ my ($redirect,$url) = &Apache::loncommon::critical_redirect(300);
+ if ($redirect) {
+ &Apache::loncommon::content_type($r,'text/html');
+ $r->header_out(Location => $url);
+ return REDIRECT;
+ }
+
+ # Check if course needs to be re-initialized
+ my $loncaparev = $r->dir_config('lonVersion');
+ my ($result,@reinit) = &Apache::loncommon::needs_coursereinit($loncaparev);
+
+ if ($result eq 'switch') {
+ &Apache::loncommon::content_type($r,'text/html');
+ $r->send_http_header;
+ $r->print(&Apache::loncommon::check_release_result(@reinit));
+ return OK;
+ } elsif ($result eq 'update') {
+ my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+ my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+ my ($furl,$ferr) = &Apache::lonuserstate::readmap("$cdom/$cnum");
+ if ($ferr) {
+ my $requrl = $r->uri;
+ $env{'user.error.msg'}="$requrl:bre:0:0:Course not initialized";
+ $env{'user.reinit'} = 1;
+ return HTTP_NOT_ACCEPTABLE;
+ }
+ }
+ }
+
# Send header, don't cache this page
&Apache::loncommon::no_cache($r);
$r->send_http_header;
my $showPoints =
- $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'standard';
+ (($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'standard')
+ || ($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'categories'));
my $notshowSPRSlink =
(($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'external')
|| ($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'externalnototals'));
@@ -80,13 +133,70 @@ sub real_handler {
&startGradeScreen($r,'quick');
- $r->rflush();
-
-# my $uname='korte';
-# my $udom='gerd';
-
+#
+# Pick student
+#
my $uname;
my $udom;
+ my $stdid;
+ if ($cangrade) {
+ $r->print("
".&mt("Download Multiple")." ".
+ ''."\n"
+ );
+ $r->print("".&mt("Display Individual")." ");
+ 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(''.&mt("Unknown Student/Employee ID: [_1]",$stdid).'
');
+ $stdid='';
+ }
+ $r->print('\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);
+ }
+}
+
+
sub startGradeScreen {
my ($r,$mode)=@_;
@@ -154,7 +294,7 @@ sub startGradeScreen {
if ($env{'form.symb'}) {
$r->print("\n".' '.
+ '&command=gradingmenu"> '.
&mt('Problem Grading').' ');
}
@@ -165,16 +305,76 @@ sub startGradeScreen {
sub endGradeScreen {
my ($r)=@_;
- $r->print('');
+ $r->print(''.&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(' '.
+ ''.&mt('Your Excel spreadsheet').' '."\n");
+ $r->rflush();
+ &excel_cleanup();
+ return;
}
+#
+# CSV data
+#
+# -----------
+
+#
+# Go through the complete course and collect data
+#
+
sub getData {
my ($showPoints,$uname,$udom)=@_;
- &Apache::lonnet::logthis("About to call with $uname $udom");
-
# Create the nav map
my $navmap = Apache::lonnavmaps::navmap->new($uname,$udom);
@@ -217,35 +417,40 @@ sub getData {
my $stack = $iterator->getStack();
for my $part (@{$parts}) {
- my $completionStatus = $curRes->getCompletionStatus($part);
my $dateStatus = $curRes->getDateStatus($part);
-
- if ($completionStatus == $curRes->EXCUSED()) {
+ my $weight = $curRes->weight($part);
+ my $problemstatus = $curRes->problemstatus($part);
+
+ if ($curRes->solved($part) eq 'excused') {
next;
}
if ($showPoints) {
my $score = 0;
# If we're not telling status and the answer date isn't passed yet,
# it's an "attempted" point
- if ((($curRes->problemstatus($part) eq 'no') ||
- ($curRes->problemstatus($part) eq 'no_feedback_ever')) &&
+ if ((($problemstatus eq 'no') ||
+ ($problemstatus eq 'no_feedback_ever')) &&
($dateStatus != $curRes->ANSWER_OPEN)) {
my $status = $curRes->simpleStatus($part);
if ($status == $curRes->ATTEMPTED) {
- $partsAttempted += $curRes->weight($part);
+ $partsAttempted += $weight;
$totalAttempted += $partsAttempted;
}
} else {
- $score = &Apache::grades::compute_points($curRes->weight($part), $curRes->awarded($part));
+ $score = &Apache::grades::compute_points($weight, $curRes->awarded($part));
}
$partsRight += $score;
$totalRight += $score;
- $partsCount += $curRes->weight($part);
+ $partsCount += $weight;
+
+ $curRes->{DATA}->{PROB_SCORE} += $score;
+ $curRes->{DATA}->{PROB_WEIGHT} += $weight;
if ($curRes->opendate($part) < $now) {
- $totalPossible += $curRes->weight($part);
+ $totalPossible += $weight;
+ $curRes->{DATA}->{PROB_POSSIBLE} += $weight;
}
- $totalParts += $curRes->weight($part);
+ $totalParts += $weight;
} else {
my $status = $curRes->simpleStatus($part);
my $thisright = 0;
@@ -262,7 +467,6 @@ sub getData {
$totalAttempted++;
}
- my $dateStatus = $curRes->getDateStatus($part);
$totalParts++;
if ($curRes->opendate($part) < $now) {
$totalPossible++;
@@ -376,7 +580,7 @@ sub outputTable {
# If there were any problems at the top level, print an extra "catchall"
if ($topLevelParts > 0) {
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(&mt("Problems Not Contained In A Folder")." ");
@@ -388,7 +592,7 @@ sub outputTable {
# show totals (if applicable), close table
#
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";
my $totaltitle = $showPoints ? &mt("Awarded Total Points") : &mt("Total Parts Done");
@@ -400,9 +604,7 @@ sub outputTable {
.&Apache::loncommon::end_data_table_row());
}
- $r->print(&Apache::loncommon::end_data_table()
- .&Apache::loncommon::end_page());
-
+ $r->print(&Apache::loncommon::end_data_table());
}
#
@@ -427,24 +629,53 @@ sub outputCategories {
# Loading old categories
%categories=&Apache::lonnet::dump('grading_categories',$cdom,$cnum);
# Storing
- if (($cangrade) && (($env{'form.storechanges'}) || ($env{'form.storemove'} ne ''))) {
+ 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
-# Form only generated if user can change the grading categories
+ &output_category_table($r,$cangrade,$navmap,1,%categories);
+#
if ($cangrade) {
- $r->print('');
+
+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;
}
#
@@ -454,9 +685,41 @@ sub outputCategories {
sub process_category_edits {
my ($r,$cangrade,%categories)=@_;
unless ($cangrade) { return %categories; }
-#
-# Business logic here
-#
+# 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;
}
@@ -465,26 +728,36 @@ sub process_category_edits {
#
sub output_category_table {
- my ($r,$cangrade,$navmaps,%categories)=@_;
- my $sum=0;
- $r->print(&Apache::loncommon::start_data_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);
+ &output_category_table_header($r,$cangrade);
+ }
#
my @order=split(/\,/,$categories{'order'});
#
-# FIXME: Debug only
- @order=('3131_4123_42124','4124_34231_3412');
-#
+ my %performance=&dumpdata($navmaps);
my $maxpos=$#order;
for (my $i=0;$i<=$maxpos;$i++) {
- my ($value,$weight)=&output_and_calc_category($r,$cangrade,$navmaps,$order[$i],$i,$maxpos,%categories);
+ 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;
}
#
- &bottom_line_category($r,$cangrade);
-#
- $r->print(&Apache::loncommon::end_data_table());
- return $sum;
+ 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 {
@@ -495,9 +768,10 @@ sub output_category_table_header {
}
$r->print(' '.&mt('Category').' '.
''.&mt('Contents').' '.
+ ''.&mt('Total Points').' '.
''.&mt('Calculation').' '.
- ''.&mt('Weight').' '.
- ''.&mt('Percent Overall').' ');
+ ''.&mt('Relative Weight').' '.
+ ''.&mt('Achieved').' ');
$r->print(&Apache::loncommon::end_data_table_header_row());
}
@@ -507,25 +781,25 @@ sub output_category_table_header {
#
sub output_and_calc_category {
- my ($r,$cangrade,$navmaps,$id,$currentpos,$maxpos,%categories)=@_;
- my $value=0;
- my $weight=0;
- my $iconpath = &Apache::loncommon::lonhttpdurl($r->dir_config('lonIconsURL') . "/");
- my %lt=&Apache::lonlocal::texthash(
+ 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("\n".&Apache::loncommon::start_data_table_row());
- if ($cangrade) {
$r->print(<
-
+
-
+
@@ -540,20 +814,215 @@ ENDMOVE
}
}
$r->print("\n\n\n");
+ $r->print(''.&mt('Delete').' ');
+ $r->print(' ');
+ } elsif ($output) {
+ $r->print(''.$categories{$id.'_name'}.' ');
+ }
+# Content display and summing up of points
+ my $totalpossible=0;
+ my $totalcorrect=0;
+ my @individual=();
+ if ($output) { $r->print(''); }
+ 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('');
+ $r->print(&Apache::lonnet::gettitle($contentid).' ('.&numberout($correct).'/'.&numberout($possible).')');
+ if ($cangrade) {
+ $r->print(' '.&mt('Delete').' ');
+ }
+ $r->print(' ');
+ }
+ }
+ if ($output) {
+ $r->print(' ');
+ if ($cangrade) {
+ $r->print(' '.&Apache::loncommon::selectresource_link('quickform','addcont_'.$id,&mt('Add Problem or Folder')).' ');
+ }
+ $r->print(''.&mt('Total raw points: [_1]/[_2]',&numberout($totalcorrect),&numberout($totalpossible)).'
');
+ $r->print(' ');
+ }
+# Total
+ if ($output) { $r->print(''); }
+ if ($cangrade) {
+ if ($output) {
+ $r->print(
+ ''.
+ ''.&mt('default').' '.
+ ''.&mt('Type-in value').' '.
+ ' '.
+ ' ');
+ }
+ } else {
+ if ($output) {
+ $r->print(' '.($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(''.&mt('Adjusted raw points: [_1]/[_2]',&numberout($totalcorrect),&numberout($totalpossible)).'
');
+ }
+
+
+# Calculation
+ if ($output) { $r->print(' '); }
+ foreach my $calcrule (split(/\,/,$categories{$id.'_calculations'})) {
+ if ($output) { $r->print(''); }
+ my ($code,$value)=split(/\:/,$calcrule);
+ if ($output) { $r->print(&pretty_prt_rule($cangrade,$id,$code,$value)); }
+ if ($cangrade) {
+ if ($output) { $r->print(' '.&mt('Delete').' '); }
+ }
+ 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(' '); }
+ }
+# Re-adjust total points if force total
+ if ($categories{$id.'_totaltype'} eq 'typein') {
+ $totalpossible=1.*$categories{$id.'_total'};
+ }
+ if ($output) {
+ $r->print(' ');
+ if ($cangrade) { $r->print(' '.&new_calc_rule_form($id)); }
+ $r->print(''.&mt('Calculated points: [_1]/[_2]',&numberout($totalcorrect),&numberout($totalpossible)).'
');
+ $r->print(' ');
+ }
+#
+# Prepare for export
+#
+# Weight
+ my $weight=$categories{$id.'_weight'};
+ unless (1.*$weight>0) { $weight=0; }
+ if ($cangrade) {
+ if ($output) {
+ $r->print(''.
+ ' ');
+ }
+ } else {
+ if ($output) {
+ $r->print(''.$weight.' ');
+ }
}
- $r->print(&Apache::loncommon::end_data_table_row()."\n");
- return ($value,$weight);
+# Achieved
+ my $type=$categories{$id.'_displayachieved'};
+ unless (($type eq 'percent') || ($type eq 'points')) { $type='points'; }
+ if ($output) { $r->print(''); }
+ if ($cangrade) {
+ if ($output) {
+ $r->print(''.
+ ''.&mt('percent').' '.
+ ''.&mt('points').' '.
+ ' ');
+ }
+ }
+ if ($output) {
+ $r->print('');
+ 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('
');
+ }
+ if ($output) { $r->print(' '); }
+
+ 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)=@_;
+ my ($r,$cangrade,$perc)=@_;
+ $r->print(&Apache::loncommon::start_data_table_row());
+ if ($cangrade) {
+ $r->print(''.&mt('Create New Category').' ');
+ }
+ $r->print(''.&mt('Total: [_1] percent',&numberout($perc)).' ');
}
+sub numberout {
+ my ($number)=@_;
+ my $printout=sprintf("%.3f", $number);
+ $printout=~s/0+$//;
+ $printout=~s/\.$//;
+ return $printout;
+}
#
# Make one new category
#
@@ -573,9 +1042,170 @@ sub make_new_category {
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='';
+ if ($cangrade) {
+ $ret.='';
+ foreach my $calc (''=>'',sort(keys(%lt))) {
+ $ret.=' '.$lt{$calc}.'';
+ }
+ $ret.=' N= ';
+ } else {
+ $ret.=$lt{$code}.'; N='.$value;
+ }
+ $ret.='';
+ return $ret;
+}
+
+sub new_calc_rule_form {
+ my ($id)=@_;
+ return ''.&mt('New Calculation Rule').' ';
+}
+
+#
+# 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=¤t_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=¤t_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
#
@@ -628,13 +1258,51 @@ sub current_pos_category {
# Set name of a category
#
sub set_category_name {
- my ($canedit,$id,$name,%categories)=@_;
- unless ($canedit) { return %categories; }
+ 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
#
#