');
+ $stdid='';
+ }
+ $r->print('\n"); }
+ &endGradeScreen($r);
+ return OK;
+
+}
+
+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_page());
+}
+
+
+sub getData {
+
+ my ($showPoints,$uname,$udom)=@_;
+
# Create the nav map
- my $navmap = Apache::lonnavmaps::navmap->new(
- $ENV{"request.course.fn"}.".db",
- $ENV{"request.course.fn"}."_parms.db", 1, 0);
+ my $navmap = Apache::lonnavmaps::navmap->new($uname,$udom);
- # Keep this hash in sync with %statusIconMap in lonnavmaps; they
- # should match color/icon
my $res = $navmap->firstResource(); # temp resource to access constants
- my $green = "#AAFFAA";
- my $red = "#FFAAAA";
- my $yellow = "#FFFFAA";
- my $orange = "#FFBB88";
- my $neutral = "";
- my %statusColorMap =
- ( $res->NETWORK_FAILURE => $neutral,
- $res->NOTHING_SET => $neutral,
- $res->CORRECT => $green,
- $res->EXCUSED => $green,
- $res->PAST_DUE_NO_ANSWER => $orange,
- $res->PAST_DUE_ANSWER_LATER => $orange,
- $res->ANSWER_OPEN => $orange,
- $res->OPEN_LATER => $neutral,
- $res->TRIES_LEFT => $neutral,
- $res->INCORRECT => $orange,
- $res->OPEN => $yellow,
- $res->ATTEMPTED => $yellow );
+ my $iterator = $navmap->getIterator(undef, undef, undef, 1);
+ my $depth = 1;
+ $iterator->next(); # ignore first BEGIN_MAP
+ my $curRes = $iterator->next();
- if (!defined($navmap)) {
- my $requrl = $r->uri;
- $ENV{'user.error.msg'} = "$requrl:bre:0:0:Course not initialized";
- return HTTP_NOT_ACCEPTABLE;
- }
-
- # Header
- $r->print(&Apache::loncommon::bodytag('Quick Score Display','',
- ''));
+ # General overview of the following: Walk along the course resources.
+ # For every problem in the resource, tell its parent maps how many
+ # parts and how many parts correct it has. After that, each map will
+ # have a count of the total parts underneath it, correct and otherwise.
+ # After that, we will walk through the course again and read off
+ # maps in order, with their data.
+ # (If in the future people decide not to be cumulative, only add
+ # the counts to the parent map.)
+ # For convenience, "totalParts" is also "totalPoints" when we're looking
+ # at points; I can't come up with a variable name that makes sense
+ # equally for both cases.
+
+ my $totalParts = 0; my $totalPossible = 0; my $totalRight = 0;
+ my $totalAttempted = 0;
+ my $now = time();
+ my $topLevelParts = 0; my $topLevelRight = 0; my $topLevelAttempted = 0;
- $navmap->init();
+ # Pre-run: Count parts correct
+ while ( $depth > 0 ) {
+ if ($curRes == $iterator->BEGIN_MAP()) {$depth++;}
+ if ($curRes == $iterator->END_MAP()) { $depth--; }
- # End navmap using boilerplate
+ if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout)
+ {
+ # Get number of correct, incorrect parts
+ my $parts = $curRes->parts();
+ my $partsRight = 0;
+ my $partsCount = 0;
+ my $partsAttempted = 0;
+ my $stack = $iterator->getStack();
+
+ for my $part (@{$parts}) {
+ my $completionStatus = $curRes->getCompletionStatus($part);
+ my $dateStatus = $curRes->getDateStatus($part);
+
+ if ($completionStatus == $curRes->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')) &&
+ ($dateStatus != $curRes->ANSWER_OPEN)) {
+ my $status = $curRes->simpleStatus($part);
+ if ($status == $curRes->ATTEMPTED) {
+ $partsAttempted += $curRes->weight($part);
+ $totalAttempted += $partsAttempted;
+ }
+ } else {
+ $score = &Apache::grades::compute_points($curRes->weight($part), $curRes->awarded($part));
+ }
+ $partsRight += $score;
+ $totalRight += $score;
+ $partsCount += $curRes->weight($part);
+
+ $curRes->{DATA}->{PROB_SCORE} += $score;
+ $curRes->{DATA}->{PROB_WEIGHT} += $curRes->weight($part);
+
+ if ($curRes->opendate($part) < $now) {
+ $totalPossible += $curRes->weight($part);
+ $curRes->{DATA}->{PROB_POSSIBLE} += $curRes->weight($part);
+ }
+ $totalParts += $curRes->weight($part);
+ } else {
+ my $status = $curRes->simpleStatus($part);
+ my $thisright = 0;
+ $partsCount++;
+ if ($status == $curRes->CORRECT ||
+ $status == $curRes->PARTIALLY_CORRECT ) {
+ $partsRight++;
+ $totalRight++;
+ $thisright = 1;
+ }
+
+ if ($status == $curRes->ATTEMPTED) {
+ $partsAttempted++;
+ $totalAttempted++;
+ }
+
+ my $dateStatus = $curRes->getDateStatus($part);
+ $totalParts++;
+ if ($curRes->opendate($part) < $now) {
+ $totalPossible++;
+ }
+ }
+ }
+
+ if ($depth == 1) { # in top-level only
+ $topLevelParts += $partsCount;
+ $topLevelRight += $partsRight;
+ $topLevelAttempted += $partsAttempted;
+ }
+
+ # Crawl down stack and record parts correct and total
+ for my $res (@{$stack}) {
+ if (ref($res) && $res->is_map()) {
+ if (!defined($res->{DATA}->{CHILD_PARTS})) {
+ $res->{DATA}->{CHILD_PARTS} = 0;
+ $res->{DATA}->{CHILD_CORRECT} = 0;
+ $res->{DATA}->{CHILD_ATTEMPTED} = 0;
+ }
+
+ $res->{DATA}->{CHILD_PARTS} += $partsCount;
+ $res->{DATA}->{CHILD_CORRECT} += $partsRight;
+ $res->{DATA}->{CHILD_ATTEMPTED} += $partsAttempted;
+ }
+ }
+ }
+ $curRes = $iterator->next();
+ }
+ return ($navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted);
+}
+
+#
+# Outputting everything.
+#
+
+sub outputTable {
+
+ my ($r,$showPoints,$notshowTotals,
+ $navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted)=@_;
+
+ my @start = (255, 255, 192);
+ my @end = (0, 192, 0);
+
+ my $indentString = ' ';
+
+ # Second pass: Print the maps.
+ $r->print(&Apache::loncommon::start_data_table()
+ .&Apache::loncommon::start_data_table_header_row()
+ .'
'
+ .&Apache::loncommon::end_data_table_row());
+ }
+ }
+ }
+
+ $curRes = $iterator->next();
+ }
+
+ # 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);
+ $r->print(&Apache::loncommon::start_data_table_row()
+ .'
');
+ $r->print(&mt("Problems Not Contained In A Folder")."
");
+ $r->print("$topLevelRight / $topLevelParts
"
+ .&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");
+
+ $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.'
'
+ .&Apache::loncommon::end_data_table_row());
+ }
+
+ $r->print(&Apache::loncommon::end_data_table());
+}
+
+#
+# === 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,%categories);
+#
+ if ($cangrade) {
+ $r->print(&Apache::loncommon::resourcebrowser_javascript().
+ ''.
+ ''.
+ ''.
+ ''.
+ '');
+ }
+#
+# Debug
+#
+# my %data=&dumpdata($navmap);
+# foreach (keys(%data)) {
+# $r->print("\n ".$_.'='.$data{$_});
+# }
+# my @debugarray=('5:1','4:3','1:1','5:5','6:7');
+# $r->print("Array: ".join(',',@debugarray).' ');
+# $r->print("0,0,0: ".join(',',&drop(0,0,0,@debugarray)).' ');
+# $r->print("1,0,0: ".join(',',&drop(1,0,0,@debugarray)).' ');
+# $r->print("0,1,0: ".join(',',&drop(0,1,0,@debugarray)).' ');
+# $r->print("1,1,0: ".join(',',&drop(1,1,0,@debugarray)).' ');
+#
+# $r->print("0,0,2: ".join(',',&drop(0,0,2,@debugarray)).' ');
+# $r->print("1,0,2: ".join(',',&drop(1,0,2,@debugarray)).' ');
+# $r->print("0,1,2: ".join(',',&drop(0,1,2,@debugarray)).' ');
+# $r->print("1,1,2: ".join(',',&drop(1,1,2,@debugarray)).' ');
+#
+# $r->print("0,0,4: ".join(',',&drop(0,0,4,@debugarray)).' ');
+# $r->print("1,0,4: ".join(',',&drop(1,0,4,@debugarray)).' ');
+# $r->print("0,1,4: ".join(',',&drop(0,1,4,@debugarray)).' ');
+# $r->print("1,1,4: ".join(',',&drop(1,1,4,@debugarray)).' ');
+#
+# $r->print("0,0,5: ".join(',',&drop(0,0,5,@debugarray)).' ');
+# $r->print("1,0,5: ".join(',',&drop(1,0,5,@debugarray)).' ');
+# $r->print("0,1,5: ".join(',',&drop(0,1,5,@debugarray)).' ');
+# $r->print("1,1,5: ".join(',',&drop(1,1,5,@debugarray)).' ');
+#
+# $r->print("0,0,7: ".join(',',&drop(0,0,7,@debugarray)).' ');
+# $r->print("1,0,7: ".join(',',&drop(1,0,7,@debugarray)).' ');
+# $r->print("0,1,7: ".join(',',&drop(0,1,7,@debugarray)).' ');
+# $r->print("1,1,7: ".join(',',&drop(1,1,7,@debugarray)).' ');
+
+}
+
+#
+# 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,%categories)=@_;
+
+ my $totalweight=0;
+ my $totalpoints=0;
+
+ $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,1,%categories);
+ unless ($possible) { next; }
+ $totalpoints+=$weight*$correct/$possible;
+ $totalweight+=$weight;
+ }
+#
+ my $perc=0;
+ if ($totalweight) { $perc=100.*$totalpoints/$totalweight; }
+
+ &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());
+}
- for my $part (@{$parts}) {
- if ($multipart && $part eq '0') { next; }
- $totalParts++;
- my $status = $curRes->status($part);
- my $color = $statusColorMap{$status};
- if ($color eq $green) { # I'm being bad here... ;-)
- $totalRight++; $totalCurrentlyPossible++;
+#
+# Output one category to table
+#
+
+sub output_and_calc_category {
+ my ($r,$cangrade,$navmaps,$id,$currentpos,$maxpos,$performance,$output,%categories)=@_;
+
+ my $iconpath = &Apache::loncommon::lonhttpdurl($r->dir_config('lonIconsURL') . "/");
+ my %lt=&Apache::lonlocal::texthash(
+ 'up' => 'Move Up',
+ 'dw' => 'Move Down');
+ if ($output) { $r->print("\n".&Apache::loncommon::start_data_table_row()); }
+
+ if ($output && $cangrade) {
+ $r->print(<
+
'); }
+ }
+# Re-adjust total points if force total
+ if ($categories{$id.'_totaltype'} eq 'typein') {
+ $totalpossible=1.*$categories{$id.'_total'};
+ }
- $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;
+}
- if (!($totalParts % 20)) { $r->rflush(); }
+#
+# 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;
+}
- $curRes = $iterator->next();
+#
+# 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;
+}
- $r->print(" \n");
- $r->print("
");
- $r->print("Total Parts Correct: $totalRight ");
- $r->print("Number Of Parts Possibly Correct: $totalCurrentlyPossible ");
- $r->print("Total Parts In Course: $totalParts
\n\n");
+#
+# 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;
+}
- $r->print("