--- loncom/interface/lonquickgrades.pm 2009/11/21 18:07:03 1.49
+++ loncom/interface/lonquickgrades.pm 2016/04/02 04:30:21 1.109
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Quick Student Grades Display
#
-# $Id: lonquickgrades.pm,v 1.49 2009/11/21 18:07:03 www Exp $
+# $Id: lonquickgrades.pm,v 1.109 2016/04/02 04:30:21 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,52 +76,315 @@ 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;
+ }
+ } 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;
+ }
+ }
+
# 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';
- my $notshowSPRSlink =
+ my $showPoints =
+ (($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'));
my $notshowTotals=
$env{'course.'.$env{'request.course.id'}.'.grading'} eq 'externalnototals';
+ my $showCategories=
+ $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'categories';
- # Create the nav map
- my $navmap = Apache::lonnavmaps::navmap->new();
- if (!defined($navmap)) {
- my $requrl = $r->uri;
- $env{'user.error.msg'} = "$requrl:bre:0:0:Navamp initialization failed.";
- return HTTP_NOT_ACCEPTABLE;
- }
-
- # Keep this hash in sync with %statusIconMap in lonnavmaps; they
- # should match color/icon
- my $res = $navmap->firstResource(); # temp resource to access constants
-
- # Header
my $title = "Grading and Statistics";#$showPoints ? "Points Display" : "Completed Problems Display";
my $brcrum = [{href=>"/adm/quickgrades",text => "Points Display"}];
$r->print(&Apache::loncommon::start_page($title,undef,
{'bread_crumbs' => $brcrum})
);
- if (!$showPoints && !$notshowSPRSlink ) {
- $r->print('
'
- .&mt('This screen shows how many problems (or problem parts) you have completed'
- .', and how many you have not yet done.'
- .' You can also look at [_1]a detailed score sheet[_2].'
- ,'','')
- .'
');
+ &startGradeScreen($r,'quick');
+
+#
+# Pick student
+#
+ my $uname;
+ my $udom;
+ my $stdid;
+ if ($cangrade) {
+ $r->print("
'.&mt('This may take a few moments to display.').'
');
+}
+
+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)=@_;
+
+ my $showPoints =
+ $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'standard';
+ my $notshowSPRSlink =
+ (($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 'categories'));
+ my $notshowTotals=
+ $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'externalnototals';
+ my $showCategories=
+ $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'categories';
+
+ my $allowed_to_view = &Apache::lonnet::allowed('vgr',$env{'request.course.id'});
+ my $allowed_to_edit = &Apache::lonnet::allowed('mgr',$env{'request.course.id'});
+
+ 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(' ',@notes)));
+ }
+
+
+ $r->print("\n".'
'
.&Apache::loncommon::end_data_table_header_row());
+#
+# Output of folder scores
+#
+
+ 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--; }
@@ -252,7 +553,7 @@ sub real_handler {
if ($total > 0) {
my $ratio;
$ratio = $correct / $total;
- my $color = mixColors(\@start, \@end, $ratio);
+ my $color = &mixColors(\@start, \@end, $ratio);
$r->print(&Apache::loncommon::start_data_table_row()
.'
');
@@ -285,7 +586,7 @@ sub real_handler {
# 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")."
");
@@ -293,28 +594,728 @@ sub real_handler {
.&Apache::loncommon::end_data_table_row());
}
+#
+# 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");
- $r->print(&Apache::loncommon::start_data_table_row()
+ $title = $showPoints ? "Points" : "Parts Done";
+ my $totaltitle = $showPoints ? &mt("Awarded Total Points") : &mt("Total Parts Done");
+ $r->print(&Apache::loncommon::start_data_table_row()
.'
'.$totaltitle.': '.$totalRight.' ');
- $r->print(&mt('Max Possible To Date')." $maxHelpLink: $totalPossible ");
- $title = $showPoints ? "Points" : "Parts";
- $r->print(&mt("Total $title In Course").': '.$totalParts.'
'
+ $r->print(&mt('Max Possible To Date')." $maxHelpLink: $totalPossible ");
+ $title = $showPoints ? "Points" : "Parts";
+ $r->print(&mt("Total $title In Course").': '.$totalParts.''
.&Apache::loncommon::end_data_table_row());
}
- $r->print(&Apache::loncommon::end_data_table()
- .&Apache::loncommon::end_page());
+ $r->print(&Apache::loncommon::end_data_table());
+}
- return OK;
+#
+# === 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 {
+
+ my ($r,$showPoints,$notshowTotals,
+ $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().
+ ''.
+ ''.
+ ''.
+ ''.
+ '');
+ }
+}
+
+#
+# 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('
'.&mt("Move").'
'.&mt('Action').'
');
+ }
+ $r->print('
'.&mt('Category').'
'.
+ '
'.&mt('Contents').'
'.
+ '
'.&mt('Total Points').'
'.
+ '
'.&mt('Calculation').'
'.
+ '
'.&mt('Relative Weight').'
'.
+ '
'.&mt('Achieved').'
');
+ $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(<
+
'); }
+ }
+# 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('
');
+}
+
+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='';
+ if ($cangrade) {
+ $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
+#
+
+sub move_category {
+ my ($id,$cangrade,$ordernum,%categories)=@_;
+ unless ($cangrade) { return %categories; }
+ my @order=split(/\,/,$categories{'order'});
+# Where is the index currently?
+ my $currentpos=¤t_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
# from 0 to 1 for how much of the latter you want to mix in. It will
# return a string ready to show ("#FFC309");
+
sub mixColors {
my $start = shift;
my $end = shift;
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.