Annotation of loncom/interface/lonquickgrades.pm, revision 1.104

1.1       bowersj2    1: # The LearningOnline Network with CAPA
                      2: # Quick Student Grades Display
                      3: #
1.104   ! musolffc    4: # $Id: lonquickgrades.pm,v 1.103 2012/01/02 05:08:03 raeburn Exp $
1.1       bowersj2    5: #
                      6: # Copyright Michigan State University Board of Trustees
                      7: #
                      8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
                      9: #
                     10: # LON-CAPA is free software; you can redistribute it and/or modify
                     11: # it under the terms of the GNU General Public License as published by
                     12: # the Free Software Foundation; either version 2 of the License, or
                     13: # (at your option) any later version.
                     14: #
                     15: # LON-CAPA is distributed in the hope that it will be useful,
                     16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
                     17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     18: # GNU General Public License for more details.
                     19: #
                     20: # You should have received a copy of the GNU General Public License
                     21: # along with LON-CAPA; if not, write to the Free Software
                     22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
                     23: #
                     24: # /home/httpd/html/adm/gpl.txt
                     25: #
                     26: # http://www.lon-capa.org/
                     27: #
                     28: 
                     29: package Apache::lonquickgrades;
                     30: 
                     31: use strict;
1.104   ! musolffc   32: use Apache::Constants qw(:common :http REDIRECT);
1.5       bowersj2   33: use POSIX;
1.25      www        34: use Apache::loncommon;
                     35: use Apache::lonlocal;
1.36      albertel   36: use Apache::lonnet;
1.38      bowersj2   37: use Apache::grades;
1.100     www        38: use Apache::loncoursedata;
1.101     www        39: use Apache::lonstudentassessment;
1.1       bowersj2   40: 
1.102     www        41: use Time::HiRes;
                     42: use Spreadsheet::WriteExcel;
                     43: use Spreadsheet::WriteExcel::Utility();
                     44: #
                     45: # Excel data
                     46: #
                     47: my $excel_sheet;
                     48: my $excel_workbook;
                     49: my $filename;
                     50: my $format;
                     51: my $request_aborted;
                     52: my $header_row;
                     53: my $cols_output;
                     54: my %prog_state;
                     55: 
                     56: 
1.1       bowersj2   57: sub handler {
                     58:     my $r = shift;
1.5       bowersj2   59:     return real_handler($r);
                     60: }
                     61: 
                     62: sub real_handler {
                     63:     my $r = shift;
1.1       bowersj2   64: 
1.104   ! musolffc   65:     # Check for critical messages and redirect if present.  
        !            66:     my ($redirect,$url) = &Apache::loncommon::critical_redirect(300);
        !            67:     if ($redirect) {
        !            68:         &Apache::loncommon::content_type($r,'text/html');
        !            69:         $r->header_out(Location => $url);
        !            70:         return REDIRECT;
        !            71:     }
        !            72: 
1.1       bowersj2   73:     &Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});
                     74: 
                     75:     # Handle header-only request
1.40      albertel   76:     if ($env{'browser.mathml'}) {
                     77: 	&Apache::loncommon::content_type($r,'text/xml');
                     78:     } else {
                     79: 	&Apache::loncommon::content_type($r,'text/html');
                     80:     }
1.1       bowersj2   81:     if ($r->header_only) {
1.40      albertel   82: 	$r->send_http_header;
1.1       bowersj2   83:         return OK;
                     84:     }
                     85: 
                     86:     # Send header, don't cache this page
                     87:     &Apache::loncommon::no_cache($r);
                     88:     $r->send_http_header;
                     89: 
1.55      www        90:     my $showPoints =
1.83      www        91:         (($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'standard')
                     92:       || ($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'categories'));
1.55      www        93:     my $notshowSPRSlink =
1.49      www        94:         (($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'external')
                     95:       || ($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'externalnototals'));
                     96:     my $notshowTotals=
                     97:         $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'externalnototals';
1.50      www        98:     my $showCategories=
                     99:         $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'categories';
                    100: 
1.17      bowersj2  101: 
1.47      schafran  102:     my $title = "Grading and Statistics";#$showPoints ? "Points Display" : "Completed Problems Display";
1.46      raeburn   103:     my $brcrum = [{href=>"/adm/quickgrades",text => "Points Display"}];
                    104:     $r->print(&Apache::loncommon::start_page($title,undef,
                    105:                                             {'bread_crumbs' => $brcrum})
                    106:              );
1.1       bowersj2  107: 
1.55      www       108:     &startGradeScreen($r,'quick');
1.17      bowersj2  109: 
1.74      www       110:     my $cangrade=&Apache::lonnet::allowed('mgr');
                    111: #
                    112: # Pick student
                    113: #
1.54      www       114:     my $uname;
                    115:     my $udom;
1.74      www       116:     my $stdid;
                    117:     if ($cangrade) {
1.101     www       118:         $r->print("<h2>".&mt("Download Multiple")."</h2>".
                    119:                   '<table cellspacing="5">'."\n".
                    120:                   '<tr>'.
                    121:                   '<td align="center"><b>'.&mt('Sections').'</b>'.
                    122:                   &Apache::loncommon::help_open_topic("Chart_Sections").
                    123:                   '</td>'.
                    124:                   '<td align="center"><b>'.&mt('Groups').'</b>'.
                    125:                   '</td>'.
                    126:                   '<td align="center"><b>'.&mt('Student Data').'</b>'.
                    127:                   &Apache::loncommon::help_open_topic("Chart_Student_Data").
                    128:                   '</td>'.
                    129:                   '<td align="center"><b>'.&mt('Access Status').'</b>'.
                    130:                   &Apache::loncommon::help_open_topic("Chart_Enrollment_Status").
                    131:                   '</td>'.
                    132:                   '<td align="center"><b>'.&mt('Output Format').'</b>'.
                    133:                   &Apache::loncommon::help_open_topic("Chart_Output_Formats").
                    134:                   '</td><td>&nbsp;</td></tr>'."\n".
                    135:                   '<tr><td align="center">'."\n".
                    136:                   &Apache::lonstatistics::SectionSelect('Section','multiple',5).
                    137:                   '</td><td align="center">'.
                    138:                   &Apache::lonstatistics::GroupSelect('Group','multiple',5).
                    139:                   '</td><td align="center">'.
                    140:                   &Apache::lonstatistics::StudentDataSelect('StudentData','multiple',5,undef).
                    141:                   '</td><td>'."\n".
                    142:                   &Apache::lonhtmlcommon::StatusOptions(undef,undef,5).
                    143:                   '</td><td>'."\n".
                    144:                   &Apache::lonstudentassessment::CreateAndParseOutputSelector().
                    145:                   '</td><td>'.
                    146:                   '<input type="submit" name="download" value="'.&mt('Display/Download Multiple Students').'" />'.
                    147:                   '</td></tr>'."\n".
                    148:                   '</table>'."\n"
                    149:                  );
                    150:         $r->print("<hr /><h2>".&mt("Display Individual")."</h2>");
1.74      www       151:         if ($env{'form.uname'}) { $uname=$env{'form.uname'}; }
                    152:         if ($env{'form.udom'}) { $udom=$env{'form.udom'}; }
                    153:         if ($env{'form.id'}) { $stdid=$env{'form.id'}; }
                    154:         if (($stdid) && ($udom)) {
                    155:             $uname=(&Apache::lonnet::idget($udom,$stdid))[1];
                    156:         }
1.75      www       157:         if (($stdid) && (!$uname)) {
                    158:             $r->print('<p><span class="LC_warning">'.&mt("Unknown Student/Employee ID: [_1]",$stdid).'</span></p>');
                    159:             $stdid='';
                    160:         }
1.74      www       161:         $r->print('<form method="post" name="quickform" action="/adm/quickgrades">');
                    162:         my $chooseopt=&Apache::loncommon::select_dom_form($udom,'udom').' '.
                    163:            &Apache::loncommon::selectstudent_link('quickform','uname','udom');
                    164:         $r->print("<p>\n".&Apache::loncommon::studentbrowser_javascript()."\n");
                    165:         $r->print(&mt('For User [_1] or Student/Employee ID [_2] at Domain [_3]'
                    166:                  ,'<input type="text" value="'.$uname.'" size="12" name="uname" />'
                    167:                  ,'<input type="text" value="'.$stdid.'" size="12" name="id" /> '
1.101     www       168:                  ,$chooseopt).
                    169:                  '&nbsp;&nbsp;<input type="submit" name="display" value="'.&mt('Display Individual Student').'" /></p>');
1.75      www       170:         if (($uname) && ($udom)) {
                    171:             $r->print('<p>'.&mt('Full Name: [_1]',&Apache::loncommon::plainname($uname,$udom)).'</p>');
                    172:         }
1.74      www       173:     }
                    174:     $r->rflush();
1.53      www       175: 
                    176:     my ($navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted)=
                    177:        &getData($showPoints,$uname,$udom);
                    178: 
                    179:     if ($showCategories) {
                    180:        &outputCategories($r,$showPoints,$notshowTotals,
                    181:                  $navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted);
                    182:     } else {
                    183:        &outputTable($r,$showPoints,$notshowTotals,
1.51      www       184:                  $navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted);
1.53      www       185:     }
1.74      www       186:     if ($cangrade) { $r->print("\n</form>\n"); }
1.55      www       187:     &endGradeScreen($r);
1.51      www       188:     return OK;
                    189: 
                    190: }
1.2       bowersj2  191: 
1.99      www       192: sub getStudentCatGrade {
                    193:     my ($uname,$udom,%categories)=@_;
                    194:     my ($navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted)=
                    195:        &getData(1,$uname,$udom);
                    196:     return &output_category_table(undef,0,$navmap,0,%categories);
                    197: }
                    198: 
                    199: sub getAllStudentData {
                    200:     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
                    201:     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
1.100     www       202: 
1.99      www       203:     my %categories=&Apache::lonnet::dump('grading_categories',$cdom,$cnum);
1.100     www       204: 
                    205:     my $classlist = &Apache::loncoursedata::get_classlist();
                    206: 
                    207:     my $statusidx   = &Apache::loncoursedata::CL_STATUS();
                    208:     my $usernameidx = &Apache::loncoursedata::CL_SNAME();
                    209:     my $domainidx   = &Apache::loncoursedata::CL_SDOM();
                    210:     my $fullnameidx = &Apache::loncoursedata::CL_FULLNAME();
                    211: 
                    212:     foreach my $key (keys(%{$classlist})) {
                    213:         my $student = $classlist->{$key};
                    214:         my $perc=&getStudentCatGrade($classlist->{$student}->[$usernameidx],
                    215:                                      $classlist->{$student}->[$domainidx],
                    216:                                      %categories);
                    217:     }
1.99      www       218: }
                    219: 
                    220: 
1.55      www       221: sub startGradeScreen {
                    222:     my ($r,$mode)=@_;
                    223: 
                    224:     my $showPoints =
                    225:         $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'standard';
                    226:     my $notshowSPRSlink =
                    227:         (($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'external')
                    228:       || ($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'externalnototals')
                    229:       || ($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'categories'));
                    230:     my $notshowTotals=
                    231:         $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'externalnototals';
                    232:     my $showCategories=
                    233:         $env{'course.'.$env{'request.course.id'}.'.grading'} eq 'categories';
                    234: 
                    235:     my $allowed_to_view =  &Apache::lonnet::allowed('vgr',$env{'request.course.id'});
                    236:     my $allowed_to_edit =  &Apache::lonnet::allowed('mgr',$env{'request.course.id'});
                    237: 
                    238:     if ($allowed_to_view) {
1.61      raeburn   239:        my @notes;
                    240:        push(@notes,&mt('Students do not see total points.')) if ($notshowTotals);
                    241:        push(@notes,&mt('Students do not see link to spreadsheet.')) if ($notshowSPRSlink);
                    242:        push(@notes,&mt('Students will see points based on problem weights.')) if ($showPoints);
                    243:        push(@notes,&mt('Students will see points based on categories.')) if ($showCategories);
                    244:        push(@notes, &Apache::lonhtmlcommon::coursepreflink(&mt('Grade display settings'),'grading'));
                    245:        $r->print(&Apache::loncommon::head_subbox(join('&nbsp;&nbsp;',@notes)));
1.55      www       246:     }
                    247: 
                    248: 
1.56      www       249:     $r->print("\n".'<ul class="LC_TabContentBigger" id="main">');
1.57      www       250:     $r->print("\n".'<li'.($mode eq 'quick'?' class="active"':'').'><a href="/adm/quickgrades"><b>&nbsp;&nbsp;&nbsp;&nbsp;'.
1.62      www       251:                                           ($showPoints?&mt('Individual Points Overview'):($showCategories?&mt('Grades Overview'):&mt('Completion Overview'))).
1.57      www       252:                                           '&nbsp;&nbsp;&nbsp;&nbsp;</b></a></li>');
1.55      www       253: 
                    254:     if (!($showPoints || $notshowSPRSlink) || ($allowed_to_view)) {
1.56      www       255:        $r->print("\n".'<li'.($mode eq 'spreadsheet'?' class="active"':'').'><a href="/adm/'.($allowed_to_view?'classcalc':'studentcalc').'"><b>'.
1.57      www       256:                                                                  &mt('Spreadsheet (Detailed)').'</b></a></li>');
1.55      www       257:     }
1.58      www       258:     if ($allowed_to_view) {
1.63      www       259:        $r->print("\n".'<li'.($mode eq 'statistics'?' class="active"':'').'><a href="/adm/statistics"><b>'.
                    260:                                                                  &mt('Statistics and Reports').'</b></a></li>');
                    261: 
1.58      www       262:        $r->print("\n".'<li'.($mode eq 'chart'?' class="active"':'').'><a href="/adm/statistics?reportSelected=student_assessment"><b>'.
                    263:                                                                  &mt('Assessment Overview Chart').'</b></a></li>');
                    264: 
                    265:     }
1.59      www       266:     if ($allowed_to_edit) {
                    267:        $r->print("\n".'<li'.($mode eq 'grading'?' class="active"':'').'><a href="/adm/grades"><b>&nbsp;&nbsp;&nbsp;&nbsp;'.
1.66      www       268:                                                                  &mt('Content Grading').'&nbsp;&nbsp;&nbsp;&nbsp;</b></a></li>');
                    269:        if ($env{'form.symb'}) {
                    270:           $r->print("\n".'<li'.($mode eq 'probgrading'?' class="active"':'').'><a href="/adm/grades?symb='.
                    271:                                               &Apache::lonhtmlcommon::entity_encode($env{'form.symb'}).
1.103     raeburn   272:                                               '&amp;command=gradingmenu"><b>&nbsp;&nbsp;&nbsp;&nbsp;'.
1.66      www       273:                                               &mt('Problem Grading').'&nbsp;&nbsp;&nbsp;&nbsp;</b></a></li>');
                    274: 
                    275:        }
1.59      www       276:     }
1.56      www       277:     $r->print("\n".'</ul>'."\n");
1.55      www       278:     $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;">');
                    279: }
                    280: 
                    281: sub endGradeScreen {
                    282:    my ($r)=@_;
1.72      www       283:    $r->print('</div></div></div>'.&Apache::loncommon::end_page());
1.55      www       284: }
                    285: 
1.102     www       286: # -----------
                    287: 
                    288: 
                    289: sub excel_cleanup {
                    290:     undef ($excel_sheet);
                    291:     undef ($excel_workbook);
                    292:     undef ($filename);
                    293:     undef ($format);
                    294: }
                    295: 
                    296: 
                    297: sub excel_initialize {
                    298:     my ($r) = @_;
                    299: 
                    300:     &excel_cleanup();
                    301: 
                    302:     # Create sheet
                    303:     ($excel_workbook,$filename,$format)=
                    304:         &Apache::loncommon::create_workbook($r);
                    305:     return if (! defined($excel_workbook));
                    306:    #
                    307:    # Add a worksheet
                    308:     my $sheetname = $env{'course.'.$env{'request.course.id'}.'.description'};
                    309:     $sheetname = &Apache::loncommon::clean_excel_name($sheetname);
                    310:     $excel_sheet = $excel_workbook->addworksheet($sheetname);
                    311:    #
                    312:    # Put the course description in the header
                    313:     $excel_sheet->write($header_row,$cols_output++,
                    314:                    $env{'course.'.$env{'request.course.id'}.'.description'},
                    315:                         $format->{'h1'});
                    316: } 
                    317: 
                    318: sub excel_finish {
                    319:     my ($r) = @_;
                    320:     if ($request_aborted || ! defined($excel_sheet)) {
                    321:         &excel_cleanup();
                    322:         return;
                    323:     }
                    324:     #
                    325:     # Write the excel file
                    326:     $excel_workbook->close();
                    327:     #
                    328:     # Close the progress window
                    329:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
                    330:     #
                    331:     # Tell the user where to get their excel file
                    332:     $r->print('<br />'.
                    333:               '<a href="'.$filename.'">'.&mt('Your Excel spreadsheet').'</a>'."\n");
                    334:     $r->rflush();
                    335:     &excel_cleanup();
                    336:     return;
                    337: }
                    338: 
                    339: 
                    340: #
                    341: # CSV data
                    342: #
                    343: # -----------
                    344: 
                    345: #
                    346: # Go through the complete course and collect data
                    347: #
1.55      www       348: 
1.51      www       349: sub getData {
                    350: 
1.53      www       351:     my ($showPoints,$uname,$udom)=@_;
                    352: 
1.51      www       353:     # Create the nav map
1.53      www       354:     my $navmap = Apache::lonnavmaps::navmap->new($uname,$udom);
1.51      www       355: 
                    356:     my $res = $navmap->firstResource(); # temp resource to access constants
1.1       bowersj2  357: 
                    358:     my $iterator = $navmap->getIterator(undef, undef, undef, 1);
                    359:     my $depth = 1;
                    360:     $iterator->next(); # ignore first BEGIN_MAP
                    361:     my $curRes = $iterator->next();
1.4       bowersj2  362:     
1.5       bowersj2  363:     # General overview of the following: Walk along the course resources.
                    364:     # For every problem in the resource, tell its parent maps how many
                    365:     # parts and how many parts correct it has. After that, each map will
                    366:     # have a count of the total parts underneath it, correct and otherwise.
                    367:     # After that, we will walk through the course again and read off
                    368:     # maps in order, with their data. 
                    369:     # (If in the future people decide not to be cumulative, only add
                    370:     #  the counts to the parent map.)
1.17      bowersj2  371:     # For convenience, "totalParts" is also "totalPoints" when we're looking
                    372:     #  at points; I can't come up with a variable name that makes sense
                    373:     #  equally for both cases.
1.5       bowersj2  374: 
                    375:     my $totalParts = 0; my $totalPossible = 0; my $totalRight = 0;
1.28      bowersj2  376:     my $totalAttempted = 0;
1.14      bowersj2  377:     my $now = time();
1.28      bowersj2  378:     my $topLevelParts = 0; my $topLevelRight = 0; my $topLevelAttempted = 0;
1.5       bowersj2  379: 
                    380:     # Pre-run: Count parts correct
1.1       bowersj2  381:     while ( $depth > 0 ) {
                    382:         if ($curRes == $iterator->BEGIN_MAP()) {$depth++;}
                    383:         if ($curRes == $iterator->END_MAP()) { $depth--; }
                    384: 
1.7       bowersj2  385:         if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout)
1.5       bowersj2  386:         {
                    387:             # Get number of correct, incorrect parts
                    388:             my $parts = $curRes->parts();
                    389:             my $partsRight = 0;
1.17      bowersj2  390: 	    my $partsCount = 0;
1.28      bowersj2  391: 	    my $partsAttempted = 0;
1.5       bowersj2  392:             my $stack = $iterator->getStack();
                    393:             
                    394:             for my $part (@{$parts}) {
1.28      bowersj2  395: 		my $dateStatus = $curRes->getDateStatus($part);
1.97      www       396:                 my $weight = $curRes->weight($part);
                    397:                 my $problemstatus = $curRes->problemstatus($part);
                    398: 
1.98      www       399:                 if ($curRes->solved($part) eq 'excused') {
1.21      matthew   400:                     next;
                    401:                 }
1.17      bowersj2  402: 		if ($showPoints) {
1.28      bowersj2  403: 		    my $score = 0;
                    404: 		    # If we're not telling status and the answer date isn't passed yet, 
                    405: 		    # it's an "attempted" point
1.97      www       406: 		    if ((($problemstatus eq 'no') ||
                    407:                          ($problemstatus eq 'no_feedback_ever')) &&
1.28      bowersj2  408: 			($dateStatus != $curRes->ANSWER_OPEN)) {
1.31      albertel  409: 			my $status = $curRes->simpleStatus($part);
                    410: 			if ($status == $curRes->ATTEMPTED) {
1.97      www       411: 			    $partsAttempted += $weight;
1.31      albertel  412: 			    $totalAttempted += $partsAttempted;
                    413: 			}
1.28      bowersj2  414: 		    } else {
1.97      www       415: 			$score = &Apache::grades::compute_points($weight, $curRes->awarded($part));
1.28      bowersj2  416: 		    }
1.17      bowersj2  417: 		    $partsRight += $score;
                    418: 		    $totalRight += $score;
1.97      www       419: 		    $partsCount += $weight;
1.18      bowersj2  420: 
1.83      www       421:                     $curRes->{DATA}->{PROB_SCORE}  += $score;
1.97      www       422:                     $curRes->{DATA}->{PROB_WEIGHT} += $weight;
1.83      www       423: 
1.17      bowersj2  424: 		    if ($curRes->opendate($part) < $now) {
1.97      www       425: 			$totalPossible += $weight;
                    426:                         $curRes->{DATA}->{PROB_POSSIBLE} += $weight;
1.17      bowersj2  427: 		    }
1.97      www       428: 		    $totalParts += $weight;
1.17      bowersj2  429: 		} else {
1.27      bowersj2  430: 		    my $status = $curRes->simpleStatus($part);
1.17      bowersj2  431: 		    my $thisright = 0;
                    432: 		    $partsCount++;
1.37      albertel  433: 		    if ($status == $curRes->CORRECT ||
                    434: 			$status == $curRes->PARTIALLY_CORRECT ) {
1.17      bowersj2  435: 			$partsRight++;
                    436: 			$totalRight++;
                    437: 			$thisright = 1;
                    438: 		    }
1.28      bowersj2  439: 
                    440: 		    if ($status == $curRes->ATTEMPTED) {
                    441: 			$partsAttempted++;
                    442: 			$totalAttempted++;
                    443: 		    }
1.17      bowersj2  444: 		    
1.19      bowersj2  445: 		    $totalParts++;
1.17      bowersj2  446: 		    if ($curRes->opendate($part) < $now) {
                    447: 			$totalPossible++;
                    448: 		    }
                    449: 		}
1.5       bowersj2  450:             }
1.15      bowersj2  451: 
                    452:             if ($depth == 1) { # in top-level only
1.19      bowersj2  453: 		$topLevelParts += $partsCount;
1.15      bowersj2  454: 		$topLevelRight += $partsRight;
1.28      bowersj2  455: 		$topLevelAttempted += $partsAttempted;
1.15      bowersj2  456: 	    }
                    457: 
1.5       bowersj2  458:             # Crawl down stack and record parts correct and total
                    459:             for my $res (@{$stack}) {
                    460:                 if (ref($res) && $res->is_map()) {
                    461:                     if (!defined($res->{DATA}->{CHILD_PARTS})) {
                    462:                         $res->{DATA}->{CHILD_PARTS} = 0;
                    463:                         $res->{DATA}->{CHILD_CORRECT} = 0;
1.28      bowersj2  464: 			$res->{DATA}->{CHILD_ATTEMPTED} = 0;
1.5       bowersj2  465:                     }
                    466:                     
1.17      bowersj2  467:                     $res->{DATA}->{CHILD_PARTS} += $partsCount;
1.5       bowersj2  468:                     $res->{DATA}->{CHILD_CORRECT} += $partsRight;
1.28      bowersj2  469: 		    $res->{DATA}->{CHILD_ATTEMPTED} += $partsAttempted;
1.5       bowersj2  470:                 }
                    471:             }
                    472:         }
                    473:         $curRes = $iterator->next();
                    474:     }
1.51      www       475:     return ($navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted);
                    476: }
                    477: 
                    478: #
                    479: # Outputting everything.
                    480: #
                    481: 
                    482: sub outputTable {
1.5       bowersj2  483: 
1.51      www       484:     my ($r,$showPoints,$notshowTotals,
                    485:            $navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted)=@_;
1.5       bowersj2  486: 
1.7       bowersj2  487:     my @start = (255, 255, 192);
1.5       bowersj2  488:     my @end   = (0, 192, 0);
                    489: 
                    490:     my $indentString = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
                    491: 
                    492:     # Second pass: Print the maps.
1.43      bisitz    493:     $r->print(&Apache::loncommon::start_data_table()
                    494:              .&Apache::loncommon::start_data_table_header_row()
                    495:              .'<th>'.&mt('Folder').'</th>');
1.51      www       496:     my $title = &mt($showPoints ? "Points Scored" : "Done");
1.28      bowersj2  497:     if ($totalAttempted) {
1.51      www       498:         $title .= " / " . &mt("Attempted");
1.28      bowersj2  499:     }
1.49      www       500:     $r->print("<th>$title".($notshowTotals?'':" / ".&mt('Total')).'</th>'
1.43      bisitz    501:              .&Apache::loncommon::end_data_table_header_row());
1.51      www       502: #
                    503: # Output of folder scores
                    504: #
                    505: 
                    506:     my $iterator = $navmap->getIterator(undef, undef, undef, 1);
                    507:     my $depth = 1;
                    508:     $iterator->next(); # ignore first BEGIN_MAP
                    509:     my $curRes = $iterator->next();
                    510: 
1.5       bowersj2  511:     while ($depth > 0) {
                    512:         if ($curRes == $iterator->BEGIN_MAP()) {$depth++;}
                    513:         if ($curRes == $iterator->END_MAP()) { $depth--; }
                    514: 
                    515:         if (ref($curRes) && $curRes->is_map()) {
                    516:             my $title = $curRes->compTitle();
                    517:             
                    518:             my $correct = $curRes->{DATA}->{CHILD_CORRECT};
                    519:             my $total = $curRes->{DATA}->{CHILD_PARTS};
1.28      bowersj2  520: 	    my $attempted = $curRes->{DATA}->{CHILD_ATTEMPTED};
1.5       bowersj2  521: 
1.6       bowersj2  522:             if ($total > 0) {
                    523:                 my $ratio;
                    524:                 $ratio = $correct / $total;
1.51      www       525:                 my $color = &mixColors(\@start, \@end, $ratio);
1.43      bisitz    526:                 $r->print(&Apache::loncommon::start_data_table_row()
                    527:                          .'<td style="background-color:'.$color.';">');
1.6       bowersj2  528:                 
1.15      bowersj2  529: 		my $thisIndent = '';
                    530:                 for (my $i = 1; $i < $depth; $i++) { $thisIndent .= $indentString; }
1.6       bowersj2  531:                 
1.15      bowersj2  532:                 $r->print("$thisIndent$title</td>");
1.28      bowersj2  533: 		if ($totalAttempted) {
1.45      bisitz    534: 		    $r->print('<td valign="top">'
                    535:                              .$thisIndent
                    536:                              .'<span class="LC_nobreak">'
1.49      www       537:                              .$correct.' / '.$attempted.($notshowTotals?'':' / '.$total)
1.45      bisitz    538:                              .'</span></td>'
                    539:                              .&Apache::loncommon::end_data_table_row()
                    540:                     );
1.28      bowersj2  541: 		} else {
1.45      bisitz    542: 		    $r->print('<td valign="top">'
                    543:                              .$thisIndent
                    544:                              .'<span class="LC_nobreak">'
1.49      www       545:                              .$correct.($notshowTotals?'':' / '.$total)
1.45      bisitz    546:                              .'</span></td>'
1.43      bisitz    547:                              .&Apache::loncommon::end_data_table_row());
1.28      bowersj2  548: 		}
1.6       bowersj2  549:             }
1.5       bowersj2  550:         }
1.4       bowersj2  551: 
1.5       bowersj2  552:         $curRes = $iterator->next();
                    553:     }
1.4       bowersj2  554: 
1.6       bowersj2  555:     # If there were any problems at the top level, print an extra "catchall"
1.15      bowersj2  556:     if ($topLevelParts > 0) {
                    557:         my $ratio = $topLevelRight / $topLevelParts;
1.68      www       558:         my $color = &mixColors(\@start, \@end, $ratio);
1.43      bisitz    559:         $r->print(&Apache::loncommon::start_data_table_row()
                    560:                  .'<td style="background-color:'.$color.';">');
1.25      www       561:         $r->print(&mt("Problems Not Contained In A Folder")."</td><td>");
1.43      bisitz    562:         $r->print("$topLevelRight / $topLevelParts</td>"
                    563:                  .&Apache::loncommon::end_data_table_row());
1.6       bowersj2  564:     }
1.4       bowersj2  565: 
1.51      www       566: #
                    567: # show totals (if applicable), close table
                    568: #
1.35      albertel  569:     if ($showPoints) {
1.68      www       570:         my $maxHelpLink = &Apache::loncommon::help_open_topic("Quick_Grades_Possibly_Correct");
1.2       bowersj2  571: 
1.51      www       572:         $title = $showPoints ? "Points" : "Parts Done";
                    573:         my $totaltitle = $showPoints ? &mt("Awarded Total Points") : &mt("Total Parts Done");
                    574:         $r->print(&Apache::loncommon::start_data_table_row()
1.48      bisitz    575:                  .'<td colspan="2" align="right">'.$totaltitle.': <b>'.$totalRight.'</b><br />');
1.51      www       576:         $r->print(&mt('Max Possible To Date')." $maxHelpLink: <b>$totalPossible</b><br />");
                    577:         $title = $showPoints ? "Points" : "Parts";
                    578:         $r->print(&mt("Total $title In Course").': <b>'.$totalParts.'</b></td>'
1.43      bisitz    579:                  .&Apache::loncommon::end_data_table_row());
1.34      www       580:     }
1.1       bowersj2  581: 
1.72      www       582:     $r->print(&Apache::loncommon::end_data_table());
1.5       bowersj2  583: }
                    584: 
1.53      www       585: #
1.65      www       586: # === Outputting category-based grades.
                    587: #
                    588: # $category{'order'}: output order of categories by id
                    589: # $category{'all'}: complete list of all categories 
                    590: # $category{$id.'_name'}: display-name of category
1.53      www       591: #
                    592: 
                    593: sub outputCategories {
                    594: 
                    595:     my ($r,$showPoints,$notshowTotals,
                    596:            $navmap,$totalParts,$totalPossible,$totalRight,$totalAttempted,$topLevelParts,$topLevelRight,$topLevelAttempted)=@_;
1.62      www       597: # Take care of storing and retrieving categories
                    598: 
1.64      www       599:     my $cangrade=&Apache::lonnet::allowed('mgr');
                    600: 
1.62      www       601:     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
                    602:     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
1.64      www       603:     my %categories=();
1.65      www       604: # Loading old categories
                    605:     %categories=&Apache::lonnet::dump('grading_categories',$cdom,$cnum);
1.64      www       606: # Storing
1.72      www       607:     if (($cangrade) && (($env{'form.storechanges'}) || ($env{'form.storemove'} ne '') || ($env{'form.cmd'} ne ''))) {
1.65      www       608: # Process the changes
                    609:         %categories=&process_category_edits($r,$cangrade,%categories);
1.64      www       610: # Actually store
                    611:         &Apache::lonnet::put('grading_categories',\%categories,$cdom,$cnum);
                    612:     }
1.65      www       613: # new categories loaded now
1.99      www       614:     &output_category_table($r,$cangrade,$navmap,1,%categories);
1.65      www       615: #
                    616:     if ($cangrade) {
1.84      www       617:         $r->print(&Apache::loncommon::resourcebrowser_javascript().
                    618:                   '<input type="hidden" name="storemove" value="" />'.
1.72      www       619:                   '<input type="hidden" name="cmd" value="" />'.
1.86      www       620:                   '<input type="hidden" name="resourcesymb" value="" />'.
1.72      www       621:                   '<input type="submit" name="storechanges" value="'.&mt("Save changes to grading categories").'" />'.
1.74      www       622:                   '<script>function storecmd (cmd) { document.quickform.cmd.value=cmd; document.quickform.submit(); }</script>');
1.65      www       623:     }
1.82      www       624: }
                    625: 
                    626: #
                    627: # Get data for all symbs
                    628: #
                    629: 
                    630: sub dumpdata {
                    631:     my ($navmap)=@_;
                    632:     my %returndata=();
                    633: 
                    634: # Run through the map and get all data
                    635: 
                    636:     my $iterator = $navmap->getIterator(undef, undef, undef, 1);
                    637:     my $depth = 1;
                    638:     $iterator->next(); # ignore first BEGIN_MAP
                    639:     my $curRes = $iterator->next();
                    640: 
                    641:     while ($depth > 0) {
                    642:         if ($curRes == $iterator->BEGIN_MAP()) {$depth++;}
                    643:         if ($curRes == $iterator->END_MAP()) { $depth--; }
1.83      www       644:         if (ref($curRes)) {
                    645:             if ($curRes->is_map()) {
                    646:                 $returndata{$curRes->symb()}='folder:'.$curRes->{DATA}->{CHILD_PARTS}.':'.$curRes->{DATA}->{CHILD_ATTEMPTED}.':'.$curRes->{DATA}->{CHILD_CORRECT};
                    647:             } else {
                    648:                 $returndata{$curRes->symb()}='res:'.$curRes->{DATA}->{PROB_WEIGHT}.':'.$curRes->{DATA}->{PROB_POSSIBLE}.':'.$curRes->{DATA}->{PROB_SCORE};
                    649:             } 
1.82      www       650:         }
                    651:         $curRes = $iterator->next();
                    652:     }
                    653:     return %returndata;
1.65      www       654: }
                    655: 
                    656: #
                    657: # Process editing commands, update category hash
                    658: #
                    659: 
                    660: sub process_category_edits {
                    661:     my ($r,$cangrade,%categories)=@_;
                    662:     unless ($cangrade) { return %categories; }
1.72      www       663: # First store everything
                    664:     foreach my $id (split(/\,/,$categories{'order'})) {
1.80      www       665: # Set names, types, and weight (there is only one of each per category)
1.72      www       666:         %categories=&set_category_name($cangrade,$id,$env{'form.name_'.$id},%categories);
                    667:         %categories=&set_category_total($cangrade,$id,$env{'form.totaltype_'.$id},$env{'form.total_'.$id},%categories);
                    668:         %categories=&set_category_weight($cangrade,$id,$env{'form.weight_'.$id},%categories);
1.81      www       669:         %categories=&set_category_displayachieved($cangrade,$id,$env{'form.displayachieved_'.$id},%categories);
1.80      www       670: # Set values for category rules (before names may change)
                    671:         %categories=&set_category_rules($cangrade,$id,%categories);
1.72      www       672:     }
                    673: 
                    674: # Now deal with commands
1.67      www       675:     my $cmd=$env{'form.cmd'};
                    676:     if ($cmd eq 'createnewcat') {
1.69      www       677:         %categories=&make_new_category($r,$cangrade,undef,%categories);
1.72      www       678:     } elsif ($cmd=~/^up\_(.+)$/) {
                    679:         %categories=&move_up_category($1,$cangrade,%categories);
                    680:     } elsif ($cmd=~/^down\_(.+)$/) {
                    681:         %categories=&move_down_category($1,$cangrade,%categories);
1.69      www       682:     } elsif ($cmd=~/^delcat\_(.+)$/) {
                    683:         %categories=&del_category($1,$cangrade,%categories);
1.75      www       684:     } elsif ($cmd=~/^addcont\_(.+)$/) {
1.87      www       685:         %categories=&add_category_content($1,$cangrade,$env{'form.resourcesymb'},%categories);
1.75      www       686:     } elsif ($cmd=~/^delcont\_(.+)\_\_\_\_\_\_(.+)$/) {
                    687:         %categories=&del_category_content($1,$cangrade,$2,%categories);
1.77      www       688:     } elsif ($cmd=~/^newrule\_(.+)$/) {
                    689:         %categories=&add_calculation_rule($1,$cangrade,':',%categories);
1.79      www       690:     } elsif ($cmd=~/^delrule\_(.+)\_\_\_\_\_\_(.*)$/) {
                    691:         %categories=&del_calculation_rule($1,$cangrade,$2,%categories);
1.73      www       692:     }
                    693: # Move to a new position
                    694:     my $moveid=$env{'form.storemove'};
                    695:     if ($moveid) {
                    696:         %categories=&move_category($moveid,$cangrade,$env{'form.newpos_'.$moveid},%categories);
1.72      www       697:     } 
1.65      www       698:     return %categories;
                    699: }
                    700: 
                    701: #
                    702: # Output the table
                    703: #
                    704: 
                    705: sub output_category_table {
1.99      www       706:     my ($r,$cangrade,$navmaps,$output,%categories)=@_;
1.96      www       707:     
                    708:     my $totalweight=0;
                    709:     my $totalpoints=0;
                    710: 
1.99      www       711:     if ($output) { 
                    712:        $r->print(&Apache::loncommon::start_data_table());
1.65      www       713: #
1.99      www       714:        &output_category_table_header($r,$cangrade);
                    715:     }
1.65      www       716: #
                    717:     my @order=split(/\,/,$categories{'order'});
                    718: #
1.88      www       719:     my %performance=&dumpdata($navmaps);
1.65      www       720:     my $maxpos=$#order;
                    721:     for (my $i=0;$i<=$maxpos;$i++) {
1.99      www       722:         my ($correct,$possible,$type,$weight)=&output_and_calc_category($r,$cangrade,$navmaps,$order[$i],$i,$maxpos,\%performance,$output,%categories);
1.96      www       723:         unless ($possible) { next; }
                    724:         $totalpoints+=$weight*$correct/$possible;
                    725:         $totalweight+=$weight;
1.65      www       726:     }
                    727: #
1.96      www       728:     my $perc=0;
                    729:     if ($totalweight) { $perc=100.*$totalpoints/$totalweight; }
                    730: 
1.99      www       731:     if ($output) { 
                    732:         &bottom_line_category($r,$cangrade,$perc); 
                    733:         $r->print(&Apache::loncommon::end_data_table());
                    734:     }
1.96      www       735:     return $perc;
1.65      www       736: }
                    737: 
                    738: sub output_category_table_header {
                    739:     my ($r,$cangrade)=@_;
                    740:     $r->print(&Apache::loncommon::start_data_table_header_row());
                    741:     if ($cangrade) {
                    742:         $r->print('<th colspan="2">'.&mt("Move").'</th><th>'.&mt('Action').'</th>');
                    743:     }
                    744:     $r->print('<th>'.&mt('Category').'</th>'.
                    745:               '<th>'.&mt('Contents').'</th>'.
1.81      www       746:               '<th>'.&mt('Total Points').'</th>'.
1.65      www       747:               '<th>'.&mt('Calculation').'</th>'.
1.81      www       748:               '<th>'.&mt('Relative Weight').'</th>'.
                    749:               '<th>'.&mt('Achieved').'</th>');
1.65      www       750:     $r->print(&Apache::loncommon::end_data_table_header_row());
                    751: }
                    752: 
                    753: 
                    754: #
                    755: # Output one category to table
                    756: #
                    757: 
                    758: sub output_and_calc_category {
1.89      www       759:     my ($r,$cangrade,$navmaps,$id,$currentpos,$maxpos,$performance,$output,%categories)=@_;
1.99      www       760:     
                    761:     if ($output) { $r->print("\n".&Apache::loncommon::start_data_table_row()); }
1.95      www       762: 
1.99      www       763:     if ($output && $cangrade) {
                    764:         my $iconpath = &Apache::loncommon::lonhttpdurl($r->dir_config('lonIconsURL') . "/");
                    765:         my %lt=&Apache::lonlocal::texthash(
1.65      www       766:            'up' => 'Move Up',
                    767:            'dw' => 'Move Down');
                    768: 
                    769:         $r->print(<<ENDMOVE);
                    770: <td>
                    771: <div class="LC_docs_entry_move">
1.72      www       772:   <a href='javascript:storecmd("up_$id");'>
1.65      www       773:     <img src="${iconpath}move_up.gif" alt='$lt{'up'}' class="LC_icon" />
                    774:   </a>
                    775: </div>
                    776: <div class="LC_docs_entry_move">
1.72      www       777:   <a href='javascript:storecmd("down_$id");'>
1.65      www       778:     <img src="${iconpath}move_down.gif" alt='$lt{'dw'}' class="LC_icon" />
                    779:   </a>
                    780: </div>
                    781: </td>
                    782: ENDMOVE
                    783:         $r->print("\n<td>\n<select name='newpos_$id' onchange='this.form.storemove.value=\"$id\";this.form.submit()'>");
                    784:         for (my $i=0;$i<=$maxpos;$i++) {
                    785:             if ($i==$currentpos) {
                    786:                 $r->print('<option value="" selected="selected">('.$i.')</option>');
                    787:             } else {
                    788:                 $r->print('<option value="'.$i.'">'.$i.'</option>');
                    789:             }
                    790:         }
                    791:         $r->print("\n</select>\n</td>\n");
1.72      www       792:         $r->print('<td><a href="javascript:storecmd(\'delcat_'.$id.'\');">'.&mt('Delete').'</a></td>');
1.69      www       793:         $r->print('<td><input type="text" name="name_'.$id.
                    794:                   '" value="'.&Apache::lonhtmlcommon::entity_encode($categories{$id.'_name'}).'" /></td>');
1.89      www       795:     } elsif ($output) {
1.69      www       796:         $r->print('<td>'.$categories{$id.'_name'}.'</td>');
1.65      www       797:     }
1.89      www       798: # Content display and summing up of points
                    799:     my $totalpossible=0;
                    800:     my $totalcorrect=0;
1.91      www       801:     my @individual=();
1.89      www       802:     if ($output) { $r->print('<td><ul>'); }
1.75      www       803:     foreach my $contentid (split(/\,/,$categories{$id.'_content'})) {
1.89      www       804:         my ($type,$possible,$attempted,$correct)=split(/\:/,$$performance{$contentid});
                    805:         $totalpossible+=$possible;
                    806:         $totalcorrect+=$correct;
1.91      www       807:         if ($possible>0) { push(@individual,"$possible:$correct"); }
1.89      www       808:         if ($output) {
                    809:            $r->print('<li>');
1.96      www       810:            $r->print(&Apache::lonnet::gettitle($contentid).' ('.&numberout($correct).'/'.&numberout($possible).')');
1.89      www       811:            if ($cangrade) {
                    812:               $r->print(' <a href="javascript:storecmd(\'delcont_'.$id.'______'.$contentid.'\');">'.&mt('Delete').'</a>');
                    813:            }
                    814:            $r->print('</li>');
1.75      www       815:         }
                    816:     }
1.89      www       817:     if ($output) {
                    818:        $r->print('</ul>');
                    819:        if ($cangrade) {
                    820:            $r->print('<br />'.&Apache::loncommon::selectresource_link('quickform','addcont_'.$id,&mt('Add Problem or Folder')).'<br />');
                    821:        }
1.96      www       822:        $r->print('<p><b>'.&mt('Total raw points: [_1]/[_2]',&numberout($totalcorrect),&numberout($totalpossible)).'</b></p>');
1.89      www       823:        $r->print('</td>'); 
1.70      www       824:     }
1.81      www       825: # Total
1.90      www       826:     if ($output) { $r->print('<td>'); }
1.81      www       827:     if ($cangrade) {
1.89      www       828:        if ($output) { 
1.90      www       829:           $r->print(
1.81      www       830:                   '<select name="totaltype_'.$id.'">'.
                    831:                   '<option value="default"'.($categories{$id.'_totaltype'} eq 'default'?' selected="selected"':'').'>'.&mt('default').'</option>'.
                    832:                   '<option value="typein"'.($categories{$id.'_totaltype'} eq 'typein'?' selected="selected"':'').'>'.&mt('Type-in value').'</option>'.
                    833:                   '</select>'.
                    834:                   '<input type="text" size="4" name="total_'.$id.
1.90      www       835:                   '" value="'.&Apache::lonhtmlcommon::entity_encode($categories{$id.'_total'}).'" />'); 
1.89      www       836:        }
1.81      www       837:     } else {
1.89      www       838:        if ($output) {
1.90      www       839:           $r->print('<td>'.($categories{$id.'_totaltype'} eq 'default'?&mt('default'):$categories{$id.'_total'}));
1.89      www       840:        }
1.81      www       841:     }
1.90      www       842: # Adjust total points
                    843:     if ($categories{$id.'_totaltype'} eq 'typein') {
                    844:        $totalpossible=1.*$categories{$id.'_total'};
                    845:     }
                    846:     if ($output) {
1.96      www       847:        $r->print('<p><b>'.&mt('Adjusted raw points: [_1]/[_2]',&numberout($totalcorrect),&numberout($totalpossible)).'</b></p>');
1.90      www       848:     }
1.81      www       849: 
                    850: 
1.70      www       851: # Calculation
1.89      www       852:     if ($output) { $r->print('<td><ul>'); }
1.76      www       853:     foreach my $calcrule (split(/\,/,$categories{$id.'_calculations'})) {
1.89      www       854:         if ($output) { $r->print('<li>'); }
1.78      www       855:         my ($code,$value)=split(/\:/,$calcrule);
1.89      www       856:         if ($output) { $r->print(&pretty_prt_rule($cangrade,$id,$code,$value)); }
1.76      www       857:         if ($cangrade) {
1.89      www       858:            if ($output) { $r->print(' <a href="javascript:storecmd(\'delrule_'.$id.'______'.$code.'\');">'.&mt('Delete').'</a>'); }
1.76      www       859:         }
1.91      www       860:         if ($code eq 'capabove') {
                    861:             if ($totalpossible>0) {
1.92      www       862:                 if ($totalcorrect/$totalpossible>$value/100.) {
                    863:                     $totalcorrect=$totalpossible*$value/100.;
1.91      www       864:                 }
                    865:             }
                    866:         } elsif ($code eq 'capbelow') {
                    867:             if ($totalpossible>0) {
1.92      www       868:                 if ($totalcorrect/$totalpossible<$value/100.) {
                    869:                     $totalcorrect=$totalpossible*$value/100.;
1.91      www       870:                 }
                    871:             }
1.92      www       872:         } elsif ($code eq 'droplow') {
1.94      www       873:             ($totalpossible,$totalcorrect,@individual)=&drop(0,0,$value,@individual);
1.92      www       874:         } elsif ($code eq 'drophigh') {
1.94      www       875:             ($totalpossible,$totalcorrect,@individual)=&drop(1,0,$value,@individual);
1.92      www       876:         } elsif ($code eq 'droplowperc') {
1.94      www       877:             ($totalpossible,$totalcorrect,@individual)=&drop(0,1,$value,@individual);
1.92      www       878:         } elsif ($code eq 'drophighperc') {
1.94      www       879:             ($totalpossible,$totalcorrect,@individual)=&drop(1,1,$value,@individual);
1.91      www       880:         }
1.89      www       881:         if ($output) { $r->print('</li>'); }
1.76      www       882:     }
1.94      www       883: # Re-adjust total points if force total
                    884:     if ($categories{$id.'_totaltype'} eq 'typein') {
                    885:        $totalpossible=1.*$categories{$id.'_total'};
                    886:     }
                    887: 
1.91      www       888:     if ($output) { 
1.92      www       889:         $r->print('</ul>'); 
                    890:         if ($cangrade) { $r->print('<br />'.&new_calc_rule_form($id)); }
1.96      www       891:         $r->print('<p><b>'.&mt('Calculated points: [_1]/[_2]',&numberout($totalcorrect),&numberout($totalpossible)).'</b></p>');
1.92      www       892:         $r->print('</td>'); 
1.91      www       893:     }
1.95      www       894: #
                    895: # Prepare for export
                    896: #
1.70      www       897: # Weight
1.95      www       898:     my $weight=$categories{$id.'_weight'};
                    899:     unless (1.*$weight>0) { $weight=0; }
1.70      www       900:     if ($cangrade) {
1.89      www       901:        if ($output) { 
                    902:           $r->print('<td>'.
1.70      www       903:                   '<input type="text" size="4" name="weight_'.$id.
1.95      www       904:                   '" value="'.&Apache::lonhtmlcommon::entity_encode($weight).'" /></td>');
1.89      www       905:        }
1.70      www       906:     } else {
1.89      www       907:        if ($output) {
1.95      www       908:           $r->print('<td>'.$weight.'</td>');
1.89      www       909:        }
1.70      www       910:     }
1.81      www       911: # Achieved
1.95      www       912:     my $type=$categories{$id.'_displayachieved'};
                    913:     unless (($type eq 'percent') || ($type eq 'points')) { $type='points'; }
1.89      www       914:     if ($output) { $r->print('<td>'); }
1.81      www       915:     if ($cangrade) {
1.89      www       916:         if ($output) {
                    917:            $r->print('<select name="displayachieved_'.$id.'">'.
1.95      www       918:                   '<option value="percent"'.($type eq 'percent'?' selected="selected"':'').'>'.&mt('percent').'</option>'.
                    919:                   '<option value="points"'.($type eq 'points'?' selected="selected"':'').'>'.&mt('points').'</option>'.
1.81      www       920:                   '</select>');
1.89      www       921:         }
1.95      www       922:     }
                    923:     if ($output) {
1.96      www       924:         $r->print('<p><b>');
1.95      www       925:         if ($type eq 'percent') {
                    926:             my $perc='---';
                    927:             if ($totalpossible) {
                    928:                 $perc=100.*$totalcorrect/$totalpossible;
1.89      www       929:             }
1.96      www       930:             $r->print(&mt('[_1] percent',&numberout($perc)));
1.95      www       931:         } else {
1.96      www       932:             $r->print(&mt('[_1]/[_2] points',&numberout($totalcorrect),&numberout($totalpossible)));
1.81      www       933:         }
1.96      www       934:         $r->print('</b></p>');
1.81      www       935:     }
1.89      www       936:     if ($output) { $r->print('</td>'); }
1.70      www       937: 
1.95      www       938:     return ($totalcorrect,$totalpossible,$type,$weight);
1.65      www       939: }
                    940: 
                    941: #
1.92      www       942: # Drop folders and problems
                    943: #
                    944: 
                    945: sub drop {
1.93      www       946:     my ($high,$percent,$n,@individual)=@_;
1.94      www       947: # Sort assignments by points or percent
1.92      www       948:     my @newindividual=sort {
                    949:         my ($pa,$ca)=split(/\:/,$a);
                    950:         my ($pb,$cb)=split(/\:/,$b);
                    951:         if ($percent) {
                    952:             my $perca=0;
                    953:             if ($pa>0) { $perca=$ca/$pa; }
                    954:             my $percb=0;
                    955:             if ($pb>0) { $percb=$cb/$pb; }
                    956:             $perca<=>$percb;
                    957:         } else {
                    958:             $ca<=>$cb;
                    959:         }
                    960:     } @individual;
1.94      www       961: # Drop the ones we don't want
1.93      www       962:     if ($#newindividual>=$n) {
                    963:         if ($high) {
                    964:            splice(@newindividual,$#newindividual+1-$n,$n);
                    965:         } else {
                    966:            splice(@newindividual,0,$n);
                    967:         }
                    968:     } else {
                    969:         @newindividual=();
                    970:     }
1.94      www       971: # Re-calculate how many points possible and achieved
                    972:     my $newpossible=0;
1.92      www       973:     my $newcorrect=0;
1.93      www       974:     for my $score (@newindividual) {
1.94      www       975:         my ($thispossible,$thiscorrect)=(split(/\:/,$score));
                    976:         $newpossible+=$thispossible;
                    977:         $newcorrect+=$thiscorrect;
1.93      www       978:     }
1.94      www       979:     return ($newpossible,$newcorrect,@newindividual);
1.92      www       980: } 
                    981: #
1.65      www       982: # Bottom line with grades
                    983: #
                    984: 
                    985: sub bottom_line_category {
1.96      www       986:     my ($r,$cangrade,$perc)=@_;
1.67      www       987:     $r->print(&Apache::loncommon::start_data_table_row());
                    988:     if ($cangrade) {
1.72      www       989:         $r->print('<td colspan="3"><a href="javascript:storecmd(\'createnewcat\');">'.&mt('Create New Category').'</a></td>');
1.67      www       990:     }
1.96      www       991:     $r->print('<td colspan="6"><b>'.&mt('Total: [_1] percent',&numberout($perc)).'</b></td>');
1.65      www       992: }
                    993: 
1.96      www       994: sub numberout {
                    995:     my ($number)=@_;
                    996:     my $printout=sprintf("%.3f", $number);
                    997:     $printout=~s/0+$//;
                    998:     $printout=~s/\.$//;
                    999:     return $printout;
                   1000: }
1.65      www      1001: #
                   1002: # Make one new category
                   1003: #
                   1004: 
                   1005: sub make_new_category {
                   1006:     my ($r,$cangrade,$ordernum,%categories)=@_;
                   1007:     unless ($cangrade) { return %categories; }
                   1008: # Generate new ID
                   1009:     my $id=time.'_'.$$.'_'.rand(10000);
                   1010: # Add new ID to list of all IDs ever created in this course
                   1011:     $categories{'all'}.=','.$id;
                   1012:     $categories{'all'}=~s/^\,//;
                   1013: # Add new ID to ordered list of displayed and evaluated categories
                   1014:     $categories{'order'}.=','.$id;
                   1015:     $categories{'order'}=~s/^\,//;
                   1016: # Move it into desired space
                   1017:     if (defined($ordernum)) {
                   1018:         %categories=&move_category($id,$cangrade,$ordernum,%categories);
                   1019:     }
1.71      www      1020:     $categories{$id.'_weight'}=0;
                   1021:     $categories{$id.'_totaltype'}='default';
1.81      www      1022:     $categories{$id.'_displayachieved'}='percent';
1.65      www      1023:     return %categories;
1.53      www      1024: }
                   1025: 
1.76      www      1026: 
                   1027: # === Calculation Rule Editing
                   1028: 
1.80      www      1029: sub category_rule_codes {
                   1030:     return &Apache::lonlocal::texthash(
1.91      www      1031:                 'droplowperc'  => 'Drop N lowest grade percentage problems/folders',
                   1032:                 'drophighperc' => 'Drop N highest grade percentage problems/folderss',
                   1033:                 'droplow'  => 'Drop N lowest point problems/folders',
                   1034:                 'drophigh' => 'Drop N highest point problems/folders',
1.78      www      1035:                 'capabove' => 'Cap percentage above N percent',
                   1036:                 'capbelow' => 'Cap percentage below N percent');
1.80      www      1037: }
                   1038: 
                   1039: sub pretty_prt_rule {
                   1040:     my ($cangrade,$id,$code,$value)=@_;
                   1041:     my $cid=$id.'_'.$code;
                   1042:     my %lt=&category_rule_codes();
1.78      www      1043:     my $ret='<span class="LC_nobreak">';
                   1044:     if ($cangrade) {
                   1045:         $ret.='<select name="sel_'.$cid.'">';
                   1046:         foreach my $calc (''=>'',sort(keys(%lt))) {
                   1047:             $ret.='<option value="'.$calc.'"'.($calc eq $code?' selected="selected"':'').' />'.$lt{$calc}.'</input>';
                   1048:         }
1.80      www      1049:         $ret.='</select> N=<input type="text" size="5" name="val_'.$cid.'" value="'.$value.'" /></span>';
1.78      www      1050:     } else {
                   1051:         $ret.=$lt{$code}.'; N='.$value;
                   1052:     }
                   1053:     $ret.='</span>';
                   1054:     return $ret;
1.76      www      1055: }
                   1056: 
                   1057: sub new_calc_rule_form {
1.77      www      1058:     my ($id)=@_;
                   1059:     return '<a href="javascript:storecmd(\'newrule_'.$id.'\');">'.&mt('New Calculation Rule').'</a>';
1.76      www      1060: }
                   1061: 
                   1062: #
                   1063: # Add a calculation rule
                   1064: #
                   1065: 
                   1066: sub add_calculation_rule {
                   1067:     my ($id,$cangrade,$newcontent,%categories)=@_;
                   1068:     unless ($cangrade) { return %categories; }
                   1069:     my %newcontent=($newcontent => 1);
                   1070:     foreach my $current (split(/\,/,$categories{$id.'_calculations'})) {
                   1071:         $newcontent{$current}=1;
                   1072:     }
                   1073:     $categories{$id.'_calculations'}=join(',',sort(keys(%newcontent)));
                   1074:     return %categories;
                   1075: }
                   1076: 
                   1077: #
                   1078: # Delete a calculation rule
                   1079: #
                   1080: 
                   1081: sub del_calculation_rule {
                   1082:     my ($id,$cangrade,$delcontent,%categories)=@_;
                   1083:     unless ($cangrade) { return %categories; }
                   1084:     my @newcontent=();
                   1085:     foreach my $current (split(/\,/,$categories{$id.'_calculations'})) {
1.78      www      1086:         unless ($current=~/^\Q$delcontent\E\:/) {
1.76      www      1087:             push(@newcontent,$current);
                   1088:         }
                   1089:     }
                   1090:     $categories{$id.'_calculations'}=join(',',@newcontent);
                   1091:     return %categories;
                   1092: }
                   1093: 
1.80      www      1094: sub set_category_rules {
                   1095:     my ($cangrade,$id,%categories)=@_;
                   1096:     unless ($cangrade) { return %categories; }
                   1097:     my %lt=&category_rule_codes();
                   1098:     my @newrules=();
                   1099:     foreach my $code ('',(keys(%lt))) {
                   1100:         if ($env{'form.sel_'.$id.'_'.$code}) {
                   1101:             push(@newrules,$env{'form.sel_'.$id.'_'.$code}.':'.$env{'form.val_'.$id.'_'.$code});
                   1102:         }
                   1103:     }
                   1104:     $categories{$id.'_calculations'}=join(',',sort(@newrules));
                   1105:     return %categories;
                   1106: }
                   1107: 
                   1108: 
1.76      www      1109: # === Category Editing
                   1110: 
1.65      www      1111: #
1.75      www      1112: # Add to category content
                   1113: #
                   1114: 
                   1115: sub add_category_content {
                   1116:     my ($id,$cangrade,$newcontent,%categories)=@_;
                   1117:     unless ($cangrade) { return %categories; }
1.87      www      1118:     &Apache::lonnet::logthis("In here $newcontent");
1.75      www      1119:     my %newcontent=($newcontent => 1);
                   1120:     foreach my $current (split(/\,/,$categories{$id.'_content'})) {
                   1121:         $newcontent{$current}=1;
                   1122:     }
                   1123:     $categories{$id.'_content'}=join(',',sort(keys(%newcontent)));
                   1124:     return %categories;
                   1125: }
                   1126: 
                   1127: #
                   1128: # Delete from category content
                   1129: #
                   1130: 
                   1131: sub del_category_content {
                   1132:     my ($id,$cangrade,$delcontent,%categories)=@_;
                   1133:     unless ($cangrade) { return %categories; }
                   1134:     my @newcontent=();
                   1135:     foreach my $current (split(/\,/,$categories{$id.'_content'})) {
                   1136:         unless ($current eq $delcontent) {
                   1137:             push(@newcontent,$current);
                   1138:         }
                   1139:     }
                   1140:     $categories{$id.'_content'}=join(',',@newcontent);
                   1141:     return %categories;
                   1142: }
                   1143: 
                   1144: #
1.68      www      1145: # Delete category
                   1146: #
                   1147: 
                   1148: sub del_category {
1.75      www      1149:     my ($id,$cangrade,%categories)=@_;
                   1150:     unless ($cangrade) { return %categories; }
                   1151:     my @neworder=();
                   1152:     foreach my $currentid (split(/\,/,$categories{'order'})) {
                   1153:         unless ($currentid eq $id) {
                   1154:             push(@neworder,$currentid);
                   1155:         }
                   1156:     }
                   1157:     $categories{'order'}=join(',',@neworder);
                   1158:     return %categories;
1.68      www      1159: }
                   1160: 
                   1161: #
1.72      www      1162: # Move category up
                   1163: #
                   1164: 
                   1165: sub move_up_category {
                   1166:     my ($id,$cangrade,%categories)=@_;
                   1167:     my $currentpos=&current_pos_category($id,%categories);
                   1168:     if ($currentpos<1) { return %categories; }
                   1169:     return &move_category($id,$cangrade,$currentpos-1,%categories);
                   1170: }
                   1171: 
                   1172: #
                   1173: # Move category down
                   1174: #
                   1175: 
                   1176: sub move_down_category {
                   1177:     my ($id,$cangrade,%categories)=@_;
                   1178:     my $currentpos=&current_pos_category($id,%categories);
                   1179:     my @order=split(/\,/,$categories{'order'});
                   1180:     if ($currentpos>=$#order) { return %categories; }
                   1181:     return &move_category($id,$cangrade,$currentpos+1,%categories);
                   1182: }
                   1183: 
                   1184: #
1.65      www      1185: # Move a category to a desired position n the display order
                   1186: #
                   1187: 
                   1188: sub move_category {
                   1189:     my ($id,$cangrade,$ordernum,%categories)=@_;
                   1190:     unless ($cangrade) { return %categories; }
                   1191:     my @order=split(/\,/,$categories{'order'});
                   1192: # Where is the index currently?
                   1193:     my $currentpos=&current_pos_category($id,%categories);
                   1194:     if (defined($currentpos)) {
                   1195:         if ($currentpos<$ordernum) {
                   1196: # This is moving to a higher index
                   1197: # ....X1234....
                   1198: # ....1234X....
                   1199:             for (my $i=$currentpos;$i<$ordernum;$i++) {
                   1200:                 $order[$i]=$order[$i+1];
                   1201:             }
                   1202:             $order[$ordernum]=$id;
                   1203:         }
                   1204:         if ($currentpos>$ordernum) {
                   1205: # This is moving to a lower index
                   1206: # ....1234X....
                   1207: # ....X1234....
                   1208:             for (my $i=$currentpos;$i>$ordernum;$i--) {
                   1209:                 $order[$i]=$order[$i-1];
                   1210:             }
                   1211:             $order[$ordernum]=$id;
                   1212:         }
                   1213:     }
                   1214:     $categories{'order'}=join(',',@order);
                   1215:     return %categories;
                   1216: }
                   1217: 
                   1218: #
                   1219: #  Find current postion of a category in the order
                   1220: #
                   1221: 
                   1222: sub current_pos_category {
                   1223:     my ($id,%categories)=@_;
                   1224:     my @order=split(/\,/,$categories{'order'});
                   1225:     for (my $i=0;$i<=$#order;$i++) {
                   1226:         if ($order[$i] eq $id) { return $i; }
                   1227:     }
                   1228: # not found
                   1229:     return undef;
                   1230: }
                   1231: 
                   1232: #
                   1233: # Set name of a category
                   1234: #
                   1235: sub set_category_name {
1.69      www      1236:     my ($cangrade,$id,$name,%categories)=@_;
                   1237:     unless ($cangrade) { return %categories; }
1.65      www      1238:     $categories{$id.'_name'}=$name;
                   1239:     return %categories;
                   1240: }
                   1241: 
                   1242: #
1.71      www      1243: # Set total of a category
1.70      www      1244: #
1.71      www      1245: sub set_category_total {
                   1246:     my ($cangrade,$id,$totaltype,$total,%categories)=@_;
1.70      www      1247:     unless ($cangrade) { return %categories; }
1.71      www      1248:     if (($categories{$id.'_total'} eq '') && ($total=~/\d/)) {
                   1249:         $totaltype='typein';
1.70      www      1250:     }
1.71      www      1251:     $categories{$id.'_totaltype'}=$totaltype;
                   1252:     if ($totaltype eq 'default') {
                   1253:         $categories{$id.'_total'}='';
1.70      www      1254:     } else {
1.71      www      1255:         $total=~s/\D//gs;
                   1256:         unless ($total) { $total=0; }
                   1257:         $categories{$id.'_total'}=$total;
1.70      www      1258:     }
                   1259:     return %categories;
                   1260: }
                   1261: 
1.71      www      1262: sub set_category_weight {
                   1263:     my ($cangrade,$id,$weight,%categories)=@_;
                   1264:     unless ($cangrade) { return %categories; }
                   1265:     $weight=~s/\D//gs;
                   1266:     unless ($weight) { $weight=0; }
                   1267:     $categories{$id.'_weight'}=$weight;
                   1268:     return %categories;
                   1269: }
1.70      www      1270: 
1.81      www      1271: sub set_category_displayachieved {
                   1272:     my ($cangrade,$id,$value,%categories)=@_;
                   1273:     unless ($cangrade) { return %categories; }
                   1274:     unless (($value eq 'percent') || ($value eq 'points')) { $value='percent'; }
                   1275:     $categories{$id.'_displayachieved'}=$value;
                   1276:     return %categories;
                   1277: }
                   1278: 
                   1279: 
1.70      www      1280: #
1.65      www      1281: # === end category-related
                   1282: #
                   1283: #
1.5       bowersj2 1284: # Pass this two refs to arrays for the start and end color, and a number
                   1285: # from 0 to 1 for how much of the latter you want to mix in. It will
                   1286: # return a string ready to show ("#FFC309");
1.51      www      1287: 
1.5       bowersj2 1288: sub mixColors {
                   1289:     my $start = shift;
                   1290:     my $end = shift;
                   1291:     my $ratio = shift;
                   1292:     
1.9       matthew  1293:     my ($a,$b);
1.5       bowersj2 1294:     my $final = "";
1.9       matthew  1295:     $a = $start->[0]; $b = $end->[0];
1.5       bowersj2 1296:     my $mix1 = POSIX::floor((1-$ratio)*$a + $ratio*$b);
1.9       matthew  1297:     $a = $start->[1]; $b = $end->[1];
1.5       bowersj2 1298:     my $mix2 = POSIX::floor((1-$ratio)*$a + $ratio*$b);
1.9       matthew  1299:     $a = $start->[2]; $b = $end->[2];
1.5       bowersj2 1300:     my $mix3 = POSIX::floor((1-$ratio)*$a + $ratio*$b);
                   1301: 
1.16      bowersj2 1302:     $final = sprintf "%02x%02x%02x", $mix1, $mix2, $mix3;
1.5       bowersj2 1303:     return "#" . $final;
1.1       bowersj2 1304: }
                   1305: 
                   1306: 1;

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.