File:  [LON-CAPA] / loncom / homework / grades.pm
Revision 1.596.2.12.2.51: download - view: text, annotated - select for diffs
Mon Aug 31 15:42:52 2020 UTC (3 years, 8 months ago) by raeburn
Branches: version_2_11_X
Diff to branchpoint 1.596.2.12: preferred, unified
- For 2.11
  Backport 1.598, 1.599, 1.600, 1.601, 1.602, 1.603, 1.605, 1.607, 1.608,
           1.609, 1.610, 1.611, 1.612, 1.613, 1.614, 1.615, 1.616, 1.617,
           1.618, 1.619, 1.620, 1.621, 1.622, 1.623, 1.624, 1.625, 1.627,
           1.628, 1.629, 1.630, 1.632, 1.633, 1.634, 1.636, 1.637, 1.638,
           1.763, 1.764, 1.766, 1.767, 1.768 (part), 1.769, 1.771, 1.772,
           1.773, 1.774

    1: # The LearningOnline Network with CAPA
    2: # The LON-CAPA Grading handler
    3: #
    4: # $Id: grades.pm,v 1.596.2.12.2.51 2020/08/31 15:42:52 raeburn Exp $
    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: 
   30: 
   31: package Apache::grades;
   32: use strict;
   33: use Apache::style;
   34: use Apache::lonxml;
   35: use Apache::lonnet;
   36: use Apache::loncommon;
   37: use Apache::lonhtmlcommon;
   38: use Apache::lonnavmaps;
   39: use Apache::lonhomework;
   40: use Apache::lonpickcode;
   41: use Apache::loncoursedata;
   42: use Apache::lonmsg();
   43: use Apache::Constants qw(:common :http);
   44: use Apache::lonlocal;
   45: use Apache::lonenc;
   46: use Apache::lonstathelpers;
   47: use Apache::bridgetask();
   48: use Apache::lontexconvert();
   49: use HTML::Parser();
   50: use File::MMagic;
   51: use String::Similarity;
   52: use LONCAPA;
   53: 
   54: use POSIX qw(floor);
   55: 
   56: 
   57: 
   58: my %perm=();
   59: my %old_essays=();
   60: 
   61: #  These variables are used to recover from ssi errors
   62: 
   63: my $ssi_retries = 5;
   64: my $ssi_error;
   65: my $ssi_error_resource;
   66: my $ssi_error_message;
   67: 
   68: 
   69: sub ssi_with_retries {
   70:     my ($resource, $retries, %form) = @_;
   71:     my ($content, $response) = &Apache::loncommon::ssi_with_retries($resource, $retries, %form);
   72:     if ($response->is_error) {
   73: 	$ssi_error          = 1;
   74: 	$ssi_error_resource = $resource;
   75: 	$ssi_error_message  = $response->code . " " . $response->message;
   76:     }
   77: 
   78:     return $content;
   79: 
   80: }
   81: #
   82: #  Prodcuces an ssi retry failure error message to the user:
   83: #
   84: 
   85: sub ssi_print_error {
   86:     my ($r) = @_;
   87:     my $helpurl = &Apache::loncommon::top_nav_help('Helpdesk');
   88:     $r->print('
   89: <br />
   90: <h2>'.&mt('An unrecoverable network error occurred:').'</h2>
   91: <p>
   92: '.&mt('Unable to retrieve a resource from a server:').'<br />
   93: '.&mt('Resource:').' '.$ssi_error_resource.'<br />
   94: '.&mt('Error:').' '.$ssi_error_message.'
   95: </p>
   96: <p>'.
   97: &mt('It is recommended that you try again later, as this error may mean the server was just temporarily unavailable, or is down for maintenance.').'<br />'.
   98: &mt('If the error persists, please contact the [_1] for assistance.',$helpurl).
   99: '</p>');
  100:     return;
  101: }
  102: 
  103: #
  104: # --- Retrieve the parts from the metadata file.---
  105: # Returns an array of everything that the resources stores away
  106: #
  107: 
  108: sub getpartlist {
  109:     my ($symb,$errorref) = @_;
  110: 
  111:     my $navmap   = Apache::lonnavmaps::navmap->new();
  112:     unless (ref($navmap)) {
  113:         if (ref($errorref)) { 
  114:             $$errorref = 'navmap';
  115:             return;
  116:         }
  117:     }
  118:     my $res      = $navmap->getBySymb($symb);
  119:     my $partlist = $res->parts();
  120:     my $url      = $res->src();
  121:     my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys'));
  122: 
  123:     my @stores;
  124:     foreach my $part (@{ $partlist }) {
  125: 	foreach my $key (@metakeys) {
  126: 	    if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }
  127: 	}
  128:     }
  129:     return @stores;
  130: }
  131: 
  132: #--- Format fullname, username:domain if different for display
  133: #--- Use anywhere where the student names are listed
  134: sub nameUserString {
  135:     my ($type,$fullname,$uname,$udom) = @_;
  136:     if ($type eq 'header') {
  137: 	return '<b>&nbsp;'.&mt('Fullname').'&nbsp;</b><span class="LC_internal_info">('.&mt('Username').')</span>';
  138:     } else {
  139: 	return '&nbsp;'.$fullname.'<span class="LC_internal_info">&nbsp;('.$uname.
  140: 	    ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</span>';
  141:     }
  142: }
  143: 
  144: #--- Get the partlist and the response type for a given problem. ---
  145: #--- Indicate if a response type is coded handgraded or not. ---
  146: #--- Count responseIDs, essayresponse items, and dropbox items ---
  147: #--- Sets response_error pointer to "1" if navmaps object broken ---
  148: sub response_type {
  149:     my ($symb,$response_error) = @_;
  150: 
  151:     my $navmap = Apache::lonnavmaps::navmap->new();
  152:     unless (ref($navmap)) {
  153:         if (ref($response_error)) {
  154:             $$response_error = 1;
  155:         }
  156:         return;
  157:     }
  158:     my $res = $navmap->getBySymb($symb);
  159:     unless (ref($res)) {
  160:         $$response_error = 1;
  161:         return;
  162:     }
  163:     my $partlist = $res->parts();
  164:     my ($numresp,$numessay,$numdropbox) = (0,0,0);
  165:     my %vPart = 
  166: 	map { $_ => 1 } (&Apache::loncommon::get_env_multiple('form.vPart'));
  167:     my (%response_types,%handgrade);
  168:     foreach my $part (@{ $partlist }) {
  169: 	next if (%vPart && !exists($vPart{$part}));
  170: 
  171: 	my @types = $res->responseType($part);
  172: 	my @ids = $res->responseIds($part);
  173: 	for (my $i=0; $i < scalar(@ids); $i++) {
  174:             $numresp ++;
  175: 	    $response_types{$part}{$ids[$i]} = $types[$i];
  176:             if ($types[$i] eq 'essay') {
  177:                 $numessay ++;
  178:                 if (&Apache::lonnet::EXT("resource.$part".'_'.$ids[$i].".uploadedfiletypes",$symb)) {
  179:                     $numdropbox ++;
  180:                 }
  181:             }
  182: 	    $handgrade{$part.'_'.$ids[$i]} = 
  183: 		&Apache::lonnet::EXT('resource.'.$part.'_'.$ids[$i].
  184: 				     '.handgrade',$symb);
  185: 	}
  186:     }
  187:     return ($partlist,\%handgrade,\%response_types,$numresp,$numessay,$numdropbox);
  188: }
  189: 
  190: sub flatten_responseType {
  191:     my ($responseType) = @_;
  192:     my @part_response_id =
  193: 	map { 
  194: 	    my $part = $_;
  195: 	    map {
  196: 		[$part,$_]
  197: 		} sort(keys(%{ $responseType->{$part} }));
  198: 	} sort(keys(%$responseType));
  199:     return @part_response_id;
  200: }
  201: 
  202: sub get_display_part {
  203:     my ($partID,$symb)=@_;
  204:     my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
  205:     if (defined($display) and $display ne '') {
  206:         $display.= ' (<span class="LC_internal_info">'
  207:                   .&mt('Part ID: [_1]',$partID).'</span>)';
  208:     } else {
  209: 	$display=$partID;
  210:     }
  211:     return $display;
  212: }
  213: 
  214: #--- Show parts and response type
  215: sub showResourceInfo {
  216:     my ($symb,$partlist,$responseType,$formname,$checkboxes,$uploads) = @_;
  217:     unless ((ref($partlist) eq 'ARRAY') && (ref($responseType) eq 'HASH')) {
  218:         return '<br clear="all">';
  219:     }
  220:     my $coltitle = &mt('Problem Part Shown');
  221:     if ($checkboxes) {
  222:         $coltitle = &mt('Problem Part');
  223:     } else {
  224:         my $checkedparts = 0;
  225:         foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) {
  226:             if (grep(/^\Q$partid\E$/,@{$partlist})) {
  227:                 $checkedparts ++;
  228:             }
  229:         }
  230:         if ($checkedparts == scalar(@{$partlist})) {
  231:             return '<br clear="all">';
  232:         }
  233:         if ($uploads) {
  234:             $coltitle = &mt('Problem Part Selected');
  235:         }
  236:     }
  237:     my $result = '<div class="LC_left_float" style="display:inline-block;">';
  238:     if ($checkboxes) {
  239:         my $legend = &mt('Parts to display');
  240:         if ($uploads) {
  241:             $legend = &mt('Part(s) with dropbox');
  242:         }
  243:         $result .= '<fieldset style="display:inline-block;"><legend>'.$legend.'</legend>'.
  244:                    '<span class="LC_nobreak">'.
  245:                    '<label><input type="radio" name="chooseparts" value="0" onclick="toggleParts('."'$formname'".');" checked="checked" />'.
  246:                    &mt('All parts').'</label>'.('&nbsp;'x2).
  247:                    '<label><input type="radio" name="chooseparts" value="1" onclick="toggleParts('."'$formname'".');" />'.
  248:                    &mt('Selected parts').'</label></span>'.
  249:                    '<div id="LC_partselector" style="display:none">';
  250:     }
  251:     $result .= &Apache::loncommon::start_data_table()
  252:               .&Apache::loncommon::start_data_table_header_row();
  253:     if ($checkboxes) {
  254:         $result .= '<th>'.&mt('Display?').'</th>';
  255:     }
  256:     $result .= '<th>'.$coltitle.'</th>'
  257:               .'<th>'.&mt('Res. ID').'</th>'
  258:               .'<th>'.&mt('Type').'</th>'
  259:               .&Apache::loncommon::end_data_table_header_row();
  260:     my %partsseen;
  261:     foreach my $partID (sort(keys(%$responseType))) {
  262:         foreach my $resID (sort(keys(%{ $responseType->{$partID} }))) {
  263:             my $responsetype = $responseType->{$partID}->{$resID};
  264:             if ($uploads) {
  265:                 next unless ($responsetype eq 'essay');
  266:                 next unless (&Apache::lonnet::EXT("resource.$partID".'_'."$resID.uploadedfiletypes",$symb));
  267:             }
  268:             my $display_part=&get_display_part($partID,$symb);
  269:             if (exists($partsseen{$partID})) {
  270:                 $result.=&Apache::loncommon::continue_data_table_row();
  271:             } else {
  272:                 $partsseen{$partID}=scalar(keys(%{$responseType->{$partID}}));
  273:                 $result.=&Apache::loncommon::start_data_table_row().
  274:                          '<td rowspan="'.$partsseen{$partID}.'" style="vertical-align:middle">';
  275:                 if ($checkboxes) {
  276:                     $result.='<input type="checkbox" name="vPart" checked="checked" value="'.$partID.'" /></td>'.
  277:                              '<td rowspan="'.$partsseen{$partID}.'" style="vertical-align:middle">'.$display_part.'</td>';
  278:                 } else {
  279:                     $result.=$display_part.'</td>';
  280:                 }
  281:             }
  282:             $result.='<td>'.'<span class="LC_internal_info">'.$resID.'</span></td>'
  283:                     .'<td>'.&mt($responsetype).'</td>'
  284:                     .&Apache::loncommon::end_data_table_row();
  285:         }
  286:     }
  287:     $result.=&Apache::loncommon::end_data_table();
  288:     if ($checkboxes) {
  289:         $result .= '</div></fieldset>';
  290:     }
  291:     $result .= '</div><div style="padding:0;clear:both;margin:0;border:0"></div>';
  292:     return $result;
  293: }
  294: 
  295: sub part_selector_js {
  296:     my $js = <<"END";
  297: function toggleParts(formname) {
  298:     if (document.getElementById('LC_partselector')) {
  299:         var index = '';
  300:         if (document.forms.length) {
  301:             for (var i=0; i<document.forms.length; i++) {
  302:                 if (document.forms[i].name == formname) {
  303:                     index = i;
  304:                     break;
  305:                 }
  306:             }
  307:         }
  308:         if ((index != '') && (document.forms[index].elements['chooseparts'].length > 1)) {
  309:             for (var i=0; i<document.forms[index].elements['chooseparts'].length; i++) {
  310:                 if (document.forms[index].elements['chooseparts'][i].checked) {
  311:                    var val = document.forms[index].elements['chooseparts'][i].value;
  312:                     if (document.forms[index].elements['chooseparts'][i].value == 1) {
  313:                         document.getElementById('LC_partselector').style.display = 'block';
  314:                     } else {
  315:                         document.getElementById('LC_partselector').style.display = 'none';
  316:                     }
  317:                 }
  318:             }
  319:         }
  320:     }
  321: }
  322: END
  323:     return &Apache::lonhtmlcommon::scripttag($js);
  324: }
  325: 
  326: sub reset_caches {
  327:     &reset_analyze_cache();
  328:     &reset_perm();
  329:     &reset_old_essays();
  330: }
  331: 
  332: {
  333:     my %analyze_cache;
  334:     my %analyze_cache_formkeys;
  335: 
  336:     sub reset_analyze_cache {
  337: 	undef(%analyze_cache);
  338:         undef(%analyze_cache_formkeys);
  339:     }
  340: 
  341:     sub get_analyze {
  342: 	my ($symb,$uname,$udom,$no_increment,$add_to_hash,$type,$trial,$rndseed,$bubbles_per_row)=@_;
  343: 	my $key = "$symb\0$uname\0$udom";
  344:         if ($type eq 'randomizetry') {
  345:             if ($trial ne '') {
  346:                 $key .= "\0".$trial;
  347:             }
  348:         }
  349: 	if (exists($analyze_cache{$key})) {
  350:             my $getupdate = 0;
  351:             if (ref($add_to_hash) eq 'HASH') {
  352:                 foreach my $item (keys(%{$add_to_hash})) {
  353:                     if (ref($analyze_cache_formkeys{$key}) eq 'HASH') {
  354:                         if (!exists($analyze_cache_formkeys{$key}{$item})) {
  355:                             $getupdate = 1;
  356:                             last;
  357:                         }
  358:                     } else {
  359:                         $getupdate = 1;
  360:                     }
  361:                 }
  362:             }
  363:             if (!$getupdate) {
  364:                 return $analyze_cache{$key};
  365:             }
  366:         }
  367: 
  368: 	my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
  369: 	$url=&Apache::lonnet::clutter($url);
  370:         my %form = ('grade_target'      => 'analyze',
  371:                     'grade_domain'      => $udom,
  372:                     'grade_symb'        => $symb,
  373:                     'grade_courseid'    =>  $env{'request.course.id'},
  374:                     'grade_username'    => $uname,
  375:                     'grade_noincrement' => $no_increment);
  376:         if ($bubbles_per_row ne '') {
  377:             $form{'bubbles_per_row'} = $bubbles_per_row;
  378:         }
  379:         if ($type eq 'randomizetry') {
  380:             $form{'grade_questiontype'} = $type;
  381:             if ($rndseed ne '') {
  382:                 $form{'grade_rndseed'} = $rndseed;
  383:             }
  384:         }
  385:         if (ref($add_to_hash)) {
  386:             %form = (%form,%{$add_to_hash});
  387:         }
  388: 	my $subresult=&ssi_with_retries($url, $ssi_retries,%form);
  389: 	(undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
  390: 	my %analyze=&Apache::lonnet::str2hash($subresult);
  391:         if (ref($add_to_hash) eq 'HASH') {
  392:             $analyze_cache_formkeys{$key} = $add_to_hash;
  393:         } else {
  394:             $analyze_cache_formkeys{$key} = {};
  395:         }
  396: 	return $analyze_cache{$key} = \%analyze;
  397:     }
  398: 
  399:     sub get_order {
  400: 	my ($partid,$respid,$symb,$uname,$udom,$no_increment,$type,$trial,$rndseed)=@_;
  401: 	my $analyze = &get_analyze($symb,$uname,$udom,$no_increment,undef,$type,$trial,$rndseed);
  402: 	return $analyze->{"$partid.$respid.shown"};
  403:     }
  404: 
  405:     sub get_radiobutton_correct_foil {
  406: 	my ($partid,$respid,$symb,$uname,$udom,$type,$trial,$rndseed)=@_;
  407: 	my $analyze = &get_analyze($symb,$uname,$udom,undef,undef,$type,$trial,$rndseed);
  408:         my $foils = &get_order($partid,$respid,$symb,$uname,$udom,undef,$type,$trial,$rndseed);
  409:         if (ref($foils) eq 'ARRAY') {
  410: 	    foreach my $foil (@{$foils}) {
  411: 	        if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {
  412: 		    return $foil;
  413: 	        }
  414: 	    }
  415: 	}
  416:     }
  417: 
  418:     sub scantron_partids_tograde {
  419:         my ($resource,$cid,$uname,$udom,$check_for_randomlist,$bubbles_per_row,$scancode) = @_;
  420:         my (%analysis,@parts);
  421:         if (ref($resource)) {
  422:             my $symb = $resource->symb();
  423:             my $add_to_form;
  424:             if ($check_for_randomlist) {
  425:                 $add_to_form = { 'check_parts_withrandomlist' => 1,};
  426:             }
  427:             if ($scancode) {
  428:                 if (ref($add_to_form) eq 'HASH') {
  429:                     $add_to_form->{'code_for_randomlist'} = $scancode;
  430:                 } else {
  431:                     $add_to_form = { 'code_for_randomlist' => $scancode,};
  432:                 }
  433:             }
  434:             my $analyze =
  435:                 &get_analyze($symb,$uname,$udom,undef,$add_to_form,
  436:                              undef,undef,undef,$bubbles_per_row);
  437:             if (ref($analyze) eq 'HASH') {
  438:                 %analysis = %{$analyze};
  439:             }
  440:             if (ref($analysis{'parts'}) eq 'ARRAY') {
  441:                 foreach my $part (@{$analysis{'parts'}}) {
  442:                     my ($id,$respid) = split(/\./,$part);
  443:                     if (!&Apache::loncommon::check_if_partid_hidden($id,$symb,$udom,$uname)) {
  444:                         push(@parts,$part);
  445:                     }
  446:                 }
  447:             }
  448:         }
  449:         return (\%analysis,\@parts);
  450:     }
  451: 
  452: }
  453: 
  454: #--- Clean response type for display
  455: #--- Currently filters option/rank/radiobutton/match/essay/Task
  456: #        response types only.
  457: sub cleanRecord {
  458:     my ($answer,$response,$symb,$partid,$respid,$record,$order,$version,
  459: 	$uname,$udom,$type,$trial,$rndseed) = @_;
  460:     my $grayFont = '<span class="LC_internal_info">';
  461:     if ($response =~ /^(option|rank)$/) {
  462: 	my %answer=&Apache::lonnet::str2hash($answer);
  463:         my @answer = %answer;
  464:         %answer = map {&HTML::Entities::encode($_, '"<>&')} @answer;
  465: 	my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
  466: 	my ($toprow,$bottomrow);
  467: 	foreach my $foil (@$order) {
  468: 	    if ($grading{$foil} == 1) {
  469: 		$toprow.='<td><b>'.$answer{$foil}.'&nbsp;</b></td>';
  470: 	    } else {
  471: 		$toprow.='<td><i>'.$answer{$foil}.'&nbsp;</i></td>';
  472: 	    }
  473: 	    $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
  474: 	}
  475: 	return '<blockquote><table border="1">'.
  476: 	    '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
  477: 	    '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
  478: 	    $bottomrow.'</tr></table></blockquote>';
  479:     } elsif ($response eq 'match') {
  480: 	my %answer=&Apache::lonnet::str2hash($answer);
  481:         my @answer = %answer;
  482:         %answer = map {&HTML::Entities::encode($_, '"<>&')} @answer;
  483: 	my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
  484: 	my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"});
  485: 	my ($toprow,$middlerow,$bottomrow);
  486: 	foreach my $foil (@$order) {
  487: 	    my $item=shift(@items);
  488: 	    if ($grading{$foil} == 1) {
  489: 		$toprow.='<td><b>'.$item.'&nbsp;</b></td>';
  490: 		$middlerow.='<td><b>'.$grayFont.$answer{$foil}.'&nbsp;</span></b></td>';
  491: 	    } else {
  492: 		$toprow.='<td><i>'.$item.'&nbsp;</i></td>';
  493: 		$middlerow.='<td><i>'.$grayFont.$answer{$foil}.'&nbsp;</span></i></td>';
  494: 	    }
  495: 	    $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
  496: 	}
  497: 	return '<blockquote><table border="1">'.
  498: 	    '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
  499: 	    '<tr valign="top"><td>'.$grayFont.&mt('Item ID').'</span></td>'.
  500: 	    $middlerow.'</tr>'.
  501: 	    '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
  502: 	    $bottomrow.'</tr></table></blockquote>';
  503:     } elsif ($response eq 'radiobutton') {
  504: 	my %answer=&Apache::lonnet::str2hash($answer);
  505:         my @answer = %answer;
  506:         %answer = map {&HTML::Entities::encode($_, '"<>&')}  @answer;
  507: 	my ($toprow,$bottomrow);
  508: 	my $correct = 
  509: 	    &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom,$type,$trial,$rndseed);
  510: 	foreach my $foil (@$order) {
  511: 	    if (exists($answer{$foil})) {
  512: 		if ($foil eq $correct) {
  513: 		    $toprow.='<td><b>'.&mt('true').'</b></td>';
  514: 		} else {
  515: 		    $toprow.='<td><i>'.&mt('true').'</i></td>';
  516: 		}
  517: 	    } else {
  518: 		$toprow.='<td>'.&mt('false').'</td>';
  519: 	    }
  520: 	    $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
  521: 	}
  522: 	return '<blockquote><table border="1">'.
  523: 	    '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
  524: 	    '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
  525: 	    $bottomrow.'</tr></table></blockquote>';
  526:     } elsif ($response eq 'essay') {
  527: 	if (! exists ($env{'form.'.$symb})) {
  528: 	    my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade',
  529: 						  $env{'course.'.$env{'request.course.id'}.'.domain'},
  530: 						  $env{'course.'.$env{'request.course.id'}.'.num'});
  531: 
  532: 	    my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
  533: 	    $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
  534: 	    $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
  535: 	    $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
  536: 	    $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
  537: 	    $env{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob.
  538: 	}
  539:         $answer = &Apache::lontexconvert::msgtexconverted($answer);
  540: 	return '<br /><br /><blockquote><tt>'.&keywords_highlight($answer).'</tt></blockquote>';
  541:     } elsif ( $response eq 'organic') {
  542:         my $result=&mt('Smile representation: [_1]',
  543:                            '"<tt>'.&HTML::Entities::encode($answer, '"<>&').'</tt>"');
  544: 	my $jme=$record->{$version."resource.$partid.$respid.molecule"};
  545: 	$result.=&Apache::chemresponse::jme_img($jme,$answer,400);
  546: 	return $result;
  547:     } elsif ( $response eq 'Task') {
  548: 	if ( $answer eq 'SUBMITTED') {
  549: 	    my $files = $record->{$version."resource.$respid.$partid.bridgetask.portfiles"};
  550: 	    my $result = &Apache::bridgetask::file_list($files,$uname,$udom);
  551: 	    return $result;
  552: 	} elsif ( grep(/^\Q$version\E.*?\.instance$/, keys(%{$record})) ) {
  553: 	    my @matches = grep(/^\Q$version\E.*?\.instance$/,
  554: 			       keys(%{$record}));
  555: 	    return join('<br />',($version,@matches));
  556: 			       
  557: 			       
  558: 	} else {
  559: 	    my $result =
  560: 		'<p>'
  561: 		.&mt('Overall result: [_1]',
  562: 		     $record->{$version."resource.$respid.$partid.status"})
  563: 		.'</p>';
  564: 	    
  565: 	    $result .= '<ul>';
  566: 	    my @grade = grep(/^\Q${version}resource.$respid.$partid.\E[^.]*[.]status$/,
  567: 			     keys(%{$record}));
  568: 	    foreach my $grade (sort(@grade)) {
  569: 		my ($dim) = ($grade =~/[.]([^.]+)[.]status$/);
  570: 		$result.= '<li>'.&mt("Dimension: [_1], status [_2] ",
  571: 				     $dim, $record->{$grade}).
  572: 			  '</li>';
  573: 	    }
  574: 	    $result.='</ul>';
  575: 	    return $result;
  576: 	}
  577:     } elsif ( $response =~ m/(?:numerical|formula|custom)/) {
  578:         # Respect multiple input fields, see Bug #5409 
  579: 	$answer = 
  580: 	    &Apache::loncommon::format_previous_attempt_value('submission',
  581: 							      $answer);
  582: 	return $answer;
  583:     }
  584:     return &HTML::Entities::encode($answer, '"<>&');
  585: }
  586: 
  587: #-- A couple of common js functions
  588: sub commonJSfunctions {
  589:     my $request = shift;
  590:     $request->print(&Apache::lonhtmlcommon::scripttag(<<COMMONJSFUNCTIONS));
  591:     function radioSelection(radioButton) {
  592: 	var selection=null;
  593: 	if (radioButton.length > 1) {
  594: 	    for (var i=0; i<radioButton.length; i++) {
  595: 		if (radioButton[i].checked) {
  596: 		    return radioButton[i].value;
  597: 		}
  598: 	    }
  599: 	} else {
  600: 	    if (radioButton.checked) return radioButton.value;
  601: 	}
  602: 	return selection;
  603:     }
  604: 
  605:     function pullDownSelection(selectOne) {
  606: 	var selection="";
  607: 	if (selectOne.length > 1) {
  608: 	    for (var i=0; i<selectOne.length; i++) {
  609: 		if (selectOne[i].selected) {
  610: 		    return selectOne[i].value;
  611: 		}
  612: 	    }
  613: 	} else {
  614:             // only one value it must be the selected one
  615: 	    return selectOne.value;
  616: 	}
  617:     }
  618: COMMONJSFUNCTIONS
  619: }
  620: 
  621: #--- Dumps the class list with usernames,list of sections,
  622: #--- section, ids and fullnames for each user.
  623: sub getclasslist {
  624:     my ($getsec,$filterbyaccstatus,$getgroup,$symb,$submitonly,$filterbysubmstatus) = @_;
  625:     my @getsec;
  626:     my @getgroup;
  627:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
  628:     if (!ref($getsec)) {
  629: 	if ($getsec ne '' && $getsec ne 'all') {
  630: 	    @getsec=($getsec);
  631: 	}
  632:     } else {
  633: 	@getsec=@{$getsec};
  634:     }
  635:     if (grep(/^all$/,@getsec)) { undef(@getsec); }
  636:     if (!ref($getgroup)) {
  637: 	if ($getgroup ne '' && $getgroup ne 'all') {
  638: 	    @getgroup=($getgroup);
  639: 	}
  640:     } else {
  641: 	@getgroup=@{$getgroup};
  642:     }
  643:     if (grep(/^all$/,@getgroup)) { undef(@getgroup); }
  644: 
  645:     my ($classlist,$keylist)=&Apache::loncoursedata::get_classlist();
  646:     # Bail out if we were unable to get the classlist
  647:     return if (! defined($classlist));
  648:     &Apache::loncoursedata::get_group_memberships($classlist,$keylist);
  649:     #
  650:     my %sections;
  651:     my %fullnames;
  652:     my ($cdom,$cnum,$partlist);
  653:     if (($filterbysubmstatus) && ($submitonly ne 'all') && ($symb ne '')) {
  654:         $cdom = $env{"course.$env{'request.course.id'}.domain"};
  655:         $cnum = $env{"course.$env{'request.course.id'}.num"};
  656:         my $res_error;
  657:         ($partlist) = &response_type($symb,\$res_error);
  658:     }
  659:     foreach my $student (keys(%$classlist)) {
  660:         my $end      = 
  661:             $classlist->{$student}->[&Apache::loncoursedata::CL_END()];
  662:         my $start    = 
  663:             $classlist->{$student}->[&Apache::loncoursedata::CL_START()];
  664:         my $id       = 
  665:             $classlist->{$student}->[&Apache::loncoursedata::CL_ID()];
  666:         my $section  = 
  667:             $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
  668:         my $fullname = 
  669:             $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()];
  670:         my $status   = 
  671:             $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];
  672:         my $group   = 
  673:             $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
  674: 	# filter students according to status selected
  675: 	if ($filterbyaccstatus && (!($stu_status =~ /Any/))) {
  676: 	    if (!($stu_status =~ $status)) {
  677: 		delete($classlist->{$student});
  678: 		next;
  679: 	    }
  680: 	}
  681: 	# filter students according to groups selected
  682: 	my @stu_groups = split(/,/,$group);
  683: 	if (@getgroup) {
  684: 	    my $exclude = 1;
  685: 	    foreach my $grp (@getgroup) {
  686: 	        foreach my $stu_group (@stu_groups) {
  687: 	            if ($stu_group eq $grp) {
  688: 	                $exclude = 0;
  689:     	            } 
  690: 	        }
  691:     	        if (($grp eq 'none') && !$group) {
  692:         	    $exclude = 0;
  693:         	}
  694: 	    }
  695: 	    if ($exclude) {
  696: 	        delete($classlist->{$student});
  697: 		next;
  698: 	    }
  699: 	}
  700:         if (($filterbysubmstatus) && ($submitonly ne 'all') && ($symb ne '')) {
  701:             my $udom =
  702:                 $classlist->{$student}->[&Apache::loncoursedata::CL_SDOM()];
  703:             my $uname =
  704:                 $classlist->{$student}->[&Apache::loncoursedata::CL_SNAME()];
  705:             if (($symb ne '') && ($udom ne '') && ($uname ne '')) {
  706:                 if ($submitonly eq 'queued') {
  707:                     my %queue_status =
  708:                         &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
  709:                                                                 $udom,$uname);
  710:                     if (!defined($queue_status{'gradingqueue'})) {
  711:                         delete($classlist->{$student});
  712:                         next;
  713:                     }
  714:                 } else {
  715:                     my (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);
  716:                     my $submitted = 0;
  717:                     my $graded = 0;
  718:                     my $incorrect = 0;
  719:                     foreach (keys(%status)) {
  720:                         $submitted = 1 if ($status{$_} ne 'nothing');
  721:                         $graded = 1 if ($status{$_} =~ /^ungraded/);
  722:                         $incorrect = 1 if ($status{$_} =~ /^incorrect/);
  723: 
  724:                         my ($foo,$partid,$foo1) = split(/\./,$_);
  725:                         if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
  726:                             $submitted = 0;
  727:                         }
  728:                     }
  729:                     if (!$submitted && ($submitonly eq 'yes' ||
  730:                                         $submitonly eq 'incorrect' ||
  731:                                         $submitonly eq 'graded')) {
  732:                         delete($classlist->{$student});
  733:                         next;
  734:                     } elsif (!$graded && ($submitonly eq 'graded')) {
  735:                         delete($classlist->{$student});
  736:                         next;
  737:                     } elsif (!$incorrect && $submitonly eq 'incorrect') {
  738:                         delete($classlist->{$student});
  739:                         next;
  740:                     }
  741:                 }
  742:             }
  743:         }
  744: 	$section = ($section ne '' ? $section : 'none');
  745: 	if (&canview($section)) {
  746: 	    if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
  747: 		$sections{$section}++;
  748: 		if ($classlist->{$student}) {
  749: 		    $fullnames{$student}=$fullname;
  750: 		}
  751: 	    } else {
  752: 		delete($classlist->{$student});
  753: 	    }
  754: 	} else {
  755: 	    delete($classlist->{$student});
  756: 	}
  757:     }
  758:     my @sections = sort(keys(%sections));
  759:     return ($classlist,\@sections,\%fullnames);
  760: }
  761: 
  762: sub canmodify {
  763:     my ($sec)=@_;
  764:     if ($perm{'mgr'}) {
  765: 	if (!defined($perm{'mgr_section'})) {
  766: 	    # can modify whole class
  767: 	    return 1;
  768: 	} else {
  769: 	    if ($sec eq $perm{'mgr_section'}) {
  770: 		#can modify the requested section
  771: 		return 1;
  772: 	    } else {
  773: 		# can't modify the requested section
  774: 		return 0;
  775: 	    }
  776: 	}
  777:     }
  778:     #can't modify
  779:     return 0;
  780: }
  781: 
  782: sub canview {
  783:     my ($sec)=@_;
  784:     if ($perm{'vgr'}) {
  785: 	if (!defined($perm{'vgr_section'})) {
  786: 	    # can view whole class
  787: 	    return 1;
  788: 	} else {
  789: 	    if ($sec eq $perm{'vgr_section'}) {
  790: 		#can view the requested section
  791: 		return 1;
  792: 	    } else {
  793: 		# can't view the requested section
  794: 		return 0;
  795: 	    }
  796: 	}
  797:     }
  798:     #can't view
  799:     return 0;
  800: }
  801: 
  802: #--- Retrieve the grade status of a student for all the parts
  803: sub student_gradeStatus {
  804:     my ($symb,$udom,$uname,$partlist) = @_;
  805:     my %record     = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
  806:     my %partstatus = ();
  807:     foreach (@$partlist) {
  808: 	my ($status,undef)   = split(/_/,$record{"resource.$_.solved"},2);
  809: 	$status              = 'nothing' if ($status eq '');
  810: 	$partstatus{$_}      = $status;
  811: 	my $subkey           = "resource.$_.submitted_by";
  812: 	$partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
  813:     }
  814:     return %partstatus;
  815: }
  816: 
  817: # hidden form and javascript that calls the form
  818: # Use by verifyscript and viewgrades
  819: # Shows a student's view of problem and submission
  820: sub jscriptNform {
  821:     my ($symb) = @_;
  822:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
  823:     my $jscript= &Apache::lonhtmlcommon::scripttag(
  824: 	'    function viewOneStudent(user,domain) {'."\n".
  825: 	'	document.onestudent.student.value = user;'."\n".
  826: 	'	document.onestudent.userdom.value = domain;'."\n".
  827: 	'	document.onestudent.submit();'."\n".
  828: 	'    }'."\n".
  829: 	"\n");
  830:     $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n".
  831: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  832: 	'<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
  833: 	'<input type="hidden" name="command" value="submission" />'."\n".
  834: 	'<input type="hidden" name="student" value="" />'."\n".
  835: 	'<input type="hidden" name="userdom" value="" />'."\n".
  836: 	'</form>'."\n";
  837:     return $jscript;
  838: }
  839: 
  840: 
  841: 
  842: # Given the score (as a number [0-1] and the weight) what is the final
  843: # point value? This function will round to the nearest tenth, third,
  844: # or quarter if one of those is within the tolerance of .00001.
  845: sub compute_points {
  846:     my ($score, $weight) = @_;
  847:     
  848:     my $tolerance = .00001;
  849:     my $points = $score * $weight;
  850: 
  851:     # Check for nearness to 1/x.
  852:     my $check_for_nearness = sub {
  853:         my ($factor) = @_;
  854:         my $num = ($points * $factor) + $tolerance;
  855:         my $floored_num = floor($num);
  856:         if ($num - $floored_num < 2 * $tolerance * $factor) {
  857:             return $floored_num / $factor;
  858:         }
  859:         return $points;
  860:     };
  861: 
  862:     $points = $check_for_nearness->(10);
  863:     $points = $check_for_nearness->(3);
  864:     $points = $check_for_nearness->(4);
  865:     
  866:     return $points;
  867: }
  868: 
  869: #------------------ End of general use routines --------------------
  870: 
  871: #
  872: # Find most similar essay
  873: #
  874: 
  875: sub most_similar {
  876:     my ($uname,$udom,$symb,$uessay)=@_;
  877: 
  878:     unless ($symb) { return ''; }
  879: 
  880:     unless (ref($old_essays{$symb}) eq 'HASH') { return ''; }
  881: 
  882: # ignore spaces and punctuation
  883: 
  884:     $uessay=~s/\W+/ /gs;
  885: 
  886: # ignore empty submissions (occuring when only files are sent)
  887: 
  888:     unless ($uessay=~/\w+/s) { return ''; }
  889: 
  890: # these will be returned. Do not care if not at least 50 percent similar
  891:     my $limit=0.6;
  892:     my $sname='';
  893:     my $sdom='';
  894:     my $scrsid='';
  895:     my $sessay='';
  896: # go through all essays ...
  897:     foreach my $tkey (keys(%{$old_essays{$symb}})) {
  898: 	my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey));
  899: # ... except the same student
  900:         next if (($tname eq $uname) && ($tdom eq $udom));
  901: 	my $tessay=$old_essays{$symb}{$tkey};
  902: 	$tessay=~s/\W+/ /gs;
  903: # String similarity gives up if not even limit
  904: 	my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit);
  905: # Found one
  906: 	if ($tsimilar>$limit) {
  907: 	    $limit=$tsimilar;
  908: 	    $sname=$tname;
  909: 	    $sdom=$tdom;
  910: 	    $scrsid=$tcrsid;
  911: 	    $sessay=$old_essays{$symb}{$tkey};
  912: 	}
  913:     }
  914:     if ($limit>0.6) {
  915:        return ($sname,$sdom,$scrsid,$sessay,$limit);
  916:     } else {
  917:        return ('','','','',0);
  918:     }
  919: }
  920: 
  921: #-------------------------------------------------------------------
  922: 
  923: #------------------------------------ Receipt Verification Routines
  924: #
  925: 
  926: sub initialverifyreceipt {
  927:    my ($request,$symb) = @_;
  928:    &commonJSfunctions($request);
  929:    return '<form name="gradingMenu" action=""><input type="submit" value="'.&mt('Verify Receipt Number.').'" />'.
  930:         &Apache::lonnet::recprefix($env{'request.course.id'}).
  931:         '-<input type="text" name="receipt" size="4" />'.
  932:         '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
  933:         '<input type="hidden" name="command" value="verify" />'.
  934:         "</form>\n";
  935: }
  936: 
  937: #--- Check whether a receipt number is valid.---
  938: sub verifyreceipt {
  939:     my ($request,$symb) = @_;
  940: 
  941:     my $courseid = $env{'request.course.id'};
  942:     my $receipt  = &Apache::lonnet::recprefix($courseid).'-'.
  943: 	$env{'form.receipt'};
  944:     $receipt     =~ s/[^\-\d]//g;
  945: 
  946:     my $title =
  947: 	'<h3><span class="LC_info">'.
  948: 	&mt('Verifying Receipt Number [_1]',$receipt).
  949: 	'</span></h3>'."\n";
  950: 
  951:     my ($string,$contents,$matches) = ('','',0);
  952:     my (undef,undef,$fullname) = &getclasslist('all','0');
  953:     
  954:     my $receiptparts=0;
  955:     if ($env{"course.$courseid.receiptalg"} eq 'receipt2' ||
  956: 	$env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; }
  957:     my $parts=['0'];
  958:     if ($receiptparts) {
  959:         my $res_error; 
  960:         ($parts)=&response_type($symb,\$res_error);
  961:         if ($res_error) {
  962:             return &navmap_errormsg();
  963:         } 
  964:     }
  965:     
  966:     my $header = 
  967: 	&Apache::loncommon::start_data_table().
  968: 	&Apache::loncommon::start_data_table_header_row().
  969: 	'<th>&nbsp;'.&mt('Fullname').'&nbsp;</th>'."\n".
  970: 	'<th>&nbsp;'.&mt('Username').'&nbsp;</th>'."\n".
  971: 	'<th>&nbsp;'.&mt('Domain').'&nbsp;</th>';
  972:     if ($receiptparts) {
  973: 	$header.='<th>&nbsp;'.&mt('Problem Part').'&nbsp;</th>';
  974:     }
  975:     $header.=
  976: 	&Apache::loncommon::end_data_table_header_row();
  977: 
  978:     foreach (sort 
  979: 	     {
  980: 		 if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
  981: 		     return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
  982: 		 }
  983: 		 return $a cmp $b;
  984: 	     } (keys(%$fullname))) {
  985: 	my ($uname,$udom)=split(/\:/);
  986: 	foreach my $part (@$parts) {
  987: 	    if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {
  988: 		$contents.=
  989: 		    &Apache::loncommon::start_data_table_row().
  990: 		    '<td>&nbsp;'."\n".
  991: 		    '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
  992: 		    '\');" target="_self">'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".
  993: 		    '<td>&nbsp;'.$uname.'&nbsp;</td>'.
  994: 		    '<td>&nbsp;'.$udom.'&nbsp;</td>';
  995: 		if ($receiptparts) {
  996: 		    $contents.='<td>&nbsp;'.$part.'&nbsp;</td>';
  997: 		}
  998: 		$contents.= 
  999: 		    &Apache::loncommon::end_data_table_row()."\n";
 1000: 		
 1001: 		$matches++;
 1002: 	    }
 1003: 	}
 1004:     }
 1005:     if ($matches == 0) {
 1006:         $string = $title
 1007:                  .'<p class="LC_warning">'
 1008:                  .&mt('No match found for the above receipt number.')
 1009:                  .'</p>';
 1010:     } else {
 1011: 	$string = &jscriptNform($symb).$title.
 1012: 	    '<p>'.
 1013: 	    &mt('The above receipt number matches the following [quant,_1,student].',$matches).
 1014: 	    '</p>'.
 1015: 	    $header.
 1016: 	    $contents.
 1017: 	    &Apache::loncommon::end_data_table()."\n";
 1018:     }
 1019:     return $string;
 1020: }
 1021: 
 1022: #--- This is called by a number of programs.
 1023: #--- Called from the Grading Menu - View/Grade an individual student
 1024: #--- Also called directly when one clicks on the subm button 
 1025: #    on the problem page.
 1026: sub listStudents {
 1027:     my ($request,$symb,$submitonly,$divforres) = @_;
 1028: 
 1029:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
 1030:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
 1031:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
 1032:     my $getgroup  = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
 1033:     unless ($submitonly) {
 1034:         $submitonly = $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
 1035:     }
 1036: 
 1037:     my $result='';
 1038:     my $res_error;
 1039:     my ($partlist,$handgrade,$responseType,$numresp,$numessay) = &response_type($symb,\$res_error);
 1040: 
 1041:     my $table;
 1042:     if (ref($partlist) eq 'ARRAY') {
 1043:         if (scalar(@$partlist) > 1 ) {
 1044:             $table = &showResourceInfo($symb,$partlist,$responseType,'gradesub',1);
 1045:         } elsif ($divforres) {
 1046:             $table = '<div style="padding:0;clear:both;margin:0;border:0"></div>';
 1047:         } else {
 1048:             $table = '<br clear="all" />';
 1049:         }
 1050:     }
 1051: 
 1052:     my %js_lt = &Apache::lonlocal::texthash (
 1053: 		'multiple' => 'Please select a student or group of students before clicking on the Next button.',
 1054: 		'single'   => 'Please select the student before clicking on the Next button.',
 1055: 	     );
 1056:     &js_escape(\%js_lt);
 1057:     $request->print(&Apache::lonhtmlcommon::scripttag(<<LISTJAVASCRIPT));
 1058:     function checkSelect(checkBox) {
 1059: 	var ctr=0;
 1060: 	var sense="";
 1061: 	if (checkBox.length > 1) {
 1062: 	    for (var i=0; i<checkBox.length; i++) {
 1063: 		if (checkBox[i].checked) {
 1064: 		    ctr++;
 1065: 		}
 1066: 	    }
 1067: 	    sense = '$js_lt{'multiple'}';
 1068: 	} else {
 1069: 	    if (checkBox.checked) {
 1070: 		ctr = 1;
 1071: 	    }
 1072: 	    sense = '$js_lt{'single'}';
 1073: 	}
 1074: 	if (ctr == 0) {
 1075: 	    alert(sense);
 1076: 	    return false;
 1077: 	}
 1078: 	document.gradesub.submit();
 1079:     }
 1080: 
 1081:     function reLoadList(formname) {
 1082: 	if (formname.saveStatusOld.value == pullDownSelection(formname.Status)) {return;}
 1083: 	formname.command.value = 'submission';
 1084: 	formname.submit();
 1085:     }
 1086: LISTJAVASCRIPT
 1087: 
 1088:     &commonJSfunctions($request);
 1089:     $request->print($result);
 1090: 
 1091:     my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.
 1092: 	"\n".$table;
 1093: 
 1094:     $gradeTable .= &Apache::lonhtmlcommon::start_pick_box();
 1095:     $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Problem Text'))
 1096:                   .'<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n"
 1097:                   .'<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n"
 1098:                   .'<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label><br />'."\n"
 1099:                   .&Apache::lonhtmlcommon::row_closure();
 1100:     $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Answer'))
 1101:                   .'<label><input type="radio" name="vAns" value="no"  /> '.&mt('no').' </label>'."\n"
 1102:                   .'<label><input type="radio" name="vAns" value="yes" /> '.&mt('one student').' </label>'."\n"
 1103:                   .'<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label><br />'."\n"
 1104:                   .&Apache::lonhtmlcommon::row_closure();
 1105: 
 1106:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
 1107:     my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status;
 1108:     $env{'form.Status'} = $saveStatus;
 1109:     my %optiontext = &Apache::lonlocal::texthash (
 1110:                           lastonly => 'last submission',
 1111:                           last     => 'last submission with details',
 1112:                           datesub  => 'all submissions',
 1113:                           all      => 'all submissions with details',
 1114:                       );
 1115:     my $submission_options =
 1116:         '<span class="LC_nobreak">'.
 1117:         '<label><input type="radio" name="lastSub" value="lastonly" /> '.
 1118:         $optiontext{'lastonly'}.' </label></span>'."\n".
 1119:         '<span class="LC_nobreak">'.
 1120:         '<label><input type="radio" name="lastSub" value="last" /> '.
 1121:         $optiontext{'last'}.' </label></span>'."\n".
 1122:         '<span class="LC_nobreak">'.
 1123:         '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> '.
 1124:         $optiontext{'datesub'}.'</label></span>'."\n".
 1125:         '<span class="LC_nobreak">'.
 1126:         '<label><input type="radio" name="lastSub" value="all" /> '.
 1127:         $optiontext{'all'}.'</label></span>';
 1128:     my ($compmsg,$nocompmsg);
 1129:     $nocompmsg = ' checked="checked"';
 1130:     if ($numessay) {
 1131:         $compmsg = $nocompmsg;
 1132:         $nocompmsg = '';
 1133:     }
 1134:     $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Submissions'))
 1135:                   .$submission_options
 1136:                   .&Apache::lonhtmlcommon::row_closure()
 1137:                   .&Apache::lonhtmlcommon::row_title(&mt('Send Messages'))
 1138:                   .'<span class="LC_nobreak">'
 1139:                   .'<label><input type="radio" name="compmsg" value="0"'.$nocompmsg.' />'
 1140:                   .&mt('No').('&nbsp;'x2).'</label>'
 1141:                   .'<label><input type="radio" name="compmsg" value="1"'.$compmsg.' />'
 1142:                   .&mt('Yes').('&nbsp;'x2).'</label>'
 1143:                   .&Apache::lonhtmlcommon::row_closure();
 1144: 
 1145:     $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Grading Increments'))
 1146:                   .'<select name="increment">'
 1147:                   .'<option value="1">'.&mt('Whole Points').'</option>'
 1148:                   .'<option value=".5">'.&mt('Half Points').'</option>'
 1149:                   .'<option value=".25">'.&mt('Quarter Points').'</option>'
 1150:                   .'<option value=".1">'.&mt('Tenths of a Point').'</option>'
 1151:                   .'</select>';
 1152:     $gradeTable .= 
 1153:         &build_section_inputs().
 1154: 	'<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".
 1155: 	'<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 1156: 	'<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";
 1157:     if (exists($env{'form.Status'})) {
 1158: 	$gradeTable .= '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n";
 1159:     } else {
 1160:         $gradeTable .= &Apache::lonhtmlcommon::row_closure()
 1161:                       .&Apache::lonhtmlcommon::row_title(&mt('Student Status'))
 1162:                       .&Apache::lonhtmlcommon::StatusOptions(
 1163:                            $saveStatus,undef,1,'javascript:reLoadList(this.form);');
 1164:     }
 1165:     if ($numessay) {
 1166:         $gradeTable .= &Apache::lonhtmlcommon::row_closure()
 1167:                       .&Apache::lonhtmlcommon::row_title(&mt('Check For Plagiarism'))
 1168:                       .'<input type="checkbox" name="checkPlag" checked="checked" />';
 1169:     }
 1170:     $gradeTable .= &Apache::lonhtmlcommon::row_closure(1)
 1171:                   .&Apache::lonhtmlcommon::end_pick_box();
 1172: 
 1173:     $gradeTable .= '<p>'
 1174:                   .&mt("To view/grade/regrade a submission or a group of submissions, click on the check box(es) next to the student's name(s). Then click on the Next button.")."\n"
 1175:                   .'<input type="hidden" name="command" value="processGroup" />'
 1176:                   .'</p>';
 1177: 
 1178: # checkall buttons
 1179:     $gradeTable.=&check_script('gradesub', 'stuinfo');
 1180:     $gradeTable.='<input type="button" '."\n".
 1181:         'onclick="javascript:checkSelect(this.form.stuinfo);" '."\n".
 1182:         'value="'.&mt('Next').' &rarr;" /> <br />'."\n";
 1183:     $gradeTable.=&check_buttons();
 1184:     my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup);
 1185:     $gradeTable.= &Apache::loncommon::start_data_table().
 1186: 	&Apache::loncommon::start_data_table_header_row();
 1187:     my $loop = 0;
 1188:     while ($loop < 2) {
 1189: 	$gradeTable.='<th>'.&mt('No.').'</th><th>'.&mt('Select').'</th>'.
 1190: 	    '<th>'.&nameUserString('header').'&nbsp;'.&mt('Section/Group').'</th>';
 1191: 	if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
 1192: 	    foreach my $part (sort(@$partlist)) {
 1193: 		my $display_part=
 1194: 		    &get_display_part((split(/_/,$part))[0],$symb);
 1195: 		$gradeTable.=
 1196: 		    '<th>'.&mt('Part: [_1] Status',$display_part).'</th>';
 1197: 	    }
 1198: 	} elsif ($submitonly eq 'queued') {
 1199: 	    $gradeTable.='<th>'.&mt('Queue Status').'&nbsp;</th>';
 1200: 	}
 1201: 	$loop++;
 1202: #	$gradeTable.='<td></td>' if ($loop%2 ==1);
 1203:     }
 1204:     $gradeTable.=&Apache::loncommon::end_data_table_header_row()."\n";
 1205: 
 1206:     my $ctr = 0;
 1207:     foreach my $student (sort 
 1208: 			 {
 1209: 			     if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
 1210: 				 return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
 1211: 			     }
 1212: 			     return $a cmp $b;
 1213: 			 }
 1214: 			 (keys(%$fullname))) {
 1215: 	my ($uname,$udom) = split(/:/,$student);
 1216: 
 1217: 	my %status = ();
 1218: 
 1219: 	if ($submitonly eq 'queued') {
 1220: 	    my %queue_status = 
 1221: 		&Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
 1222: 							$udom,$uname);
 1223: 	    next if (!defined($queue_status{'gradingqueue'}));
 1224: 	    $status{'gradingqueue'} = $queue_status{'gradingqueue'};
 1225: 	}
 1226: 
 1227: 	if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
 1228: 	    (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);
 1229: 	    my $submitted = 0;
 1230: 	    my $graded = 0;
 1231: 	    my $incorrect = 0;
 1232: 	    foreach (keys(%status)) {
 1233: 		$submitted = 1 if ($status{$_} ne 'nothing');
 1234: 		$graded = 1 if ($status{$_} =~ /^ungraded/);
 1235: 		$incorrect = 1 if ($status{$_} =~ /^incorrect/);
 1236: 		
 1237: 		my ($foo,$partid,$foo1) = split(/\./,$_);
 1238: 		if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
 1239: 		    $submitted = 0;
 1240: 		    my ($part)=split(/\./,$partid);
 1241: 		    $gradeTable.='<input type="hidden" name="'.
 1242: 			$student.':'.$part.':submitted_by" value="'.
 1243: 			$status{'resource.'.$partid.'.submitted_by'}.'" />';
 1244: 		}
 1245: 	    }
 1246: 	    
 1247: 	    next if (!$submitted && ($submitonly eq 'yes' ||
 1248: 				     $submitonly eq 'incorrect' ||
 1249: 				     $submitonly eq 'graded'));
 1250: 	    next if (!$graded && ($submitonly eq 'graded'));
 1251: 	    next if (!$incorrect && $submitonly eq 'incorrect');
 1252: 	}
 1253: 
 1254: 	$ctr++;
 1255: 	my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
 1256:         my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
 1257: 	if ( $perm{'vgr'} eq 'F' ) {
 1258: 	    if ($ctr%2 ==1) {
 1259: 		$gradeTable.= &Apache::loncommon::start_data_table_row();
 1260: 	    }
 1261: 	    $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.
 1262:                '<td align="center"><label><input type="checkbox" name="stuinfo" value="'.
 1263:                $student.':'.$$fullname{$student}.':::SECTION'.$section.
 1264: 	       ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.
 1265: 	       &nameUserString(undef,$$fullname{$student},$uname,$udom).
 1266: 	       '&nbsp;'.$section.($group ne '' ?'/'.$group:'').'</td>'."\n";
 1267: 
 1268: 	    if ($submitonly ne 'all') {
 1269: 		foreach (sort(keys(%status))) {
 1270: 		    next if ($_ =~ /^resource.*?submitted_by$/);
 1271: 		    $gradeTable.='<td align="center">&nbsp;'.&mt($status{$_}).'&nbsp;</td>'."\n";
 1272: 		}
 1273: 	    }
 1274: #	    $gradeTable.='<td></td>' if ($ctr%2 ==1);
 1275: 	    if ($ctr%2 ==0) {
 1276: 		$gradeTable.=&Apache::loncommon::end_data_table_row()."\n";
 1277: 	    }
 1278: 	}
 1279:     }
 1280:     if ($ctr%2 ==1) {
 1281: 	$gradeTable.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>';
 1282: 	    if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
 1283: 		foreach (@$partlist) {
 1284: 		    $gradeTable.='<td>&nbsp;</td>';
 1285: 		}
 1286: 	    } elsif ($submitonly eq 'queued') {
 1287: 		$gradeTable.='<td>&nbsp;</td>';
 1288: 	    }
 1289: 	$gradeTable.=&Apache::loncommon::end_data_table_row();
 1290:     }
 1291: 
 1292:     $gradeTable.=&Apache::loncommon::end_data_table()."\n".
 1293:         '<input type="button" '.
 1294:         'onclick="javascript:checkSelect(this.form.stuinfo);" '.
 1295:         'value="'.&mt('Next').' &rarr;" /></form>'."\n";
 1296:     if ($ctr == 0) {
 1297: 	my $num_students=(scalar(keys(%$fullname)));
 1298: 	if ($num_students eq 0) {
 1299: 	    $gradeTable='<br />&nbsp;<span class="LC_warning">'.&mt('There are no students currently enrolled.').'</span>';
 1300: 	} else {
 1301: 	    my $submissions='submissions';
 1302: 	    if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; }
 1303: 	    if ($submitonly eq 'graded'   ) { $submissions = 'ungraded submissions'; }
 1304: 	    if ($submitonly eq 'queued'   ) { $submissions = 'queued submissions'; }
 1305: 	    $gradeTable='<br />&nbsp;<span class="LC_warning">'.
 1306: 		&mt('No '.$submissions.' found for this resource for any students. ([quant,_1,student] checked for '.$submissions.')',
 1307: 		    $num_students).
 1308: 		'</span><br />';
 1309: 	}
 1310:     } elsif ($ctr == 1) {
 1311: 	$gradeTable =~ s/type="checkbox"/type="checkbox" checked="checked"/;
 1312:     }
 1313:     $request->print($gradeTable);
 1314:     return '';
 1315: }
 1316: 
 1317: #---- Called from the listStudents routine
 1318: 
 1319: sub check_script {
 1320:     my ($form,$type) = @_;
 1321:     my $chkallscript = &Apache::lonhtmlcommon::scripttag('
 1322:     function checkall() {
 1323:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
 1324:             ele = document.forms.'.$form.'.elements[i];
 1325:             if (ele.name == "'.$type.'") {
 1326:             document.forms.'.$form.'.elements[i].checked=true;
 1327:                                        }
 1328:         }
 1329:     }
 1330: 
 1331:     function checksec() {
 1332:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
 1333:             ele = document.forms.'.$form.'.elements[i];
 1334:            string = document.forms.'.$form.'.chksec.value;
 1335:            if
 1336:           (ele.value.indexOf(":::SECTION"+string)>0) {
 1337:               document.forms.'.$form.'.elements[i].checked=true;
 1338:             }
 1339:         }
 1340:     }
 1341: 
 1342: 
 1343:     function uncheckall() {
 1344:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
 1345:             ele = document.forms.'.$form.'.elements[i];
 1346:             if (ele.name == "'.$type.'") {
 1347:             document.forms.'.$form.'.elements[i].checked=false;
 1348:                                        }
 1349:         }
 1350:     }
 1351: 
 1352: '."\n");
 1353:     return $chkallscript;
 1354: }
 1355: 
 1356: sub check_buttons {
 1357:     my $buttons.='<input type="button" onclick="checkall()" value="'.&mt('Check All').'" />';
 1358:     $buttons.='<input type="button" onclick="uncheckall()" value="'.&mt('Uncheck All').'" />&nbsp;';
 1359:     $buttons.='<input type="button" onclick="checksec()" value="'.&mt('Check Section/Group').'" />';
 1360:     $buttons.='<input type="text" size="5" name="chksec" />&nbsp;';
 1361:     return $buttons;
 1362: }
 1363: 
 1364: #     Displays the submissions for one student or a group of students
 1365: sub processGroup {
 1366:     my ($request,$symb) = @_;
 1367:     my $ctr        = 0;
 1368:     my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo');
 1369:     my $total      = scalar(@stuchecked)-1;
 1370: 
 1371:     foreach my $student (@stuchecked) {
 1372: 	my ($uname,$udom,$fullname) = split(/:/,$student);
 1373: 	$env{'form.student'}        = $uname;
 1374: 	$env{'form.userdom'}        = $udom;
 1375: 	$env{'form.fullname'}       = $fullname;
 1376: 	&submission($request,$ctr,$total,$symb);
 1377: 	$ctr++;
 1378:     }
 1379:     return '';
 1380: }
 1381: 
 1382: #------------------------------------------------------------------------------------
 1383: #
 1384: #-------------------------- Next few routines handles grading by student, essentially
 1385: #                           handles essay response type problem/part
 1386: #
 1387: #--- Javascript to handle the submission page functionality ---
 1388: sub sub_page_js {
 1389:     my $request = shift;
 1390:     my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = ');
 1391:     &js_escape(\$alertmsg);
 1392:     $request->print(&Apache::lonhtmlcommon::scripttag(<<SUBJAVASCRIPT));
 1393:     function updateRadio(formname,id,weight) {
 1394: 	var gradeBox = formname["GD_BOX"+id];
 1395: 	var radioButton = formname["RADVAL"+id];
 1396: 	var oldpts = formname["oldpts"+id].value;
 1397: 	var pts = checkSolved(formname,id) == 'update' ? gradeBox.value : oldpts;
 1398: 	gradeBox.value = pts;
 1399: 	var resetbox = false;
 1400: 	if (isNaN(pts) || pts < 0) {
 1401: 	    alert("$alertmsg"+pts);
 1402: 	    for (var i=0; i<radioButton.length; i++) {
 1403: 		if (radioButton[i].checked) {
 1404: 		    gradeBox.value = i;
 1405: 		    resetbox = true;
 1406: 		}
 1407: 	    }
 1408: 	    if (!resetbox) {
 1409: 		formtextbox.value = "";
 1410: 	    }
 1411: 	    return;
 1412: 	}
 1413: 
 1414: 	if (pts > weight) {
 1415: 	    var resp = confirm("You entered a value ("+pts+
 1416: 			       ") greater than the weight for the part. Accept?");
 1417: 	    if (resp == false) {
 1418: 		gradeBox.value = oldpts;
 1419: 		return;
 1420: 	    }
 1421: 	}
 1422: 
 1423: 	for (var i=0; i<radioButton.length; i++) {
 1424: 	    radioButton[i].checked=false;
 1425: 	    if (pts == i && pts != "") {
 1426: 		radioButton[i].checked=true;
 1427: 	    }
 1428: 	}
 1429: 	updateSelect(formname,id);
 1430: 	formname["stores"+id].value = "0";
 1431:     }
 1432: 
 1433:     function writeBox(formname,id,pts) {
 1434: 	var gradeBox = formname["GD_BOX"+id];
 1435: 	if (checkSolved(formname,id) == 'update') {
 1436: 	    gradeBox.value = pts;
 1437: 	} else {
 1438: 	    var oldpts = formname["oldpts"+id].value;
 1439: 	    gradeBox.value = oldpts;
 1440: 	    var radioButton = formname["RADVAL"+id];
 1441: 	    for (var i=0; i<radioButton.length; i++) {
 1442: 		radioButton[i].checked=false;
 1443: 		if (i == oldpts) {
 1444: 		    radioButton[i].checked=true;
 1445: 		}
 1446: 	    }
 1447: 	}
 1448: 	formname["stores"+id].value = "0";
 1449: 	updateSelect(formname,id);
 1450: 	return;
 1451:     }
 1452: 
 1453:     function clearRadBox(formname,id) {
 1454: 	if (checkSolved(formname,id) == 'noupdate') {
 1455: 	    updateSelect(formname,id);
 1456: 	    return;
 1457: 	}
 1458: 	gradeSelect = formname["GD_SEL"+id];
 1459: 	for (var i=0; i<gradeSelect.length; i++) {
 1460: 	    if (gradeSelect[i].selected) {
 1461: 		var selectx=i;
 1462: 	    }
 1463: 	}
 1464: 	var stores = formname["stores"+id];
 1465: 	if (selectx == stores.value) { return };
 1466: 	var gradeBox = formname["GD_BOX"+id];
 1467: 	gradeBox.value = "";
 1468: 	var radioButton = formname["RADVAL"+id];
 1469: 	for (var i=0; i<radioButton.length; i++) {
 1470: 	    radioButton[i].checked=false;
 1471: 	}
 1472: 	stores.value = selectx;
 1473:     }
 1474: 
 1475:     function checkSolved(formname,id) {
 1476: 	if (formname["solved"+id].value == "correct_by_student" && formname.overRideScore.value == 'no') {
 1477: 	    var reply = confirm("This problem has been graded correct by the computer. Do you want to change the score?");
 1478: 	    if (!reply) {return "noupdate";}
 1479: 	    formname.overRideScore.value = 'yes';
 1480: 	}
 1481: 	return "update";
 1482:     }
 1483: 
 1484:     function updateSelect(formname,id) {
 1485: 	formname["GD_SEL"+id][0].selected = true;
 1486: 	return;
 1487:     }
 1488: 
 1489: //=========== Check that a point is assigned for all the parts  ============
 1490:     function checksubmit(formname,val,total,parttot) {
 1491: 	formname.gradeOpt.value = val;
 1492: 	if (val == "Save & Next") {
 1493: 	    for (i=0;i<=total;i++) {
 1494: 		for (j=0;j<parttot;j++) {
 1495: 		    var partid = formname["partid"+i+"_"+j].value;
 1496: 		    if (formname["GD_SEL"+i+"_"+partid][0].selected) {
 1497: 			var points = formname["GD_BOX"+i+"_"+partid].value;
 1498: 			if (points == "") {
 1499: 			    var name = formname["name"+i].value;
 1500: 			    var studentID = (name != '' ? name : formname["unamedom"+i].value);
 1501: 			    var resp = confirm("You did not assign a score for "+studentID+
 1502: 					       ", part "+partid+". Continue?");
 1503: 			    if (resp == false) {
 1504: 				formname["GD_BOX"+i+"_"+partid].focus();
 1505: 				return false;
 1506: 			    }
 1507: 			}
 1508: 		    }
 1509: 		}
 1510: 	    }
 1511: 	}
 1512: 	formname.submit();
 1513:     }
 1514: 
 1515: //======= Check that a score is assigned for all the problems (page/sequence grading only) =========
 1516:     function checkSubmitPage(formname,total) {
 1517: 	noscore = new Array(100);
 1518: 	var ptr = 0;
 1519: 	for (i=1;i<total;i++) {
 1520: 	    var partid = formname["q_"+i].value;
 1521: 	    if (formname["GD_SEL"+i+"_"+partid][0].selected) {
 1522: 		var points = formname["GD_BOX"+i+"_"+partid].value;
 1523: 		var status = formname["solved"+i+"_"+partid].value;
 1524: 		if (points == "" && status != "correct_by_student") {
 1525: 		    noscore[ptr] = i;
 1526: 		    ptr++;
 1527: 		}
 1528: 	    }
 1529: 	}
 1530: 	if (ptr != 0) {
 1531: 	    var sense = ptr == 1 ? ": " : "s: ";
 1532: 	    var prolist = "";
 1533: 	    if (ptr == 1) {
 1534: 		prolist = noscore[0];
 1535: 	    } else {
 1536: 		var i = 0;
 1537: 		while (i < ptr-1) {
 1538: 		    prolist += noscore[i]+", ";
 1539: 		    i++;
 1540: 		}
 1541: 		prolist += "and "+noscore[i];
 1542: 	    }
 1543: 	    var resp = confirm("You did not assign any score for the following problem"+sense+prolist+". Continue?");
 1544: 	    if (resp == false) {
 1545: 		return false;
 1546: 	    }
 1547: 	}
 1548: 
 1549: 	formname.submit();
 1550:     }
 1551: SUBJAVASCRIPT
 1552: }
 1553: 
 1554: #--- javascript for grading message center
 1555: sub sub_grademessage_js {
 1556:     my $request = shift;
 1557:     my $iconpath = $request->dir_config('lonIconsURL');
 1558:     &commonJSfunctions($request);
 1559: 
 1560:     my $inner_js_msg_central= (<<INNERJS);
 1561: <script type="text/javascript">
 1562:     function checkInput() {
 1563:       opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);
 1564:       var nmsg   = opener.document.SCORE.savemsgN.value;
 1565:       var usrctr = document.msgcenter.usrctr.value;
 1566:       var newval = opener.document.SCORE["newmsg"+usrctr];
 1567:       newval.value = opener.checkEntities(document.msgcenter.newmsg.value);
 1568: 
 1569:       var msgchk = "";
 1570:       if (document.msgcenter.subchk.checked) {
 1571:          msgchk = "msgsub,";
 1572:       }
 1573:       var includemsg = 0;
 1574:       for (var i=1; i<=nmsg; i++) {
 1575:           var opnmsg = opener.document.SCORE["savemsg"+i];
 1576:           var frmmsg = document.msgcenter["msg"+i];
 1577:           opnmsg.value = opener.checkEntities(frmmsg.value);
 1578:           var showflg = opener.document.SCORE["shownOnce"+i];
 1579:           showflg.value = "1";
 1580:           var chkbox = document.msgcenter["msgn"+i];
 1581:           if (chkbox.checked) {
 1582:              msgchk += "savemsg"+i+",";
 1583:              includemsg = 1;
 1584:           }
 1585:       }
 1586:       if (document.msgcenter.newmsgchk.checked) {
 1587:          msgchk += "newmsg"+usrctr;
 1588:          includemsg = 1;
 1589:       }
 1590:       imgformname = opener.document.SCORE["mailicon"+usrctr];
 1591:       imgformname.src = "$iconpath/"+((includemsg) ? "mailto.gif" : "mailbkgrd.gif");
 1592:       var includemsg = opener.document.SCORE["includemsg"+usrctr];
 1593:       includemsg.value = msgchk;
 1594: 
 1595:       self.close()
 1596: 
 1597:     }
 1598: </script>
 1599: INNERJS
 1600: 
 1601:     my $start_page_msg_central =
 1602:         &Apache::loncommon::start_page('Message Central',$inner_js_msg_central,
 1603: 				       {'js_ready'  => 1,
 1604: 					'only_body' => 1,
 1605: 					'bgcolor'   =>'#FFFFFF',});
 1606:     my $end_page_msg_central =
 1607: 	&Apache::loncommon::end_page({'js_ready' => 1});
 1608: 
 1609: 
 1610:     my $docopen=&Apache::lonhtmlcommon::javascript_docopen();
 1611:     $docopen=~s/^document\.//;
 1612: 
 1613:     my %html_js_lt = &Apache::lonlocal::texthash(
 1614:                 comp => 'Compose Message for: ',
 1615:                 incl => 'Include',
 1616:                 type => 'Type',
 1617:                 subj => 'Subject',
 1618:                 mesa => 'Message',
 1619:                 new  => 'New',
 1620:                 save => 'Save',
 1621:                 canc => 'Cancel',
 1622:              );
 1623:     &html_escape(\%html_js_lt);
 1624:     &js_escape(\%html_js_lt);
 1625:     $request->print(&Apache::lonhtmlcommon::scripttag(<<SUBJAVASCRIPT));
 1626: 
 1627: //===================== Script to view submitted by ==================
 1628:   function viewSubmitter(submitter) {
 1629:     document.SCORE.refresh.value = "on";
 1630:     document.SCORE.NCT.value = "1";
 1631:     document.SCORE.unamedom0.value = submitter;
 1632:     document.SCORE.submit();
 1633:     return;
 1634:   }
 1635: 
 1636: //====================== Script for composing message ==============
 1637:    // preload images
 1638:    img1 = new Image();
 1639:    img1.src = "$iconpath/mailbkgrd.gif";
 1640:    img2 = new Image();
 1641:    img2.src = "$iconpath/mailto.gif";
 1642: 
 1643:   function msgCenter(msgform,usrctr,fullname) {
 1644:     var Nmsg  = msgform.savemsgN.value;
 1645:     savedMsgHeader(Nmsg,usrctr,fullname);
 1646:     var subject = msgform.msgsub.value;
 1647:     var msgchk = document.SCORE["includemsg"+usrctr].value;
 1648:     re = /msgsub/;
 1649:     var shwsel = "";
 1650:     if (re.test(msgchk)) { shwsel = "checked" }
 1651:     subject = (document.SCORE.shownSub.value == 0 ? checkEntities(subject) : subject);
 1652:     displaySubject(checkEntities(subject),shwsel);
 1653:     for (var i=1; i<=Nmsg; i++) {
 1654: 	var testmsg = "savemsg"+i+",";
 1655: 	re = new RegExp(testmsg,"g");
 1656: 	shwsel = "";
 1657: 	if (re.test(msgchk)) { shwsel = "checked" }
 1658: 	var message = document.SCORE["savemsg"+i].value;
 1659: 	message = (document.SCORE["shownOnce"+i].value == 0 ? checkEntities(message) : message);
 1660: 	displaySavedMsg(i,message,shwsel); //I do not get it. w/o checkEntities on saved messages,
 1661: 	                                   //any &lt; is already converted to <, etc. However, only once!!
 1662:     }
 1663:     newmsg = document.SCORE["newmsg"+usrctr].value;
 1664:     shwsel = "";
 1665:     re = /newmsg/;
 1666:     if (re.test(msgchk)) { shwsel = "checked" }
 1667:     newMsg(newmsg,shwsel);
 1668:     msgTail(); 
 1669:     return;
 1670:   }
 1671: 
 1672:   function checkEntities(strx) {
 1673:     if (strx.length == 0) return strx;
 1674:     var orgStr = ["&", "<", ">", '"']; 
 1675:     var newStr = ["&amp;", "&lt;", "&gt;", "&quot;"];
 1676:     var counter = 0;
 1677:     while (counter < 4) {
 1678: 	strx = strReplace(strx,orgStr[counter],newStr[counter]);
 1679: 	counter++;
 1680:     }
 1681:     return strx;
 1682:   }
 1683: 
 1684:   function strReplace(strx, orgStr, newStr) {
 1685:     return strx.split(orgStr).join(newStr);
 1686:   }
 1687: 
 1688:   function savedMsgHeader(Nmsg,usrctr,fullname) {
 1689:     var height = 70*Nmsg+250;
 1690:     if (height > 600) {
 1691: 	height = 600;
 1692:     }
 1693:     var xpos = (screen.width-600)/2;
 1694:     xpos = (xpos < 0) ? '0' : xpos;
 1695:     var ypos = (screen.height-height)/2-30;
 1696:     ypos = (ypos < 0) ? '0' : ypos;
 1697: 
 1698:     pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars=yes,screenx='+xpos+',screeny='+ypos+',width=700,height='+height);
 1699:     pWin.focus();
 1700:     pDoc = pWin.document;
 1701:     pDoc.$docopen;
 1702:     pDoc.write('$start_page_msg_central');
 1703: 
 1704:     pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");
 1705:     pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");
 1706:     pDoc.write("<h1>&nbsp;$html_js_lt{'comp'}\"+fullname+\"<\\/h1>");
 1707: 
 1708:     pDoc.write('<table style="border:1px solid black;"><tr>');
 1709:     pDoc.write("<td><b>$html_js_lt{'incl'}<\\/b><\\/td><td><b>$html_js_lt{'type'}<\\/b><\\/td><td><b>$html_js_lt{'mesa'}<\\/td><\\/tr>");
 1710: }
 1711:     function displaySubject(msg,shwsel) {
 1712:     pDoc = pWin.document;
 1713:     pDoc.write("<tr>");
 1714:     pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
 1715:     pDoc.write("<td>$html_js_lt{'subj'}<\\/td>");
 1716:     pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"40\\" maxlength=\\"80\\"><\\/td><\\/tr>");
 1717: }
 1718: 
 1719:   function displaySavedMsg(ctr,msg,shwsel) {
 1720:     pDoc = pWin.document;
 1721:     pDoc.write("<tr>");
 1722:     pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
 1723:     pDoc.write("<td align=\\"center\\">"+ctr+"<\\/td>");
 1724:     pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"<\\/textarea><\\/td><\\/tr>");
 1725: }
 1726: 
 1727:   function newMsg(newmsg,shwsel) {
 1728:     pDoc = pWin.document;
 1729:     pDoc.write("<tr>");
 1730:     pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
 1731:     pDoc.write("<td align=\\"center\\">$html_js_lt{'new'}<\\/td>");
 1732:     pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"<\\/textarea><\\/td><\\/tr>");
 1733: }
 1734: 
 1735:   function msgTail() {
 1736:     pDoc = pWin.document;
 1737:     pDoc.write("<\\/td><\\/tr><\\/table>&nbsp;");
 1738:     pDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'save'}\\" onclick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");
 1739:     pDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'canc'}\\" onclick=\\"self.close()\\"><br /><br />");
 1740:     pDoc.write("<\\/form>");
 1741:     pDoc.write('$end_page_msg_central');
 1742:     pDoc.close();
 1743: }
 1744: 
 1745: SUBJAVASCRIPT
 1746: }
 1747: 
 1748: #--- javascript for essay type problem --
 1749: sub sub_page_kw_js {
 1750:     my $request = shift;
 1751: 
 1752:     unless ($env{'form.compmsg'}) {
 1753:         &commonJSfunctions($request);
 1754:     }
 1755: 
 1756:     my $inner_js_highlight_central= (<<INNERJS);
 1757: <script type="text/javascript">
 1758:     function updateChoice(flag) {
 1759:       opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr);
 1760:       opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize);
 1761:       opener.document.SCORE.kwstyle.value = opener.radioSelection(document.hlCenter.kwdstyle);
 1762:       opener.document.SCORE.refresh.value = "on";
 1763:       if (opener.document.SCORE.keywords.value!=""){
 1764:          opener.document.SCORE.submit();
 1765:       }
 1766:       self.close()
 1767:     }
 1768: </script>
 1769: INNERJS
 1770: 
 1771:     my $start_page_highlight_central =
 1772:         &Apache::loncommon::start_page('Highlight Central',
 1773:                                        $inner_js_highlight_central,
 1774:                                        {'js_ready'  => 1,
 1775:                                         'only_body' => 1,
 1776:                                         'bgcolor'   =>'#FFFFFF',});
 1777:     my $end_page_highlight_central =
 1778:         &Apache::loncommon::end_page({'js_ready' => 1});
 1779: 
 1780:     my $docopen=&Apache::lonhtmlcommon::javascript_docopen();
 1781:     $docopen=~s/^document\.//;
 1782: 
 1783:     my %js_lt = &Apache::lonlocal::texthash(
 1784:                 keyw => 'Keywords list, separated by a space. Add/delete to list if desired.',
 1785:                 plse => 'Please select a word or group of words from document and then click this link.',
 1786:                 adds => 'Add selection to keyword list? Edit if desired.',
 1787:                 col1 => 'red',
 1788:                 col2 => 'green',
 1789:                 col3 => 'blue',
 1790:                 siz1 => 'normal',
 1791:                 siz2 => '+1',
 1792:                 siz3 => '+2',
 1793:                 sty1 => 'normal',
 1794:                 sty2 => 'italic',
 1795:                 sty3 => 'bold',
 1796:              );
 1797:     my %html_js_lt = &Apache::lonlocal::texthash(
 1798:                 save => 'Save',
 1799:                 canc => 'Cancel',
 1800:                 kehi => 'Keyword Highlight Options',
 1801:                 txtc => 'Text Color',
 1802:                 font => 'Font Size',
 1803:                 fnst => 'Font Style',
 1804:              );
 1805:     &js_escape(\%js_lt);
 1806:     &html_escape(\%html_js_lt);
 1807:     &js_escape(\%html_js_lt);
 1808:     $request->print(&Apache::lonhtmlcommon::scripttag(<<SUBJAVASCRIPT));
 1809: 
 1810: //===================== Show list of keywords ====================
 1811:   function keywords(formname) {
 1812:     var nret = prompt("$js_lt{'keyw'}",formname.keywords.value);
 1813:     if (nret==null) return;
 1814:     formname.keywords.value = nret;
 1815: 
 1816:     if (formname.keywords.value != "") {
 1817:         formname.refresh.value = "on";
 1818:         formname.submit();
 1819:     }
 1820:     return;
 1821:   }
 1822: 
 1823: //===================== Script to add keyword(s) ==================
 1824:   function getSel() {
 1825:     if (document.getSelection) txt = document.getSelection();
 1826:     else if (document.selection) txt = document.selection.createRange().text;
 1827:     else return;
 1828:     if (typeof(txt) != 'string') {
 1829:         txt = String(txt);
 1830:     }
 1831:     var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");
 1832:     if (cleantxt=="") {
 1833:         alert("$js_lt{'plse'}");
 1834:         return;
 1835:     }
 1836:     var nret = prompt("$js_lt{'adds'}",cleantxt);
 1837:     if (nret==null) return;
 1838:     document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret;
 1839:     if (document.SCORE.keywords.value != "") {
 1840:         document.SCORE.refresh.value = "on";
 1841:         document.SCORE.submit();
 1842:     }
 1843:     return;
 1844:   }
 1845: 
 1846: //====================== Script for keyword highlight options ==============
 1847:   function kwhighlight() {
 1848:     var kwclr    = document.SCORE.kwclr.value;
 1849:     var kwsize   = document.SCORE.kwsize.value;
 1850:     var kwstyle  = document.SCORE.kwstyle.value;
 1851:     var redsel = "";
 1852:     var grnsel = "";
 1853:     var blusel = "";
 1854:     var txtcol1 = "$js_lt{'col1'}";
 1855:     var txtcol2 = "$js_lt{'col2'}";
 1856:     var txtcol3 = "$js_lt{'col3'}";
 1857:     var txtsiz1 = "$js_lt{'siz1'}";
 1858:     var txtsiz2 = "$js_lt{'siz2'}";
 1859:     var txtsiz3 = "$js_lt{'siz3'}";
 1860:     var txtsty1 = "$js_lt{'sty1'}";
 1861:     var txtsty2 = "$js_lt{'sty2'}";
 1862:     var txtsty3 = "$js_lt{'sty3'}";
 1863:     if (kwclr=="red")   {var redsel="checked='checked'"};
 1864:     if (kwclr=="green") {var grnsel="checked='checked'"};
 1865:     if (kwclr=="blue")  {var blusel="checked='checked'"};
 1866:     var sznsel = "";
 1867:     var sz1sel = "";
 1868:     var sz2sel = "";
 1869:     if (kwsize=="0")  {var sznsel="checked='checked'"};
 1870:     if (kwsize=="+1") {var sz1sel="checked='checked'"};
 1871:     if (kwsize=="+2") {var sz2sel="checked='checked'"};
 1872:     var synsel = "";
 1873:     var syisel = "";
 1874:     var sybsel = "";
 1875:     if (kwstyle=="")    {var synsel="checked='checked'"};
 1876:     if (kwstyle=="<i>") {var syisel="checked='checked'"};
 1877:     if (kwstyle=="<b>") {var sybsel="checked='checked'"};
 1878:     highlightCentral();
 1879:     highlightbody('red',txtcol1,redsel,'0',txtsiz1,sznsel,'',txtsty1,synsel);
 1880:     highlightbody('green',txtcol2,grnsel,'+1',txtsiz2,sz1sel,'<i>',txtsty2,syisel);
 1881:     highlightbody('blue',txtcol3,blusel,'+2',txtsiz3,sz2sel,'<b>',txtsty3,sybsel);
 1882:     highlightend();
 1883:     return;
 1884:   }
 1885: 
 1886:   function highlightCentral() {
 1887: //    if (window.hwdWin) window.hwdWin.close();
 1888:     var xpos = (screen.width-400)/2;
 1889:     xpos = (xpos < 0) ? '0' : xpos;
 1890:     var ypos = (screen.height-330)/2-30;
 1891:     ypos = (ypos < 0) ? '0' : ypos;
 1892: 
 1893:     hwdWin = window.open('', 'KeywordHighlightCentral', 'resizeable=yes,toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx='+xpos+',screeny='+ypos);
 1894:     hwdWin.focus();
 1895:     var hDoc = hwdWin.document;
 1896:     hDoc.$docopen;
 1897:     hDoc.write('$start_page_highlight_central');
 1898:     hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
 1899:     hDoc.write("<h1>$html_js_lt{'kehi'}<\\/h1>");
 1900: 
 1901:     hDoc.write('<table border="0" width="100%"><tr style="background-color:#A1D676">');
 1902:     hDoc.write("<th>$html_js_lt{'txtc'}<\\/th><th>$html_js_lt{'font'}<\\/th><th>$html_js_lt{'fnst'}<\\/th><\\/tr>");
 1903:   }
 1904: 
 1905:   function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { 
 1906:     var hDoc = hwdWin.document;
 1907:     hDoc.write("<tr>");
 1908:     hDoc.write("<td align=\\"left\\">");
 1909:     hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+" \\/>&nbsp;"+clrtxt+"<\\/td>");
 1910:     hDoc.write("<td align=\\"left\\">");
 1911:     hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+" \\/>&nbsp;"+sztxt+"<\\/td>");
 1912:     hDoc.write("<td align=\\"left\\">");
 1913:     hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+" \\/>&nbsp;"+sytxt+"<\\/td>");
 1914:     hDoc.write("<\\/tr>");
 1915:   }
 1916: 
 1917:   function highlightend() { 
 1918:     var hDoc = hwdWin.document;
 1919:     hDoc.write("<\\/table><br \\/>");
 1920:     hDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'save'}\\" onclick=\\"javascript:updateChoice(1)\\" \\/>&nbsp;&nbsp;");
 1921:     hDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'canc'}\\" onclick=\\"self.close()\\" \\/><br /><br />");
 1922:     hDoc.write("<\\/form>");
 1923:     hDoc.write('$end_page_highlight_central');
 1924:     hDoc.close();
 1925:   }
 1926: 
 1927: SUBJAVASCRIPT
 1928: }
 1929: 
 1930: sub get_increment {
 1931:     my $increment = $env{'form.increment'};
 1932:     if ($increment != 1 && $increment != .5 && $increment != .25 &&
 1933:         $increment != .1) {
 1934:         $increment = 1;
 1935:     }
 1936:     return $increment;
 1937: }
 1938: 
 1939: sub gradeBox_start {
 1940:     return (
 1941:         &Apache::loncommon::start_data_table()
 1942:        .&Apache::loncommon::start_data_table_header_row()
 1943:        .'<th>'.&mt('Part').'</th>'
 1944:        .'<th>'.&mt('Points').'</th>'
 1945:        .'<th>&nbsp;</th>'
 1946:        .'<th>'.&mt('Assign Grade').'</th>'
 1947:        .'<th>'.&mt('Weight').'</th>'
 1948:        .'<th>'.&mt('Grade Status').'</th>'
 1949:        .&Apache::loncommon::end_data_table_header_row()
 1950:     );
 1951: }
 1952: 
 1953: sub gradeBox_end {
 1954:     return (
 1955:         &Apache::loncommon::end_data_table()
 1956:     );
 1957: }
 1958: #--- displays the grading box, used in essay type problem and grading by page/sequence
 1959: sub gradeBox {
 1960:     my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;
 1961:     my $checkIcon = '<img alt="'.&mt('Check Mark').
 1962: 	'" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
 1963:     my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
 1964:     my $wgtmsg = ($wgt > 0) ? &mt('(problem weight)') 
 1965:                            : '<span class="LC_info">'.&mt('problem weight assigned by computer').'</span>';
 1966:     $wgt       = ($wgt > 0 ? $wgt : '1');
 1967:     my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
 1968: 		  '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
 1969:     my $data_WGT='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
 1970:     my $display_part= &get_display_part($partid,$symb);
 1971:     my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
 1972: 				       [$partid]);
 1973:     my $aggtries = $$record{'resource.'.$partid.'.tries'};
 1974:     if ($last_resets{$partid}) {
 1975:         $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
 1976:     }
 1977:     my $result=&Apache::loncommon::start_data_table_row();
 1978:     my $ctr = 0;
 1979:     my $thisweight = 0;
 1980:     my $increment = &get_increment();
 1981: 
 1982:     my $radio.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
 1983:     while ($thisweight<=$wgt) {
 1984: 	$radio.= '<td><span class="LC_nobreak"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
 1985:         'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.
 1986: 	    $thisweight.')" value="'.$thisweight.'" '.
 1987: 	    ($score eq $thisweight ? 'checked="checked"':'').' /> '.$thisweight."</label></span></td>\n";
 1988: 	$radio.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
 1989:         $thisweight += $increment;
 1990: 	$ctr++;
 1991:     }
 1992:     $radio.='</tr></table>';
 1993: 
 1994:     my $line.='<input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
 1995: 	($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
 1996: 	'onchange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.
 1997: 	$wgt.')" /></td>'."\n";
 1998:     $line.='<td>/'.$wgt.' '.$wgtmsg.
 1999: 	($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').
 2000: 	' </td>'."\n";
 2001:     $line.='<td><select name="GD_SEL'.$counter.'_'.$partid.'" '.
 2002: 	'onchange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
 2003:     if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
 2004: 	$line.='<option></option>'.
 2005: 	    '<option value="excused" selected="selected">'.&mt('excused').'</option>';
 2006:     } else {
 2007: 	$line.='<option selected="selected"></option>'.
 2008: 	    '<option value="excused" >'.&mt('excused').'</option>';
 2009:     }
 2010:     $line.='<option value="reset status">'.&mt('reset status').'</option></select>'."\n";
 2011: 
 2012: 
 2013:     $result .= 
 2014: 	    '<td>'.$data_WGT.$display_part.'</td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>';
 2015:     $result.=&Apache::loncommon::end_data_table_row();
 2016:     $result.=&Apache::loncommon::start_data_table_row().'<td colspan="6">';
 2017:     $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n".
 2018: 	'<input type="hidden" name="oldpts'.$counter.'_'.$partid.'" value="'.$score.'" />'."\n".
 2019: 	'<input type="hidden" name="solved'.$counter.'_'.$partid.'" value="'.
 2020: 	$$record{'resource.'.$partid.'.solved'}.'" />'."\n".
 2021:         '<input type="hidden" name="totaltries'.$counter.'_'.$partid.'" value="'.
 2022:         $$record{'resource.'.$partid.'.tries'}.'" />'."\n".
 2023:         '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
 2024:         $aggtries.'" />'."\n";
 2025:     my $res_error;
 2026:     $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record,\$res_error);
 2027:     $result.='</td>'.&Apache::loncommon::end_data_table_row();
 2028:     if ($res_error) {
 2029:         return &navmap_errormsg();
 2030:     }
 2031:     return $result;
 2032: }
 2033: 
 2034: sub handback_box {
 2035:     my ($symb,$uname,$udom,$counter,$partid,$record,$res_error_pointer) = @_;
 2036:     my ($partlist,$handgrade,$responseType,$numresp,$numessay) = &response_type($symb,$res_error_pointer);
 2037:     return unless ($numessay);
 2038:     my (@respids);
 2039:     my @part_response_id = &flatten_responseType($responseType);
 2040:     foreach my $part_response_id (@part_response_id) {
 2041:     	my ($part,$resp) = @{ $part_response_id };
 2042:         if ($part eq $partid) {
 2043:             push(@respids,$resp);
 2044:         }
 2045:     }
 2046:     my $result;
 2047:     foreach my $respid (@respids) {
 2048: 	my $prefix = $counter.'_'.$partid.'_'.$respid.'_';
 2049: 	my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record);
 2050: 	next if (!@$files);
 2051: 	my $file_counter = 0;
 2052: 	foreach my $file (@$files) {
 2053: 	    if ($file =~ /\/portfolio\//) {
 2054:                 $file_counter++;
 2055:     	        my ($file_path, $file_disp) = ($file =~ m|(.+/)(.+)$|);
 2056:     	        my ($name,$version,$ext) = &file_name_version_ext($file_disp);
 2057:     	        $file_disp = "$name.$ext";
 2058:     	        $file = $file_path.$file_disp;
 2059:     	        $result.=&mt('Return commented version of [_1] to student.',
 2060:     			 '<span class="LC_filename">'.$file_disp.'</span>');
 2061:     	        $result.='<input type="file"   name="'.$prefix.'returndoc'.$file_counter.'" />'."\n";
 2062:     	        $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />'."\n";
 2063: 	    }
 2064: 	}
 2065:         if ($file_counter) {
 2066:             $result .= '<input type="hidden" name="'.$prefix.'countreturndoc" value="'.$file_counter.'" />'."\n".
 2067:                        '<span class="LC_info">'.
 2068:                        '('.&mt('File(s) will be uploaded when you click on Save &amp; Next below.',$file_counter).')</span><br /><br />';
 2069:         }
 2070:     }
 2071:     return $result;    
 2072: }
 2073: 
 2074: sub show_problem {
 2075:     my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode,$form) = @_;
 2076:     my $rendered;
 2077:     my %form = ((ref($form) eq 'HASH')? %{$form} : ());
 2078:     &Apache::lonxml::remember_problem_counter();
 2079:     if ($mode eq 'both' or $mode eq 'text') {
 2080: 	$rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,
 2081: 						       $env{'request.course.id'},
 2082: 						       undef,\%form);
 2083:     }
 2084:     if ($removeform) {
 2085: 	$rendered=~s|<form(.*?)>||g;
 2086: 	$rendered=~s|</form>||g;
 2087: 	$rendered=~s|(<input[^>]*name\s*=\s*"?)(\w+)("?)|$1would_have_been_$2$3|g;
 2088:     }
 2089:     my $companswer;
 2090:     if ($mode eq 'both' or $mode eq 'answer') {
 2091: 	&Apache::lonxml::restore_problem_counter();
 2092: 	$companswer=
 2093: 	    &Apache::loncommon::get_student_answers($symb,$uname,$udom,
 2094: 						    $env{'request.course.id'},
 2095: 						    %form);
 2096:     }
 2097:     if ($removeform) {
 2098: 	$companswer=~s|<form(.*?)>||g;
 2099: 	$companswer=~s|</form>||g;
 2100: 	$companswer=~s|name="submit"|name="would_have_been_submit"|g;
 2101:     }
 2102:     my $renderheading = &mt('View of the problem');
 2103:     my $answerheading = &mt('Correct answer');
 2104:     if (($uname ne $env{'user.name'}) || ($udom ne $env{'user.domain'})) {
 2105:         my $stu_fullname = $env{'form.fullname'};
 2106:         if ($stu_fullname eq '') {
 2107:             $stu_fullname = &Apache::loncommon::plainname($uname,$udom,'lastname');
 2108:         }
 2109:         my $forwhom = &nameUserString(undef,$stu_fullname,$uname,$udom);
 2110:         if ($forwhom ne '') {
 2111:             $renderheading = &mt('View of the problem for[_1]',$forwhom);
 2112:             $answerheading = &mt('Correct answer for[_1]',$forwhom);
 2113:         }
 2114:     }
 2115:     $rendered=
 2116:         '<div class="LC_Box">'
 2117:        .'<h3 class="LC_hcell">'.$renderheading.'</h3>'
 2118:        .$rendered
 2119:        .'</div>';
 2120:     $companswer=
 2121:         '<div class="LC_Box">'
 2122:        .'<h3 class="LC_hcell">'.$answerheading.'</h3>'
 2123:        .$companswer
 2124:        .'</div>';
 2125:     my $result;
 2126:     if ($mode eq 'both') {
 2127:         $result=$rendered.$companswer;
 2128:     } elsif ($mode eq 'text') {
 2129:         $result=$rendered;
 2130:     } elsif ($mode eq 'answer') {
 2131:         $result=$companswer;
 2132:     }
 2133:     return $result;
 2134: }
 2135: 
 2136: sub files_exist {
 2137:     my ($r, $symb) = @_;
 2138:     my @students = &Apache::loncommon::get_env_multiple('form.stuinfo');
 2139:     foreach my $student (@students) {
 2140:         my ($uname,$udom,$fullname) = split(/:/,$student);
 2141:         my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
 2142: 					      $udom,$uname);
 2143:         my ($string,$timestamp)= &get_last_submission(\%record);
 2144:         foreach my $submission (@$string) {
 2145:             my ($partid,$respid) =
 2146: 		($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
 2147:             my $files=&get_submitted_files($udom,$uname,$partid,$respid,
 2148: 					   \%record);
 2149:             return 1 if (@$files);
 2150:         }
 2151:     }
 2152:     return 0;
 2153: }
 2154: 
 2155: sub download_all_link {
 2156:     my ($r,$symb) = @_;
 2157:     unless (&files_exist($r, $symb)) {
 2158:         $r->print(&mt('There are currently no submitted documents.'));
 2159:         return;
 2160:     }
 2161:     my $all_students = 
 2162: 	join("\n", &Apache::loncommon::get_env_multiple('form.stuinfo'));
 2163: 
 2164:     my $parts =
 2165: 	join("\n",&Apache::loncommon::get_env_multiple('form.vPart'));
 2166: 
 2167:     my $identifier = &Apache::loncommon::get_cgi_id();
 2168:     &Apache::lonnet::appenv({'cgi.'.$identifier.'.students' => $all_students,
 2169:                              'cgi.'.$identifier.'.symb' => $symb,
 2170:                              'cgi.'.$identifier.'.parts' => $parts,});
 2171:     $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.
 2172: 	      &mt('Download All Submitted Documents').'</a>');
 2173:     return;
 2174: }
 2175: 
 2176: sub submit_download_link {
 2177:     my ($request,$symb) = @_;
 2178:     if (!$symb) { return ''; }
 2179:     my $res_error;
 2180:     my ($partlist,$handgrade,$responseType,$numresp,$numessay,$numdropbox) =
 2181:         &response_type($symb,\$res_error);
 2182:     if ($res_error) {
 2183:         $request->print(&mt('An error occurred retrieving response types'));
 2184:         return;
 2185:     }
 2186:     unless ($numessay) {
 2187:         $request->print(&mt('No essayresponse items found'));
 2188:         return;
 2189:     }
 2190:     my @chosenparts = &Apache::loncommon::get_env_multiple('form.vPart');
 2191:     if (@chosenparts) {
 2192:         $request->print(&showResourceInfo($symb,$partlist,$responseType,
 2193:                                           undef,undef,1));
 2194:     }
 2195:     if ($numessay) {
 2196:         my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
 2197:         my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
 2198:         my $getgroup  = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
 2199:         (undef,undef,my $fullname) = &getclasslist($getsec,1,$getgroup,$symb,$submitonly,1);
 2200:         if (ref($fullname) eq 'HASH') {
 2201:             my @students = map { $_.':'.$fullname->{$_} } (keys(%{$fullname}));
 2202:             if (@students) {
 2203:                 @{$env{'form.stuinfo'}} = @students;
 2204:                 if ($numdropbox) {
 2205:                     &download_all_link($request,$symb);
 2206:                 } else {
 2207:                     $request->print(&mt('No essayrespose items with dropbox found'));
 2208:                 }
 2209: # FIXME Need a mechanism to download essays, i.e., if $numessay > $numdropbox
 2210: # Needs to omit user's identity if resource instance is for an anonymous survey.
 2211:             } else {
 2212:                 $request->print(&mt('No students match the criteria you selected'));
 2213:             }
 2214:         } else {
 2215:             $request->print(&mt('Could not retrieve student information'));
 2216:         }
 2217:     } else {
 2218:         $request->print(&mt('No essayresponse items found'));
 2219:     }
 2220:     return;
 2221: }
 2222: 
 2223: sub build_section_inputs {
 2224:     my $section_inputs;
 2225:     if ($env{'form.section'} eq '') {
 2226:         $section_inputs .= '<input type="hidden" name="section" value="all" />'."\n";
 2227:     } else {
 2228:         my @sections = &Apache::loncommon::get_env_multiple('form.section');
 2229:         foreach my $section (@sections) {
 2230:             $section_inputs .= '<input type="hidden" name="section" value="'.$section.'" />'."\n";
 2231:         }
 2232:     }
 2233:     return $section_inputs;
 2234: }
 2235: 
 2236: # --------------------------- show submissions of a student, option to grade 
 2237: sub submission {
 2238:     my ($request,$counter,$total,$symb,$divforres,$calledby) = @_;
 2239:     my ($uname,$udom)     = ($env{'form.student'},$env{'form.userdom'});
 2240:     $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student?
 2241:     my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
 2242:     $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq '';
 2243: 
 2244:     if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }
 2245:     my $probtitle=&Apache::lonnet::gettitle($symb);
 2246:     my ($essayurl,%coursedesc_by_cid);
 2247: 
 2248:     if (!&canview($usec)) {
 2249:         $request->print(
 2250:             '<span class="LC_warning">'.
 2251:             &mt('Unable to view requested student.').
 2252:             ' '.&mt('([_1] in section [_2] in course id [_3])',
 2253:                         $uname.':'.$udom,$usec,$env{'request.course.id'}).
 2254:             '</span>');
 2255: 	return;
 2256:     }
 2257: 
 2258:     my $res_error;
 2259:     my ($partlist,$handgrade,$responseType,$numresp,$numessay) =
 2260:         &response_type($symb,\$res_error);
 2261:     if ($res_error) {
 2262:         $request->print(&navmap_errormsg());
 2263:         return;
 2264:     }
 2265: 
 2266:     if (!$env{'form.lastSub'}) { $env{'form.lastSub'} = 'datesub'; }
 2267:     if (!$env{'form.vProb'}) { $env{'form.vProb'} = 'yes'; }
 2268:     if (!$env{'form.vAns'}) { $env{'form.vAns'} = 'yes'; }
 2269:     if (($numessay) && ($calledby eq 'submission') && (!exists($env{'form.compmsg'}))) {
 2270:         $env{'form.compmsg'} = 1;
 2271:     }
 2272:     my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
 2273:     my $checkIcon = '<img alt="'.&mt('Check Mark').
 2274: 	'" src="'.$request->dir_config('lonIconsURL').
 2275: 	'/check.gif" height="16" border="0" />';
 2276: 
 2277:     # header info
 2278:     if ($counter == 0) {
 2279:         my @chosenparts = &Apache::loncommon::get_env_multiple('form.vPart');
 2280:         if (@chosenparts) {
 2281:             $request->print(&showResourceInfo($symb,$partlist,$responseType,'gradesub'));
 2282:         } elsif ($divforres) {
 2283:             $request->print('<div style="padding:0;clear:both;margin:0;border:0"></div>');
 2284:         } else {
 2285:             $request->print('<br clear="all" />');
 2286:         }
 2287: 	&sub_page_js($request);
 2288:         &sub_grademessage_js($request) if ($env{'form.compmsg'});
 2289: 	&sub_page_kw_js($request) if ($numessay);
 2290: 
 2291: 	# option to display problem, only once else it cause problems 
 2292:         # with the form later since the problem has a form.
 2293: 	if ($env{'form.vProb'} eq 'yes' or $env{'form.vAns'} eq 'yes') {
 2294: 	    my $mode;
 2295: 	    if ($env{'form.vProb'} eq 'yes' && $env{'form.vAns'} eq 'yes') {
 2296: 		$mode='both';
 2297: 	    } elsif ($env{'form.vProb'} eq 'yes') {
 2298: 		$mode='text';
 2299: 	    } elsif ($env{'form.vAns'} eq 'yes') {
 2300: 		$mode='answer';
 2301: 	    }
 2302: 	    &Apache::lonxml::clear_problem_counter();
 2303: 	    $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode));
 2304: 	}
 2305: 
 2306: 	my %keyhash = ();
 2307: 	if (($env{'form.kwclr'} eq '' && $numessay) || ($env{'form.compmsg'})) {
 2308: 	    %keyhash = &Apache::lonnet::dump('nohist_handgrade',
 2309: 					     $env{'course.'.$env{'request.course.id'}.'.domain'},
 2310: 					     $env{'course.'.$env{'request.course.id'}.'.num'});
 2311: 	}
 2312: 	# kwclr is the only variable that is guaranteed not to be blank
 2313: 	# if this subroutine has been called once.
 2314: 	if ($env{'form.kwclr'} eq '' && $numessay) {
 2315: 	    my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
 2316: 	    $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
 2317: 	    $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
 2318: 	    $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
 2319: 	    $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
 2320: 	}
 2321: 	if ($env{'form.compmsg'}) {
 2322: 	    $env{'form.msgsub'}   = $keyhash{$symb.'_subject'} ne '' ?
 2323: 		$keyhash{$symb.'_subject'} : $probtitle;
 2324: 	    $env{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0';
 2325: 	}
 2326: 
 2327: 	my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'};
 2328: 	my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
 2329: 	$request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n".
 2330: 			'<input type="hidden" name="command"    value="handgrade" />'."\n".
 2331: 			'<input type="hidden" name="Status"     value="'.$stu_status.'" />'."\n".
 2332: 			'<input type="hidden" name="overRideScore" value="'.$overRideScore.'" />'."\n".
 2333: 			'<input type="hidden" name="refresh"    value="off" />'."\n".
 2334: 			'<input type="hidden" name="studentNo"  value="" />'."\n".
 2335: 			'<input type="hidden" name="gradeOpt"   value="" />'."\n".
 2336: 			'<input type="hidden" name="symb"       value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 2337: 			'<input type="hidden" name="vProb"      value="'.$env{'form.vProb'}.'" />'."\n".
 2338: 			'<input type="hidden" name="vAns"       value="'.$env{'form.vAns'}.'" />'."\n".
 2339: 			'<input type="hidden" name="lastSub"    value="'.$env{'form.lastSub'}.'" />'."\n".
 2340: 			'<input type="hidden" name="compmsg"    value="'.$env{'form.compmsg'}.'" />'."\n".
 2341: 			&build_section_inputs().
 2342: 			'<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'" />'."\n".
 2343: 			'<input type="hidden" name="NCT"'.
 2344: 			' value="'.($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : $total+1).'" />'."\n");
 2345: 	if ($env{'form.compmsg'}) {
 2346: 	    $request->print('<input type="hidden" name="msgsub"   value="'.$env{'form.msgsub'}.'" />'."\n".
 2347: 			    '<input type="hidden" name="shownSub" value="0" />'."\n".
 2348: 			    '<input type="hidden" name="savemsgN" value="'.$env{'form.savemsgN'}.'" />'."\n");
 2349: 	}
 2350: 	if ($numessay) {
 2351: 	    $request->print('<input type="hidden" name="keywords" value="'.$env{'form.keywords'}.'" />'."\n".
 2352: 			    '<input type="hidden" name="kwclr"    value="'.$env{'form.kwclr'}.'" />'."\n".
 2353: 			    '<input type="hidden" name="kwsize"   value="'.$env{'form.kwsize'}.'" />'."\n".
 2354: 			    '<input type="hidden" name="kwstyle"  value="'.$env{'form.kwstyle'}.'" />'."\n");
 2355: 	}
 2356: 
 2357: 	my ($cts,$prnmsg) = (1,'');
 2358: 	while ($cts <= $env{'form.savemsgN'}) {
 2359: 	    $prnmsg.='<input type="hidden" name="savemsg'.$cts.'" value="'.
 2360: 		(!exists($keyhash{$symb.'_savemsg'.$cts}) ? 
 2361: 		 &Apache::lonfeedback::clear_out_html($env{'form.savemsg'.$cts}) :
 2362: 		 &Apache::lonfeedback::clear_out_html($keyhash{$symb.'_savemsg'.$cts})).
 2363: 		'" />'."\n".
 2364: 		'<input type="hidden" name="shownOnce'.$cts.'" value="0" />'."\n";
 2365: 	    $cts++;
 2366: 	}
 2367: 	$request->print($prnmsg);
 2368: 
 2369: 	if ($numessay) {
 2370: 
 2371:             my %lt = &Apache::lonlocal::texthash(
 2372:                           keyh => 'Keyword Highlighting for Essays',
 2373:                           keyw => 'Keyword Options',
 2374:                           list => 'List',
 2375:                           past => 'Paste Selection to List',
 2376:                           high => 'Highlight Attribute',
 2377:                      );
 2378: #
 2379: # Print out the keyword options line
 2380: #
 2381: 	    $request->print(
 2382:                 '<div class="LC_columnSection">'
 2383:                .'<fieldset><legend>'.$lt{'keyh'}.'</legend>'
 2384:                .&Apache::lonhtmlcommon::funclist_from_array(
 2385:                     ['<a href="javascript:keywords(document.SCORE);" target="_self">'.$lt{'list'}.'</a>',
 2386:                      '<a href="#" onmousedown="javascript:getSel(); return false"
 2387:  class="page">'.$lt{'past'}.'</a>',
 2388:                      '<a href="javascript:kwhighlight();" target="_self">'.$lt{'high'}.'</a>'],
 2389:                     {legend => $lt{'keyw'}})
 2390:                .'</fieldset></div>'
 2391:             );
 2392: 
 2393: #
 2394: # Load the other essays for similarity check
 2395: #
 2396:             (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb);
 2397:             if ($essayurl eq 'lib/templates/simpleproblem.problem') {
 2398:                 my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
 2399:                 my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
 2400:                 if ($cdom ne '' && $cnum ne '') {
 2401:                     my ($map,$id,$res) = &Apache::lonnet::decode_symb($symb);
 2402:                     if ($map =~ m{^\Quploaded/$cdom/$cnum/\E(default(?:|_\d+)\.(?:sequence|page))$}) {
 2403:                         my $apath = $1.'_'.$id;
 2404:                         $apath=~s/\W/\_/gs;
 2405:                         &init_old_essays($symb,$apath,$cdom,$cnum);
 2406:                     }
 2407:                 }
 2408:             } else {
 2409: 	        my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);
 2410: 	        $apath=&escape($apath);
 2411: 	        $apath=~s/\W/\_/gs;
 2412:                 &init_old_essays($symb,$apath,$adom,$aname);
 2413:             }
 2414:         }
 2415:     }
 2416: 
 2417: # This is where output for one specific student would start
 2418:     my $add_class = ($counter%2) ? ' LC_grade_show_user_odd_row' : '';
 2419:     $request->print(
 2420:         "\n\n"
 2421:        .'<div class="LC_grade_show_user'.$add_class.'">'
 2422:        .'<h2>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'</h2>'
 2423:        ."\n"
 2424:     );
 2425: 
 2426:     # Show additional functions if allowed
 2427:     if ($perm{'vgr'}) {
 2428:         $request->print(
 2429:             &Apache::loncommon::track_student_link(
 2430:                 'View recent activity',
 2431:                 $uname,$udom,'check')
 2432:            .' '
 2433:         );
 2434:     }
 2435:     if ($perm{'opa'}) {
 2436:         $request->print(
 2437:             &Apache::loncommon::pprmlink(
 2438:                 &mt('Set/Change parameters'),
 2439:                 $uname,$udom,$symb,'check'));
 2440:     }
 2441: 
 2442:     # Show Problem
 2443:     if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {
 2444: 	my $mode;
 2445: 	if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {
 2446: 	    $mode='both';
 2447: 	} elsif ($env{'form.vProb'} eq 'all' ) {
 2448: 	    $mode='text';
 2449: 	} elsif ($env{'form.vAns'} eq 'all') {
 2450: 	    $mode='answer';
 2451: 	}
 2452: 	&Apache::lonxml::clear_problem_counter();
 2453: 	$request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode,{'request.prefix' => 'ctr'.$counter}));
 2454:     }
 2455: 
 2456:     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
 2457: 
 2458:     # Display student info
 2459:     $request->print(($counter == 0 ? '' : '<br />'));
 2460: 
 2461:     my $result='<div class="LC_Box">'
 2462:               .'<h3 class="LC_hcell">'.&mt('Submissions').'</h3>';
 2463:     $result.='<input type="hidden" name="name'.$counter.
 2464:              '" value="'.$env{'form.fullname'}.'" />'."\n";
 2465:     if ($numresp > $numessay) {
 2466:         $result.='<p class="LC_info">'
 2467:                 .&mt('Part(s) graded correct by the computer is marked with a [_1] symbol.',$checkIcon)
 2468:                 ."</p>\n";
 2469:     }
 2470: 
 2471:     # If any part of the problem is an essayresponse, then check for collaborators
 2472:     my $fullname;
 2473:     my $col_fullnames = [];
 2474:     if ($numessay) {
 2475: 	(my $sub_result,$fullname,$col_fullnames)=
 2476: 	    &check_collaborators($symb,$uname,$udom,\%record,$handgrade,
 2477: 				 $counter);
 2478: 	$result.=$sub_result;
 2479:     }
 2480:     $request->print($result."\n");
 2481: 
 2482:     # print student answer/submission
 2483:     # Options are (1) Last submission only
 2484:     #             (2) Last submission (with detailed information for that submission)
 2485:     #             (3) All transactions (by date)
 2486:     #             (4) The whole record (with detailed information for all transactions)
 2487: 
 2488:     my ($string,$timestamp)= &get_last_submission(\%record);
 2489: 
 2490:     my $lastsubonly;
 2491: 
 2492:     if ($$timestamp eq '') {
 2493:         $lastsubonly.='<div class="LC_grade_submissions_body">'.$$string[0].'</div>'; 
 2494:     } else {
 2495:         $lastsubonly =
 2496:             '<div class="LC_grade_submissions_body">'
 2497:            .'<b>'.&mt('Date Submitted:').'</b> '.$$timestamp."\n";
 2498: 
 2499: 	my %seenparts;
 2500: 	my @part_response_id = &flatten_responseType($responseType);
 2501: 	foreach my $part (@part_response_id) {
 2502: 	    my ($partid,$respid) = @{ $part };
 2503: 	    my $display_part=&get_display_part($partid,$symb);
 2504: 	    if ($env{"form.$uname:$udom:$partid:submitted_by"}) {
 2505: 		if (exists($seenparts{$partid})) { next; }
 2506: 		$seenparts{$partid}=1;
 2507:                 $request->print(
 2508:                     '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
 2509:                     ' <b>'.&mt('Collaborative submission by: [_1]',
 2510:                                '<a href="javascript:viewSubmitter(\''.
 2511:                                $env{"form.$uname:$udom:$partid:submitted_by"}.
 2512:                                '\');" target="_self">'.
 2513:                                $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a>').
 2514:                     '<br />');
 2515: 		next;
 2516: 	    }
 2517: 	    my $responsetype = $responseType->{$partid}->{$respid};
 2518: 	    if (!exists($record{"resource.$partid.$respid.submission"})) {
 2519:                 $lastsubonly.="\n".'<div class="LC_grade_submission_part">'.
 2520:                     '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
 2521:                     ' <span class="LC_internal_info">'.
 2522:                     '('.&mt('Response ID: [_1]',$respid).')'.
 2523:                     '</span>&nbsp; &nbsp;'.
 2524: 	            '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br /><br /></div>';
 2525: 		next;
 2526: 	    }
 2527: 	    foreach my $submission (@$string) {
 2528: 		my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
 2529: 		if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
 2530: 		my ($ressub,$hide,$draft,$subval) = split(/:/,$submission,4);
 2531: 		# Similarity check
 2532:                 my $similar='';
 2533:                 my ($type,$trial,$rndseed);
 2534:                 if ($hide eq 'rand') {
 2535:                     $type = 'randomizetry';
 2536:                     $trial = $record{"resource.$partid.tries"};
 2537:                     $rndseed = $record{"resource.$partid.rndseed"};
 2538:                 }
 2539: 		if ($env{'form.checkPlag'}) {
 2540: 		    my ($oname,$odom,$ocrsid,$oessay,$osim)=
 2541: 		        &most_similar($uname,$udom,$symb,$subval);
 2542: 		    if ($osim) {
 2543: 		        $osim=int($osim*100.0);
 2544:                         if ($hide eq 'anon') {
 2545:                             $similar='<hr /><span class="LC_warning">'.&mt("Essay was found to be similar to another essay submitted for this assignment.").'<br />'.
 2546:                                      &mt('As the current submission is for an anonymous survey, no other details are available.').'</span><hr />';
 2547:                         } else {
 2548: 			    $similar='<hr />';
 2549:                             if ($essayurl eq 'lib/templates/simpleproblem.problem') {
 2550:                                 $similar .= '<h3><span class="LC_warning">'.
 2551:                                             &mt('Essay is [_1]% similar to an essay by [_2]',
 2552:                                                 $osim,
 2553:                                                 &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')').
 2554:                                             '</span></h3>';
 2555:                             } elsif ($ocrsid ne '') {
 2556:                                 my %old_course_desc;
 2557:                                 if (ref($coursedesc_by_cid{$ocrsid}) eq 'HASH') {
 2558:                                     %old_course_desc = %{$coursedesc_by_cid{$ocrsid}};
 2559:                                 } else {
 2560:                                     my $args;
 2561:                                     if ($ocrsid ne $env{'request.course.id'}) {
 2562:                                         $args = {'one_time' => 1};
 2563:                                     }
 2564:                                     %old_course_desc =
 2565:                                         &Apache::lonnet::coursedescription($ocrsid,$args);
 2566:                                     $coursedesc_by_cid{$ocrsid} = \%old_course_desc;
 2567:                                 }
 2568:                                 $similar .=
 2569:                                     '<h3><span class="LC_warning">'.
 2570: 				    &mt('Essay is [_1]% similar to an essay by [_2] in course [_3] (course id [_4]:[_5])',
 2571: 				        $osim,
 2572: 				        &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')',
 2573: 				        $old_course_desc{'description'},
 2574: 				        $old_course_desc{'num'},
 2575: 				        $old_course_desc{'domain'}).
 2576: 				    '</span></h3>';
 2577:                             } else {
 2578:                                 $similar .=
 2579:                                     '<h3><span class="LC_warning">'.
 2580:                                     &mt('Essay is [_1]% similar to an essay by [_2] in an unknown course',
 2581:                                         $osim,
 2582:                                         &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')').
 2583:                                     '</span></h3>';
 2584:                             }
 2585:                             $similar .= '<blockquote><i>'.
 2586:                                         &keywords_highlight($oessay).
 2587:                                         '</i></blockquote><hr />';
 2588: 		        }
 2589:                     }
 2590:                 }
 2591: 		my $order=&get_order($partid,$respid,$symb,$uname,$udom,
 2592:                                      undef,$type,$trial,$rndseed);
 2593:                 if (($env{'form.lastSub'} eq 'lastonly') ||
 2594:                     ($env{'form.lastSub'} eq 'datesub')  ||
 2595:                     ($env{'form.lastSub'} =~ /^(last|all)$/)) {
 2596: 		    my $display_part=&get_display_part($partid,$symb);
 2597:                     $lastsubonly.='<div class="LC_grade_submission_part">'.
 2598:                         '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
 2599:                         ' <span class="LC_internal_info">'.
 2600:                         '('.&mt('Response ID: [_1]',$respid).')'.
 2601:                         '</span>&nbsp; &nbsp;';
 2602: 		    my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
 2603: 		    if (@$files) {
 2604:                         if ($hide eq 'anon') {
 2605:                             $lastsubonly.='<br />'.&mt('[quant,_1,file] uploaded to this anonymous survey',scalar(@{$files}));
 2606:                         } else {
 2607:                             $lastsubonly.='<br /><br />'.'<b>'.&mt('Submitted Files:').'</b>'
 2608:                                          .'<br /><span class="LC_warning">';
 2609:                             if(@$files == 1) {
 2610:                                 $lastsubonly .= &mt('Like all files provided by users, this file may contain viruses!');
 2611:                             } else {
 2612:                                 $lastsubonly .= &mt('Like all files provided by users, these files may contain viruses!');
 2613:                             }
 2614:                             $lastsubonly .= '</span>';
 2615:                             foreach my $file (@$files) {
 2616:                                 &Apache::lonnet::allowuploaded('/adm/grades',$file);
 2617:                                 $lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border="0" alt="" /> '.$file.'</a>';
 2618:                             }
 2619:                         }
 2620: 			$lastsubonly.='<br />';
 2621: 		    }
 2622:                     if ($hide eq 'anon') {
 2623:                         $lastsubonly.='<br /><b>'.&mt('Anonymous Survey').'</b>'; 
 2624:                     } else {
 2625:                         $lastsubonly.='<br /><b>'.&mt('Submitted Answer:').' </b>';
 2626:                         if ($draft) {
 2627:                             $lastsubonly.= ' <span class="LC_warning">'.&mt('Draft Copy').'</span>';
 2628:                         }
 2629:                         $subval =
 2630: 			    &cleanRecord($subval,$responsetype,$symb,$partid,
 2631: 					 $respid,\%record,$order,undef,$uname,$udom,$type,$trial,$rndseed);
 2632:                         if ($responsetype eq 'essay') {
 2633:                             $subval =~ s{\n}{<br />}g;
 2634:                         }
 2635:                         $lastsubonly.=$subval."\n";
 2636:                     }
 2637:                     if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
 2638: 		    $lastsubonly.='</div>';
 2639: 		}
 2640: 	    }
 2641: 	}
 2642: 	$lastsubonly.='</div>'."\n"; # End: LC_grade_submissions_body
 2643:     }
 2644:     $request->print($lastsubonly);
 2645:     if ($env{'form.lastSub'} eq 'datesub') {
 2646:         my ($parts,$handgrade,$responseType) = &response_type($symb,\$res_error);
 2647: 	$request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));
 2648:     }
 2649:     if ($env{'form.lastSub'} =~ /^(last|all)$/) {
 2650:         my $identifier = (&canmodify($usec)? $counter : '');
 2651: 	$request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,
 2652: 								 $env{'request.course.id'},
 2653: 								 $last,'.submission',
 2654: 								 'Apache::grades::keywords_highlight',
 2655:                                                                  $usec,$identifier));
 2656:     }
 2657:     $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'
 2658: 	.$udom.'" />'."\n");
 2659:     # return if view submission with no grading option
 2660:     if (!&canmodify($usec)) {
 2661:         $request->print('<p><span class="LC_warning">'.&mt('No grading privileges').'</span></p></div>');
 2662:         return;
 2663:     } else {
 2664: 	$request->print('</div>'."\n");
 2665:     }
 2666: 
 2667:     # grading message center
 2668: 
 2669:     if ($env{'form.compmsg'}) {
 2670:         my $result='<div class="LC_Box">'.
 2671:                    '<h3 class="LC_hcell">'.&mt('Send Message').'</h3>'.
 2672:                    '<div class="LC_grade_message_center_body">';
 2673:         my ($lastname,$givenn) = split(/,/,$env{'form.fullname'});
 2674:         my $msgfor = $givenn.' '.$lastname;
 2675:         if (scalar(@$col_fullnames) > 0) {
 2676:             my $lastone = pop(@$col_fullnames);
 2677:             $msgfor .= ', '.(join ', ',@$col_fullnames).' and '.$lastone.'.';
 2678:         }
 2679:         $msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript
 2680:         $result.='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".
 2681:                  '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n".
 2682: 	         '&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.
 2683:                  ',\''.$msgfor.'\');" target="_self">'.
 2684:                  &mt('Compose message to student'.(scalar(@$col_fullnames) >= 1 ? 's' : '')).'</a><label> ('.
 2685:                  &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'.
 2686:                  ' <img src="'.$request->dir_config('lonIconsURL').
 2687:                  '/mailbkgrd.gif" width="14" height="10" alt="" name="mailicon'.$counter.'" />'."\n".
 2688:                  '<br />&nbsp;('.
 2689:                  &mt('Message will be sent when you click on Save &amp; Next below.').")\n".
 2690: 	         '</div></div>';
 2691:         $request->print($result);
 2692:     }
 2693: 
 2694:     my %seen = ();
 2695:     my @partlist;
 2696:     my @gradePartRespid;
 2697:     my @part_response_id = &flatten_responseType($responseType);
 2698:     $request->print(
 2699:         '<div class="LC_Box">'
 2700:        .'<h3 class="LC_hcell">'.&mt('Assign Grades').'</h3>'
 2701:     );
 2702:     $request->print(&gradeBox_start());
 2703:     foreach my $part_response_id (@part_response_id) {
 2704:     	my ($partid,$respid) = @{ $part_response_id };
 2705: 	my $part_resp = join('_',@{ $part_response_id });
 2706: 	next if ($seen{$partid} > 0);
 2707: 	$seen{$partid}++;
 2708: 	push(@partlist,$partid);
 2709: 	push(@gradePartRespid,$partid.'.'.$respid);
 2710: 	$request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
 2711:     }
 2712:     $request->print(&gradeBox_end()); # </div>
 2713:     $request->print('</div>');
 2714: 
 2715:     $request->print('<div class="LC_grade_info_links">');
 2716:     $request->print('</div>');
 2717: 
 2718:     $result='<input type="hidden" name="partlist'.$counter.
 2719: 	'" value="'.(join ":",@partlist).'" />'."\n";
 2720:     $result.='<input type="hidden" name="gradePartRespid'.
 2721: 	'" value="'.(join ":",@gradePartRespid).'" />'."\n" if ($counter == 0);
 2722:     my $ctr = 0;
 2723:     while ($ctr < scalar(@partlist)) {
 2724: 	$result.='<input type="hidden" name="partid'.$counter.'_'.$ctr.'" value="'.
 2725: 	    $partlist[$ctr].'" />'."\n";
 2726: 	$ctr++;
 2727:     }
 2728:     $request->print($result.''."\n");
 2729: 
 2730: # Done with printing info for one student
 2731: 
 2732:     $request->print('</div>');#LC_grade_show_user
 2733: 
 2734: 
 2735:     # print end of form
 2736:     if ($counter == $total) {
 2737:         my $endform='<br /><hr /><table border="0"><tr><td>'."\n";
 2738: 	$endform.='<input type="button" value="'.&mt('Save &amp; Next').'" '.
 2739: 	    'onclick="javascript:checksubmit(this.form,\'Save & Next\','.
 2740: 	    $total.','.scalar(@partlist).');" target="_self" /> &nbsp;'."\n";
 2741: 	my $ntstu ='<select name="NTSTU">'.
 2742: 	    '<option>1</option><option>2</option>'.
 2743: 	    '<option>3</option><option>5</option>'.
 2744: 	    '<option>7</option><option>10</option></select>'."\n";
 2745: 	my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1');
 2746: 	$ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</;
 2747:         $endform.=&mt('[_1]student(s)',$ntstu);
 2748: 	$endform.='&nbsp;&nbsp;<input type="button" value="'.&mt('Previous').'" '.
 2749: 	    'onclick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> &nbsp;'."\n".
 2750: 	    '<input type="button" value="'.&mt('Next').'" '.
 2751: 	    'onclick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> &nbsp;';
 2752:         $endform.='<span class="LC_warning">'.
 2753:                   &mt('(Next and Previous (student) do not save the scores.)').
 2754:                   '</span>'."\n" ;
 2755:         $endform.="<input type='hidden' value='".&get_increment().
 2756:             "' name='increment' />";
 2757: 	$endform.='</td></tr></table></form>';
 2758: 	$request->print($endform);
 2759:     }
 2760:     return '';
 2761: }
 2762: 
 2763: sub check_collaborators {
 2764:     my ($symb,$uname,$udom,$record,$handgrade,$counter) = @_;
 2765:     my ($result,@col_fullnames);
 2766:     my ($classlist,undef,$fullname) = &getclasslist('all','0');
 2767:     foreach my $part (keys(%$handgrade)) {
 2768: 	my $ncol = &Apache::lonnet::EXT('resource.'.$part.
 2769: 					'.maxcollaborators',
 2770: 					$symb,$udom,$uname);
 2771: 	next if ($ncol <= 0);
 2772: 	$part =~ s/\_/\./g;
 2773: 	next if ($record->{'resource.'.$part.'.collaborators'} eq '');
 2774: 	my (@good_collaborators, @bad_collaborators);
 2775: 	foreach my $possible_collaborator
 2776: 	    (split(/[,;\s]+/,$record->{'resource.'.$part.'.collaborators'})) { 
 2777: 	    $possible_collaborator =~ s/[\$\^\(\)]//g;
 2778: 	    next if ($possible_collaborator eq '');
 2779: 	    my ($co_name,$co_dom) = split(/:/,$possible_collaborator);
 2780: 	    $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);
 2781: 	    next if ($co_name eq $uname && $co_dom eq $udom);
 2782: 	    # Doing this grep allows 'fuzzy' specification
 2783: 	    my @matches = grep(/^\Q$co_name\E:\Q$co_dom\E$/i, 
 2784: 			       keys(%$classlist));
 2785: 	    if (! scalar(@matches)) {
 2786: 		push(@bad_collaborators, $possible_collaborator);
 2787: 	    } else {
 2788: 		push(@good_collaborators, @matches);
 2789: 	    }
 2790: 	}
 2791: 	if (scalar(@good_collaborators) != 0) {
 2792: 	    $result.='<br />'.&mt('Collaborators:').'<ol>';
 2793: 	    foreach my $name (@good_collaborators) {
 2794: 		my ($lastname,$givenn) = split(/,/,$$fullname{$name});
 2795: 		push(@col_fullnames, $givenn.' '.$lastname);
 2796: 		$result.='<li>'.$fullname->{$name}.'</li>';
 2797: 	    }
 2798: 	    $result.='</ol><br />'."\n";
 2799: 	    my ($part)=split(/\./,$part);
 2800: 	    $result.='<input type="hidden" name="collaborator'.$counter.
 2801: 		'" value="'.$part.':'.(join ':',@good_collaborators).'" />'.
 2802: 		"\n";
 2803: 	}
 2804: 	if (scalar(@bad_collaborators) > 0) {
 2805: 	    $result.='<div class="LC_warning">';
 2806: 	    $result.=&mt('This student has submitted [quant,_1,invalid collaborator]: [_2]',scalar(@bad_collaborators),join(', ',@bad_collaborators));
 2807: 	    $result .= '</div>';
 2808: 	}         
 2809: 	if (scalar(@bad_collaborators > $ncol)) {
 2810: 	    $result .= '<div class="LC_warning">';
 2811: 	    $result .= &mt('This student has submitted too many '.
 2812: 		'collaborators.  Maximum is [_1].',$ncol);
 2813: 	    $result .= '</div>';
 2814: 	}
 2815:     }
 2816:     return ($result,$fullname,\@col_fullnames);
 2817: }
 2818: 
 2819: #--- Retrieve the last submission for all the parts
 2820: sub get_last_submission {
 2821:     my ($returnhash)=@_;
 2822:     my (@string,$timestamp,%lasthidden);
 2823:     if ($$returnhash{'version'}) {
 2824: 	my %lasthash=();
 2825: 	my ($version);
 2826: 	for ($version=1;$version<=$$returnhash{'version'};$version++) {
 2827: 	    foreach my $key (sort(split(/\:/,
 2828: 					$$returnhash{$version.':keys'}))) {
 2829: 		$lasthash{$key}=$$returnhash{$version.':'.$key};
 2830: 		$timestamp = 
 2831: 		    &Apache::lonlocal::locallocaltime($$returnhash{$version.':timestamp'});
 2832: 	    }
 2833: 	}
 2834:         my (%typeparts,%randombytry);
 2835:         my $showsurv = 
 2836:             &Apache::lonnet::allowed('vas',$env{'request.course.id'});
 2837:         foreach my $key (sort(keys(%lasthash))) {
 2838:             if ($key =~ /\.type$/) {
 2839:                 if (($lasthash{$key} eq 'anonsurvey') || 
 2840:                     ($lasthash{$key} eq 'anonsurveycred') ||
 2841:                     ($lasthash{$key} eq 'randomizetry')) {
 2842:                     my ($ign,@parts) = split(/\./,$key);
 2843:                     pop(@parts);
 2844:                     my $id = join('.',@parts);
 2845:                     if ($lasthash{$key} eq 'randomizetry') {
 2846:                         $randombytry{$ign.'.'.$id} = $lasthash{$key};
 2847:                     } else {
 2848:                         unless ($showsurv) {
 2849:                             $typeparts{$ign.'.'.$id} = $lasthash{$key};
 2850:                         }
 2851:                     }
 2852:                     delete($lasthash{$key});
 2853:                 }
 2854:             }
 2855:         }
 2856:         my @hidden = keys(%typeparts);
 2857:         my @randomize = keys(%randombytry);
 2858: 	foreach my $key (keys(%lasthash)) {
 2859: 	    next if ($key !~ /\.submission$/);
 2860:             my $hide;
 2861:             if (@hidden) {
 2862:                 foreach my $id (@hidden) {
 2863:                     if ($key =~ /^\Q$id\E/) {
 2864:                         $hide = 'anon';
 2865:                         last;
 2866:                     }
 2867:                 }
 2868:             }
 2869:             unless ($hide) {
 2870:                 if (@randomize) {
 2871:                     foreach my $id (@randomize) {
 2872:                         if ($key =~ /^\Q$id\E/) {
 2873:                             $hide = 'rand';
 2874:                             last;
 2875:                         }
 2876:                     }
 2877:                 }
 2878:             }
 2879: 	    my ($partid,$foo) = split(/submission$/,$key);
 2880: 	    my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ? 1 : 0;
 2881:             push(@string, join(':', $key, $hide, $draft, (
 2882:                 ref($lasthash{$key}) eq 'ARRAY' ?
 2883:                     join(',', @{$lasthash{$key}}) : $lasthash{$key}) ));
 2884: 	}
 2885:     }
 2886:     if (!@string) {
 2887: 	$string[0] =
 2888: 	    '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span>';
 2889:     }
 2890:     return (\@string,\$timestamp);
 2891: }
 2892: 
 2893: #--- High light keywords, with style choosen by user.
 2894: sub keywords_highlight {
 2895:     my $string    = shift;
 2896:     my $size      = $env{'form.kwsize'} eq '0' ? '' : 'size='.$env{'form.kwsize'};
 2897:     my $styleon   = $env{'form.kwstyle'} eq ''  ? '' : $env{'form.kwstyle'};
 2898:     (my $styleoff = $styleon) =~ s/\</\<\//;
 2899:     my @keylist   = split(/[,\s+]/,$env{'form.keywords'});
 2900:     foreach my $keyword (@keylist) {
 2901: 	$string =~ s/\b\Q$keyword\E(\b|\.)/<font color\=$env{'form.kwclr'} $size\>$styleon$keyword$styleoff<\/font>/gi;
 2902:     }
 2903:     return $string;
 2904: }
 2905: 
 2906: # For Tasks provide a mechanism to display previous version for one specific student
 2907: 
 2908: sub show_previous_task_version {
 2909:     my ($request,$symb) = @_;
 2910:     if ($symb eq '') {
 2911:         $request->print(
 2912:             '<span class="LC_error">'.
 2913:             &mt('Unable to handle ambiguous references.').
 2914:             '</span>');
 2915:         return '';
 2916:     }
 2917:     my ($uname,$udom) = ($env{'form.student'},$env{'form.userdom'});
 2918:     my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
 2919:     if (!&canview($usec)) {
 2920:         $request->print('<span class="LC_warning">'.
 2921:                         &mt('Unable to view previous version for requested student.').
 2922:                         ' '.&mt('([_1] in section [_2] in course id [_3])',
 2923:                                 $uname.':'.$udom,$usec,$env{'request.course.id'}).
 2924:                         '</span>');
 2925:         return;
 2926:     }
 2927:     my $mode = 'both';
 2928:     my $isTask = ($symb =~/\.task$/);
 2929:     if ($isTask) {
 2930:         if ($env{'form.previousversion'} =~ /^\d+$/) {
 2931:             if ($env{'form.fullname'} eq '') {
 2932:                 $env{'form.fullname'} =
 2933:                     &Apache::loncommon::plainname($uname,$udom,'lastname');
 2934:             }
 2935:             my $probtitle=&Apache::lonnet::gettitle($symb);
 2936:             $request->print("\n\n".
 2937:                             '<div class="LC_grade_show_user">'.
 2938:                             '<h2>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
 2939:                             '</h2>'."\n");
 2940:             &Apache::lonxml::clear_problem_counter();
 2941:             $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode,
 2942:                             {'previousversion' => $env{'form.previousversion'} }));
 2943:             $request->print("\n</div>");
 2944:         }
 2945:     }
 2946:     return;
 2947: }
 2948: 
 2949: sub choose_task_version_form {
 2950:     my ($symb,$uname,$udom,$nomenu) = @_;
 2951:     my $isTask = ($symb =~/\.task$/);
 2952:     my ($current,$version,$result,$js,$displayed,$rowtitle);
 2953:     if ($isTask) {
 2954:         my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
 2955:                                               $udom,$uname);
 2956:         if (($record{'resource.0.version'} eq '') ||
 2957:             ($record{'resource.0.version'} < 2)) {
 2958:             return ($record{'resource.0.version'},
 2959:                     $record{'resource.0.version'},$result,$js);
 2960:         } else {
 2961:             $current = $record{'resource.0.version'};
 2962:         }
 2963:         if ($env{'form.previousversion'}) {
 2964:             $displayed = $env{'form.previousversion'};
 2965:             $rowtitle = &mt('Choose another version:')
 2966:         } else {
 2967:             $displayed = $current;
 2968:             $rowtitle = &mt('Show earlier version:');
 2969:         }
 2970:         $result = '<div class="LC_left_float">';
 2971:         my $list;
 2972:         my $numversions = 0;
 2973:         for (my $i=1; $i<=$record{'resource.0.version'}; $i++) {
 2974:             if ($i == $current) {
 2975:                 if (!$env{'form.previousversion'} || $nomenu) {
 2976:                     next;
 2977:                 } else {
 2978:                     $list .= '<option value="'.$i.'">'.&mt('Current').'</option>'."\n";
 2979:                     $numversions ++;
 2980:                 }
 2981:             } elsif (defined($record{'resource.'.$i.'.0.status'})) {
 2982:                 unless ($i == $env{'form.previousversion'}) {
 2983:                     $numversions ++;
 2984:                 }
 2985:                 $list .= '<option value="'.$i.'">'.$i.'</option>'."\n";
 2986:             }
 2987:         }
 2988:         if ($numversions) {
 2989:             $symb = &HTML::Entities::encode($symb,'<>"&');
 2990:             $result .=
 2991:                 '<form name="getprev" method="post" action=""'.
 2992:                 ' onsubmit="return previousVersion('."'$uname','$udom','$symb','$displayed'".');">'.
 2993:                 &Apache::loncommon::start_data_table().
 2994:                 &Apache::loncommon::start_data_table_row().
 2995:                 '<th align="left">'.$rowtitle.'</th>'.
 2996:                 '<td><select name="version">'.
 2997:                 '<option>'.&mt('Select').'</option>'.
 2998:                 $list.
 2999:                 '</select></td>'.
 3000:                 &Apache::loncommon::end_data_table_row();
 3001:             unless ($nomenu) {
 3002:                 $result .= &Apache::loncommon::start_data_table_row().
 3003:                 '<th align="left">'.&mt('Open in new window').'</th>'.
 3004:                 '<td><span class="LC_nobreak">'.
 3005:                 '<label><input type="radio" name="prevwin" value="1" />'.
 3006:                 &mt('Yes').'</label>'.
 3007:                 '<label><input type="radio" name="prevwin" value="0" checked="checked" />'.&mt('No').'</label>'.
 3008:                 '</span></td>'.
 3009:                 &Apache::loncommon::end_data_table_row();
 3010:             }
 3011:             $result .=
 3012:                 &Apache::loncommon::start_data_table_row().
 3013:                 '<th align="left">&nbsp;</th>'.
 3014:                 '<td>'.
 3015:                 '<input type="submit" name="prevsub" value="'.&mt('Display').'" />'.
 3016:                 '</td>'.
 3017:                 &Apache::loncommon::end_data_table_row().
 3018:                 &Apache::loncommon::end_data_table().
 3019:                 '</form>';
 3020:             $js = &previous_display_javascript($nomenu,$current);
 3021:         } elsif ($displayed && $nomenu) {
 3022:             $result .= '<a href="javascript:window.close()">'.&mt('Close window').'</a>';
 3023:         } else {
 3024:             $result .= &mt('No previous versions to show for this student');
 3025:         }
 3026:         $result .= '</div>';
 3027:     }
 3028:     return ($current,$displayed,$result,$js);
 3029: }
 3030: 
 3031: sub previous_display_javascript {
 3032:     my ($nomenu,$current) = @_;
 3033:     my $js = <<"JSONE";
 3034: <script type="text/javascript">
 3035: // <![CDATA[
 3036: function previousVersion(uname,udom,symb) {
 3037:     var current = '$current';
 3038:     var version = document.getprev.version.options[document.getprev.version.selectedIndex].value;
 3039:     var prevstr = new RegExp("^\\\\d+\$");
 3040:     if (!prevstr.test(version)) {
 3041:         return false;
 3042:     }
 3043:     var url = '';
 3044:     if (version == current) {
 3045:         url = '/adm/grades?student='+uname+'&userdom='+udom+'&symb='+symb+'&command=submission';
 3046:     } else {
 3047:         url = '/adm/grades?student='+uname+'&userdom='+udom+'&symb='+symb+'&command=versionsub&previousversion='+version;
 3048:     }
 3049: JSONE
 3050:     if ($nomenu) {
 3051:         $js .= <<"JSTWO";
 3052:     document.location.href = url;
 3053: JSTWO
 3054:     } else {
 3055:         $js .= <<"JSTHREE";
 3056:     var newwin = 0;
 3057:     for (var i=0; i<document.getprev.prevwin.length; i++) {
 3058:         if (document.getprev.prevwin[i].checked == true) {
 3059:             newwin = document.getprev.prevwin[i].value;
 3060:         }
 3061:     }
 3062:     if (newwin == 1) {
 3063:         var options = 'height=600,width=800,resizable=yes,scrollbars=yes,location=no,menubar=no,toolbar=no';
 3064:         url = url+'&inhibitmenu=yes';
 3065:         if (typeof(previousWin) == 'undefined' || previousWin.closed) {
 3066:             previousWin = window.open(url,'',options,1);
 3067:         } else {
 3068:             previousWin.location.href = url;
 3069:         }
 3070:         previousWin.focus();
 3071:         return false;
 3072:     } else {
 3073:         document.location.href = url;
 3074:         return false;
 3075:     }
 3076: JSTHREE
 3077:     }
 3078:     $js .= <<"ENDJS";
 3079:     return false;
 3080: }
 3081: // ]]>
 3082: </script>
 3083: ENDJS
 3084: 
 3085: }
 3086: 
 3087: #--- Called from submission routine
 3088: sub processHandGrade {
 3089:     my ($request,$symb) = @_;
 3090:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
 3091:     my $button = $env{'form.gradeOpt'};
 3092:     my $ngrade = $env{'form.NCT'};
 3093:     my $ntstu  = $env{'form.NTSTU'};
 3094:     my $cdom   = $env{'course.'.$env{'request.course.id'}.'.domain'};
 3095:     my $cnum   = $env{'course.'.$env{'request.course.id'}.'.num'};
 3096: 
 3097:     if ($button eq 'Save & Next') {
 3098: 	my $ctr = 0;
 3099: 	while ($ctr < $ngrade) {
 3100: 	    my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr});
 3101: 	    my ($errorflag,$pts,$wgt,$numhidden) = 
 3102:                 &saveHandGrade($request,$symb,$uname,$udom,$ctr);
 3103: 	    if ($errorflag eq 'no_score') {
 3104: 		$ctr++;
 3105: 		next;
 3106: 	    }
 3107: 	    if ($errorflag eq 'not_allowed') {
 3108:                 $request->print(
 3109:                     '<span class="LC_error">'
 3110:                    .&mt('Not allowed to modify grades for [_1]',"$uname:$udom")
 3111:                    .'</span>');
 3112: 		$ctr++;
 3113: 		next;
 3114: 	    }
 3115:             if ($numhidden) {
 3116:                 $request->print(
 3117:                     '<span class="LC_info">'
 3118:                    .&mt('For [_1]: [quant,_2,transaction] hidden',"$uname:$udom",$numhidden)
 3119:                    .'</span><br />');
 3120:             }
 3121: 	    my $includemsg = $env{'form.includemsg'.$ctr};
 3122: 	    my ($subject,$message,$msgstatus) = ('','','');
 3123: 	    my $restitle = &Apache::lonnet::gettitle($symb);
 3124:             my ($feedurl,$showsymb) =
 3125: 		&get_feedurl_and_symb($symb,$uname,$udom);
 3126: 	    my $messagetail;
 3127: 	    if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) {
 3128: 		$subject = $env{'form.msgsub'} if ($includemsg =~ /msgsub/);
 3129: 		unless ($subject=~/\w/) { $subject=&mt('Grading Feedback'); }
 3130: 		$subject.=' ['.$restitle.']';
 3131: 		my (@msgnum) = split(/,/,$includemsg);
 3132: 		foreach (@msgnum) {
 3133: 		    $message.=$env{'form.'.$_} if ($_ =~ /savemsg|newmsg/ && $_ ne '');
 3134: 		}
 3135: 		$message =&Apache::lonfeedback::clear_out_html($message);
 3136: 		if ($env{'form.withgrades'.$ctr}) {
 3137: 		    $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;
 3138: 		    $messagetail = " for <a href=\"".
 3139: 		                   $feedurl."?symb=$showsymb\">$restitle</a>";
 3140: 		}
 3141: 		$msgstatus = 
 3142:                     &Apache::lonmsg::user_normal_msg($uname,$udom,$subject,
 3143: 						     $message.$messagetail,
 3144:                                                      undef,$feedurl,undef,
 3145:                                                      undef,undef,$showsymb,
 3146:                                                      $restitle);
 3147: 		$request->print('<br />'.&mt('Sending message to [_1]',$uname.':'.$udom).': '.
 3148: 				$msgstatus.'<br />');
 3149: 	    }
 3150: 	    if ($env{'form.collaborator'.$ctr}) {
 3151: 		my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr");
 3152: 		foreach my $collabstr (@collabstrs) {
 3153: 		    my ($part,@collaborators) = split(/:/,$collabstr);
 3154: 		    foreach my $collaborator (@collaborators) {
 3155: 			my ($errorflag,$pts,$wgt) = 
 3156: 			    &saveHandGrade($request,$symb,$collaborator,$udom,$ctr,
 3157: 					   $env{'form.unamedom'.$ctr},$part);
 3158: 			if ($errorflag eq 'not_allowed') {
 3159: 			    $request->print("<span class=\"LC_error\">".&mt('Not allowed to modify grades for [_1]',"$collaborator:$udom")."</span>");
 3160: 			    next;
 3161: 			} elsif ($message ne '') {
 3162: 			    my ($baseurl,$showsymb) = 
 3163: 				&get_feedurl_and_symb($symb,$collaborator,
 3164: 						      $udom);
 3165: 			    if ($env{'form.withgrades'.$ctr}) {
 3166: 				$messagetail = " for <a href=\"".
 3167:                                     $baseurl."?symb=$showsymb\">$restitle</a>";
 3168: 			    }
 3169: 			    $msgstatus = 
 3170: 				&Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
 3171: 			}
 3172: 		    }
 3173: 		}
 3174: 	    }
 3175: 	    $ctr++;
 3176: 	}
 3177:     }
 3178: 
 3179:     my $res_error;
 3180:     my ($partlist,$handgrade,$responseType,$numresp,$numessay) = &response_type($symb,\$res_error);
 3181:     if ($res_error) {
 3182:         $request->print(&navmap_errormsg());
 3183:         return;
 3184:     }
 3185: 
 3186:     my %keyhash = ();
 3187:     if ($numessay) {
 3188: 	# Keywords sorted in alphabatical order
 3189: 	my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
 3190: 	$env{'form.keywords'}           =~ s/,\s{0,}|\s+/ /g;
 3191: 	$env{'form.keywords'}           =~ s/^\s+|\s+$//;
 3192: 	my (@keywords) = sort(split(/\s+/,$env{'form.keywords'}));
 3193: 	$env{'form.keywords'} = join(' ',@keywords);
 3194: 	$keyhash{$symb.'_keywords'}     = $env{'form.keywords'};
 3195: 	$keyhash{$symb.'_subject'}      = $env{'form.msgsub'};
 3196: 	$keyhash{$loginuser.'_kwclr'}   = $env{'form.kwclr'};
 3197: 	$keyhash{$loginuser.'_kwsize'}  = $env{'form.kwsize'};
 3198: 	$keyhash{$loginuser.'_kwstyle'} = $env{'form.kwstyle'};
 3199:     }
 3200: 
 3201:     if ($env{'form.compmsg'}) {
 3202: 	# message center - Order of message gets changed. Blank line is eliminated.
 3203: 	# New messages are saved in env for the next student.
 3204: 	# All messages are saved in nohist_handgrade.db
 3205: 	my ($ctr,$idx) = (1,1);
 3206: 	while ($ctr <= $env{'form.savemsgN'}) {
 3207: 	    if ($env{'form.savemsg'.$ctr} ne '') {
 3208: 		$keyhash{$symb.'_savemsg'.$idx} = $env{'form.savemsg'.$ctr};
 3209: 		$idx++;
 3210: 	    }
 3211: 	    $ctr++;
 3212: 	}
 3213: 	$ctr = 0;
 3214: 	while ($ctr < $ngrade) {
 3215: 	    if ($env{'form.newmsg'.$ctr} ne '') {
 3216: 		$keyhash{$symb.'_savemsg'.$idx} = $env{'form.newmsg'.$ctr};
 3217: 		$env{'form.savemsg'.$idx} = $env{'form.newmsg'.$ctr};
 3218: 		$idx++;
 3219: 	    }
 3220: 	    $ctr++;
 3221: 	}
 3222: 	$env{'form.savemsgN'} = --$idx;
 3223: 	$keyhash{$symb.'_savemsgN'} = $env{'form.savemsgN'};
 3224:     }
 3225:     if (($numessay) || ($env{'form.compmsg'})) {
 3226: 	my $putresult = &Apache::lonnet::put
 3227: 	    ('nohist_handgrade',\%keyhash,$cdom,$cnum);
 3228:     }
 3229: 
 3230:     # Called by Save & Refresh from Highlight Attribute Window
 3231:     my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
 3232:     if ($env{'form.refresh'} eq 'on') {
 3233: 	my ($ctr,$total) = (0,0);
 3234: 	while ($ctr < $ngrade) {
 3235: 	    $total++ if  $env{'form.unamedom'.$ctr} ne '';
 3236: 	    $ctr++;
 3237: 	}
 3238: 	$env{'form.NTSTU'}=$ngrade;
 3239: 	$ctr = 0;
 3240: 	while ($ctr < $total) {
 3241: 	    my $processUser = $env{'form.unamedom'.$ctr};
 3242: 	    ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
 3243: 	    $env{'form.fullname'} = $$fullname{$processUser};
 3244: 	    &submission($request,$ctr,$total-1,$symb);
 3245: 	    $ctr++;
 3246: 	}
 3247: 	return '';
 3248:     }
 3249: 
 3250:     # Get the next/previous one or group of students
 3251:     my $firststu = $env{'form.unamedom0'};
 3252:     my $laststu = $env{'form.unamedom'.($ngrade-1)};
 3253:     my $ctr = 2;
 3254:     while ($laststu eq '') {
 3255: 	$laststu  = $env{'form.unamedom'.($ngrade-$ctr)};
 3256: 	$ctr++;
 3257: 	$laststu = $firststu if ($ctr > $ngrade);
 3258:     }
 3259: 
 3260:     my (@parsedlist,@nextlist);
 3261:     my ($nextflg) = 0;
 3262:     foreach my $item (sort 
 3263: 	     {
 3264: 		 if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
 3265: 		     return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
 3266: 		 }
 3267: 		 return $a cmp $b;
 3268: 	     } (keys(%$fullname))) {
 3269: 	if ($nextflg == 1 && $button =~ /Next$/) {
 3270: 	    push(@parsedlist,$item);
 3271: 	}
 3272: 	$nextflg = 1 if ($item eq $laststu);
 3273: 	if ($button eq 'Previous') {
 3274: 	    last if ($item eq $firststu);
 3275: 	    push(@parsedlist,$item);
 3276: 	}
 3277:     }
 3278:     $ctr = 0;
 3279:     @parsedlist = reverse @parsedlist if ($button eq 'Previous');
 3280:     foreach my $student (@parsedlist) {
 3281: 	my $submitonly=$env{'form.submitonly'};
 3282: 	my ($uname,$udom) = split(/:/,$student);
 3283: 	
 3284: 	if ($submitonly eq 'queued') {
 3285: 	    my %queue_status = 
 3286: 		&Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
 3287: 							$udom,$uname);
 3288: 	    next if (!defined($queue_status{'gradingqueue'}));
 3289: 	}
 3290: 
 3291: 	if ($submitonly =~ /^(yes|graded|incorrect)$/) {
 3292: #	    my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
 3293: 	    my %status=&student_gradeStatus($symb,$udom,$uname,$partlist);
 3294: 	    my $submitted = 0;
 3295: 	    my $ungraded = 0;
 3296: 	    my $incorrect = 0;
 3297: 	    foreach my $item (keys(%status)) {
 3298: 		$submitted = 1 if ($status{$item} ne 'nothing');
 3299: 		$ungraded = 1 if ($status{$item} =~ /^ungraded/);
 3300: 		$incorrect = 1 if ($status{$item} =~ /^incorrect/);
 3301: 		my ($foo,$partid,$foo1) = split(/\./,$item);
 3302: 		if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
 3303: 		    $submitted = 0;
 3304: 		}
 3305: 	    }
 3306: 	    next if (!$submitted && ($submitonly eq 'yes' ||
 3307: 				     $submitonly eq 'incorrect' ||
 3308: 				     $submitonly eq 'graded'));
 3309: 	    next if (!$ungraded && ($submitonly eq 'graded'));
 3310: 	    next if (!$incorrect && $submitonly eq 'incorrect');
 3311: 	}
 3312: 	push(@nextlist,$student) if ($ctr < $ntstu);
 3313: 	last if ($ctr == $ntstu);
 3314: 	$ctr++;
 3315:     }
 3316: 
 3317:     $ctr = 0;
 3318:     my $total = scalar(@nextlist)-1;
 3319: 
 3320:     foreach (sort(@nextlist)) {
 3321: 	my ($uname,$udom,$submitter) = split(/:/);
 3322: 	$env{'form.student'}  = $uname;
 3323: 	$env{'form.userdom'}  = $udom;
 3324: 	$env{'form.fullname'} = $$fullname{$_};
 3325: 	&submission($request,$ctr,$total,$symb);
 3326: 	$ctr++;
 3327:     }
 3328:     if ($total < 0) {
 3329:         my $the_end.='<p>'.&mt('[_1]Message:[_2] No more students for this section or class.','<b>','</b>').'</p>'."\n";
 3330: 	$request->print($the_end);
 3331:     }
 3332:     return '';
 3333: }
 3334: 
 3335: #---- Save the score and award for each student, if changed
 3336: sub saveHandGrade {
 3337:     my ($request,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_;
 3338:     my @version_parts;
 3339:     my $usec = &Apache::lonnet::getsection($domain,$stuname,
 3340: 					   $env{'request.course.id'});
 3341:     if (!&canmodify($usec)) { return('not_allowed'); }
 3342:     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname);
 3343:     my @parts_graded;
 3344:     my %newrecord  = ();
 3345:     my ($pts,$wgt,$totchg) = ('','',0);
 3346:     my %aggregate = ();
 3347:     my $aggregateflag = 0;
 3348:     if ($env{'form.HIDE'.$newflg}) {
 3349:         my ($version,$parts) = split(/:/,$env{'form.HIDE'.$newflg},2);
 3350:         my $numchgs = &makehidden($version,$parts,\%record,$symb,$domain,$stuname,1);
 3351:         $totchg += $numchgs;
 3352:     }
 3353:     my @parts = split(/:/,$env{'form.partlist'.$newflg});
 3354:     foreach my $new_part (@parts) {
 3355: 	#collaborator ($submi may vary for different parts
 3356: 	if ($submitter && $new_part ne $part) { next; }
 3357: 	my $dropMenu = $env{'form.GD_SEL'.$newflg.'_'.$new_part};
 3358: 	if ($dropMenu eq 'excused') {
 3359: 	    if ($record{'resource.'.$new_part.'.solved'} ne 'excused') {
 3360: 		$newrecord{'resource.'.$new_part.'.solved'} = 'excused';
 3361: 		if (exists($record{'resource.'.$new_part.'.awarded'})) {
 3362: 		    $newrecord{'resource.'.$new_part.'.awarded'} = '';
 3363: 		}
 3364: 	        $newrecord{'resource.'.$new_part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
 3365: 	    }
 3366: 	} elsif ($dropMenu eq 'reset status'
 3367: 		 && exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts
 3368: 	    foreach my $key (keys(%record)) {
 3369: 		if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; }
 3370: 	    }
 3371: 	    $newrecord{'resource.'.$new_part.'.regrader'}=
 3372: 		"$env{'user.name'}:$env{'user.domain'}";
 3373:             my $totaltries = $record{'resource.'.$part.'.tries'};
 3374: 
 3375:             my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
 3376: 					       [$new_part]);
 3377:             my $aggtries =$totaltries;
 3378:             if ($last_resets{$new_part}) {
 3379:                 $aggtries = &get_num_tries(\%record,$last_resets{$new_part},
 3380: 					   $new_part);
 3381:             }
 3382: 
 3383:             my $solvedstatus = $record{'resource.'.$new_part.'.solved'};
 3384:             if ($aggtries > 0) {
 3385:                 &decrement_aggs($symb,$new_part,\%aggregate,$aggtries,$totaltries,$solvedstatus);
 3386:                 $aggregateflag = 1;
 3387:             }
 3388: 	} elsif ($dropMenu eq '') {
 3389: 	    $pts = ($env{'form.GD_BOX'.$newflg.'_'.$new_part} ne '' ? 
 3390: 		    $env{'form.GD_BOX'.$newflg.'_'.$new_part} : 
 3391: 		    $env{'form.RADVAL'.$newflg.'_'.$new_part});
 3392: 	    if ($pts eq '' && $env{'form.GD_SEL'.$newflg.'_'.$new_part} eq '') {
 3393: 		next;
 3394: 	    }
 3395: 	    $wgt = $env{'form.WGT'.$newflg.'_'.$new_part} eq '' ? 1 : 
 3396: 		$env{'form.WGT'.$newflg.'_'.$new_part};
 3397: 	    my $partial= $pts/$wgt;
 3398: 	    if ($partial eq $record{'resource.'.$new_part.'.awarded'}) {
 3399: 		#do not update score for part if not changed.
 3400:                 &handback_files($request,$symb,$stuname,$domain,$newflg,$new_part,\%newrecord);
 3401: 		next;
 3402: 	    } else {
 3403: 	        push(@parts_graded,$new_part);
 3404: 	    }
 3405: 	    if ($record{'resource.'.$new_part.'.awarded'} ne $partial) {
 3406: 		$newrecord{'resource.'.$new_part.'.awarded'}  = $partial;
 3407: 	    }
 3408: 	    my $reckey = 'resource.'.$new_part.'.solved';
 3409: 	    if ($partial == 0) {
 3410: 		if ($record{$reckey} ne 'incorrect_by_override') {
 3411: 		    $newrecord{$reckey} = 'incorrect_by_override';
 3412: 		}
 3413: 	    } else {
 3414: 		if ($record{$reckey} ne 'correct_by_override') {
 3415: 		    $newrecord{$reckey} = 'correct_by_override';
 3416: 		}
 3417: 	    }	    
 3418: 	    if ($submitter && 
 3419: 		($record{'resource.'.$new_part.'.submitted_by'} ne $submitter)) {
 3420: 		$newrecord{'resource.'.$new_part.'.submitted_by'} = $submitter;
 3421: 	    }
 3422: 	    $newrecord{'resource.'.$new_part.'.regrader'}=
 3423: 		"$env{'user.name'}:$env{'user.domain'}";
 3424: 	}
 3425: 	# unless problem has been graded, set flag to version the submitted files
 3426: 	unless ($record{'resource.'.$new_part.'.solved'} =~ /^correct_/  || 
 3427: 	        $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override' ||
 3428: 	        $dropMenu eq 'reset status')
 3429: 	   {
 3430: 	    push(@version_parts,$new_part);
 3431: 	}
 3432:     }
 3433:     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
 3434:     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
 3435: 
 3436:     if (%newrecord) {
 3437:         if (@version_parts) {
 3438:             my @changed_keys = &version_portfiles(\%record, \@parts_graded, 
 3439:                                 $env{'request.course.id'}, $symb, $domain, $stuname, \@version_parts);
 3440: 	    @newrecord{@changed_keys} = @record{@changed_keys};
 3441: 	    foreach my $new_part (@version_parts) {
 3442: 		&handback_files($request,$symb,$stuname,$domain,$newflg,
 3443: 				$new_part,\%newrecord);
 3444: 	    }
 3445:         }
 3446: 	&Apache::lonnet::cstore(\%newrecord,$symb,
 3447: 				$env{'request.course.id'},$domain,$stuname);
 3448: 	&check_and_remove_from_queue(\@parts,\%record,\%newrecord,$symb,
 3449: 				     $cdom,$cnum,$domain,$stuname);
 3450:     }
 3451:     if ($aggregateflag) {
 3452:         &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
 3453: 			      $cdom,$cnum);
 3454:     }
 3455:     return ('',$pts,$wgt,$totchg);
 3456: }
 3457: 
 3458: sub makehidden {
 3459:     my ($version,$parts,$record,$symb,$domain,$stuname,$tolog) = @_;
 3460:     return unless (ref($record) eq 'HASH');
 3461:     my %modified;
 3462:     my $numchanged = 0;
 3463:     if (exists($record->{$version.':keys'})) {
 3464:         my $partsregexp = $parts;
 3465:         $partsregexp =~ s/,/|/g;
 3466:         foreach my $key (split(/\:/,$record->{$version.':keys'})) {
 3467:             if ($key =~ /^resource\.(?:$partsregexp)\.([^\.]+)$/) {
 3468:                  my $item = $1;
 3469:                  unless (($item eq 'solved') || ($item =~ /^award(|msg|ed)$/)) {
 3470:                      $modified{$key} = $record->{$version.':'.$key};
 3471:                  }
 3472:             } elsif ($key =~ m{^(resource\.(?:$partsregexp)\.[^\.]+\.)(.+)$}) {
 3473:                 $modified{$1.'hidden'.$2} = $record->{$version.':'.$key};
 3474:             } elsif ($key =~ /^(ip|timestamp|host)$/) {
 3475:                 $modified{$key} = $record->{$version.':'.$key};
 3476:             }
 3477:         }
 3478:         if (keys(%modified)) {
 3479:             if (&Apache::lonnet::putstore($env{'request.course.id'},$symb,$version,\%modified,
 3480:                                           $domain,$stuname,$tolog) eq 'ok') {
 3481:                 $numchanged ++;
 3482:             }
 3483:         }
 3484:     }
 3485:     return $numchanged;
 3486: }
 3487: 
 3488: sub check_and_remove_from_queue {
 3489:     my ($parts,$record,$newrecord,$symb,$cdom,$cnum,$domain,$stuname) = @_;
 3490:     my @ungraded_parts;
 3491:     foreach my $part (@{$parts}) {
 3492: 	if (    $record->{   'resource.'.$part.'.awarded'} eq ''
 3493: 	     && $record->{   'resource.'.$part.'.solved' } ne 'excused'
 3494: 	     && $newrecord->{'resource.'.$part.'.awarded'} eq ''
 3495: 	     && $newrecord->{'resource.'.$part.'.solved' } ne 'excused'
 3496: 		) {
 3497: 	    push(@ungraded_parts, $part);
 3498: 	}
 3499:     }
 3500:     if ( !@ungraded_parts ) {
 3501: 	&Apache::bridgetask::remove_from_queue('gradingqueue',$symb,$cdom,
 3502: 					       $cnum,$domain,$stuname);
 3503:     }
 3504: }
 3505: 
 3506: sub handback_files {
 3507:     my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;
 3508:     my $portfolio_root = '/userfiles/portfolio';
 3509:     my $res_error;
 3510:     my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
 3511:     if ($res_error) {
 3512:         $request->print('<br />'.&navmap_errormsg().'<br />');
 3513:         return;
 3514:     }
 3515:     my @handedback;
 3516:     my $file_msg;
 3517:     my @part_response_id = &flatten_responseType($responseType);
 3518:     foreach my $part_response_id (@part_response_id) {
 3519:     	my ($part_id,$resp_id) = @{ $part_response_id };
 3520: 	my $part_resp = join('_',@{ $part_response_id });
 3521:         if (($env{'form.'.$newflg.'_'.$part_resp.'_countreturndoc'} =~ /^\d+$/) & ($new_part eq $part_id)) {
 3522:             for (my $counter=1; $counter<=$env{'form.'.$newflg.'_'.$part_resp.'_countreturndoc'}; $counter++) {
 3523:                 # if multiple files are uploaded names will be 'returndoc2','returndoc3'
 3524: 		if ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$counter}) {
 3525:                     my $fname=$env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$counter.'.filename'};
 3526:                     my ($directory,$answer_file) = 
 3527:                         ($env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$counter} =~ /^(.*?)([^\/]*)$/);
 3528:                     my ($answer_name,$answer_ver,$answer_ext) =
 3529: 		        &file_name_version_ext($answer_file);
 3530: 		    my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/);
 3531:                     my $getpropath = 1;
 3532:                     my ($dir_list,$listerror) =
 3533:                         &Apache::lonnet::dirlist($portfolio_root.$portfolio_path,
 3534:                                                  $domain,$stuname,$getpropath);
 3535: 		    my $version = &get_next_version($answer_name,$answer_ext,$dir_list);
 3536:                     # fix filename
 3537:                     my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);
 3538:                     my $result=&Apache::lonnet::finishuserfileupload($stuname,$domain,
 3539:             	                                $newflg.'_'.$part_resp.'_returndoc'.$counter,
 3540:             	                                $save_file_name);
 3541:                     if ($result !~ m|^/uploaded/|) {
 3542:                         $request->print('<br /><span class="LC_error">'.
 3543:                             &mt('An error occurred ([_1]) while trying to upload [_2].',
 3544:                                 $result,$newflg.'_'.$part_resp.'_returndoc'.$counter).
 3545:                                         '</span>');
 3546:                     } else {
 3547:                         # mark the file as read only
 3548:                         push(@handedback,$save_file_name);
 3549: 			if (exists($$newrecord{"resource.$new_part.$resp_id.handback"})) {
 3550: 			    $$newrecord{"resource.$new_part.$resp_id.handback"}.=',';
 3551: 			}
 3552:                         $$newrecord{"resource.$new_part.$resp_id.handback"} .= $save_file_name;
 3553: 			$file_msg.='<span class="LC_filename"><a href="/uploaded/'."$domain/$stuname/".$save_file_name.'">'.$save_file_name."</a></span> <br />";
 3554: 
 3555:                     }
 3556:                     $request->print('<br />'.&mt('[_1] will be the uploaded filename [_2]','<span class="LC_info">'.$fname.'</span>','<span class="LC_filename">'.$env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$counter}.'</span>'));
 3557:                 }
 3558:             }
 3559:         }
 3560:     }
 3561:     if (@handedback > 0) {
 3562:         $request->print('<br />');
 3563:         my @what = ($symb,$env{'request.course.id'},'handback');
 3564:         &Apache::lonnet::mark_as_readonly($domain,$stuname,\@handedback,\@what);
 3565:         my $user_lh = &Apache::loncommon::user_lang($stuname,$domain,$env{'request.course.id'});
 3566:         my ($subject,$message);
 3567:         if (scalar(@handedback) == 1) {
 3568:             $subject = &mt_user($user_lh,'File Handed Back by Instructor');
 3569:             $message = &mt_user($user_lh,'A file has been returned that was originally submitted in response to: ');
 3570:         } else {
 3571:             $subject = &mt_user($user_lh,'Files Handed Back by Instructor');
 3572:             $message = &mt_user($user_lh,'Files have been returned that were originally submitted in response to: ');
 3573:         }
 3574:         $message .= "<p><strong>".&Apache::lonnet::gettitle($symb)." </strong></p>";
 3575:         $message .= &mt_user($user_lh,'The returned file(s) are named: [_1]',"<br />$file_msg <br />").
 3576:                     &mt_user($user_lh,'The file(s) can be found in your [_1]portfolio[_2].','<a href="/adm/portfolio">','</a>');
 3577:         my ($feedurl,$showsymb) =
 3578:             &get_feedurl_and_symb($symb,$domain,$stuname);
 3579:         my $restitle = &Apache::lonnet::gettitle($symb);
 3580:         $subject .= ' '.&mt_user($user_lh,'(File Returned)').' ['.$restitle.']';
 3581:         my $msgstatus =
 3582:              &Apache::lonmsg::user_normal_msg($stuname,$domain,$subject,
 3583:                  $message,undef,$feedurl,undef,undef,undef,$showsymb,
 3584:                  $restitle);
 3585:         if ($msgstatus) {
 3586:             $request->print(&mt('Notification message status: [_1]','<span class="LC_info">'.$msgstatus.'</span>').'<br />');
 3587:         }
 3588:     }
 3589:     return;
 3590: }
 3591: 
 3592: sub get_feedurl_and_symb {
 3593:     my ($symb,$uname,$udom) = @_;
 3594:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
 3595:     $url = &Apache::lonnet::clutter($url);
 3596:     my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl',
 3597: 					$symb,$udom,$uname);
 3598:     if ($encrypturl =~ /^yes$/i) {
 3599: 	&Apache::lonenc::encrypted(\$url,1);
 3600: 	&Apache::lonenc::encrypted(\$symb,1);
 3601:     }
 3602:     return ($url,$symb);
 3603: }
 3604: 
 3605: sub get_submitted_files {
 3606:     my ($udom,$uname,$partid,$respid,$record) = @_;
 3607:     my @files;
 3608:     if ($$record{"resource.$partid.$respid.portfiles"}) {
 3609:         my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio';
 3610:         foreach my $file (split(',',$$record{"resource.$partid.$respid.portfiles"})) {
 3611:     	    push(@files,$file_url.$file);
 3612:         }
 3613:     }
 3614:     if ($$record{"resource.$partid.$respid.uploadedurl"}) {
 3615:         push(@files,$$record{"resource.$partid.$respid.uploadedurl"});
 3616:     }
 3617:     return (\@files);
 3618: }
 3619: 
 3620: # ----------- Provides number of tries since last reset.
 3621: sub get_num_tries {
 3622:     my ($record,$last_reset,$part) = @_;
 3623:     my $timestamp = '';
 3624:     my $num_tries = 0;
 3625:     if ($$record{'version'}) {
 3626:         for (my $version=$$record{'version'};$version>=1;$version--) {
 3627:             if (exists($$record{$version.':resource.'.$part.'.solved'})) {
 3628:                 $timestamp = $$record{$version.':timestamp'};
 3629:                 if ($timestamp > $last_reset) {
 3630:                     $num_tries ++;
 3631:                 } else {
 3632:                     last;
 3633:                 }
 3634:             }
 3635:         }
 3636:     }
 3637:     return $num_tries;
 3638: }
 3639: 
 3640: # ----------- Determine decrements required in aggregate totals 
 3641: sub decrement_aggs {
 3642:     my ($symb,$part,$aggregate,$aggtries,$totaltries,$solvedstatus) = @_;
 3643:     my %decrement = (
 3644:                         attempts => 0,
 3645:                         users => 0,
 3646:                         correct => 0
 3647:                     );
 3648:     $decrement{'attempts'} = $aggtries;
 3649:     if ($solvedstatus =~ /^correct/) {
 3650:         $decrement{'correct'} = 1;
 3651:     }
 3652:     if ($aggtries == $totaltries) {
 3653:         $decrement{'users'} = 1;
 3654:     }
 3655:     foreach my $type (keys(%decrement)) {
 3656:         $$aggregate{$symb."\0".$part."\0".$type} = -$decrement{$type};
 3657:     }
 3658:     return;
 3659: }
 3660: 
 3661: # ----------- Determine timestamps for last reset of aggregate totals for parts  
 3662: sub get_last_resets {
 3663:     my ($symb,$courseid,$partids) =@_;
 3664:     my %last_resets;
 3665:     my $cdom = $env{'course.'.$courseid.'.domain'};
 3666:     my $cname = $env{'course.'.$courseid.'.num'};
 3667:     my @keys;
 3668:     foreach my $part (@{$partids}) {
 3669: 	push(@keys,"$symb\0$part\0resettime");
 3670:     }
 3671:     my %results=&Apache::lonnet::get('nohist_resourcetracker',\@keys,
 3672: 				     $cdom,$cname);
 3673:     foreach my $part (@{$partids}) {
 3674: 	$last_resets{$part}=$results{"$symb\0$part\0resettime"};
 3675:     }
 3676:     return %last_resets;
 3677: }
 3678: 
 3679: # ----------- Handles creating versions for portfolio files as answers
 3680: sub version_portfiles {
 3681:     my ($record, $parts_graded, $courseid, $symb, $domain, $stu_name, $v_flag) = @_;
 3682:     my $version_parts = join('|',@$v_flag);
 3683:     my @returned_keys;
 3684:     my $parts = join('|', @$parts_graded);
 3685:     my $portfolio_root = '/userfiles/portfolio';
 3686:     foreach my $key (keys(%$record)) {
 3687:         my $new_portfiles;
 3688:         if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
 3689:             my @versioned_portfiles;
 3690:             my @portfiles = split(/\s*,\s*/,$$record{$key});
 3691:             foreach my $file (@portfiles) {
 3692:                 &Apache::lonnet::unmark_as_readonly($domain,$stu_name,[$symb,$env{'request.course.id'}],$file);
 3693:                 my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/);
 3694: 		my ($answer_name,$answer_ver,$answer_ext) =
 3695: 		    &file_name_version_ext($answer_file);
 3696:                 my $getpropath = 1;
 3697:                 my ($dir_list,$listerror) =
 3698:                     &Apache::lonnet::dirlist($portfolio_root.$directory,$domain,
 3699:                                              $stu_name,$getpropath);
 3700:                 my $version = &get_next_version($answer_name,$answer_ext,$dir_list);
 3701:                 my $new_answer = &version_selected_portfile($domain, $stu_name, $directory, $answer_file, $version);
 3702:                 if ($new_answer ne 'problem getting file') {
 3703:                     push(@versioned_portfiles, $directory.$new_answer);
 3704:                     &Apache::lonnet::mark_as_readonly($domain,$stu_name,
 3705:                         [$directory.$new_answer],
 3706:                         [$symb,$env{'request.course.id'},'graded']);
 3707:                 }
 3708:             }
 3709:             $$record{$key} = join(',',@versioned_portfiles);
 3710:             push(@returned_keys,$key);
 3711:         }
 3712:     } 
 3713:     return (@returned_keys);   
 3714: }
 3715: 
 3716: sub get_next_version {
 3717:     my ($answer_name, $answer_ext, $dir_list) = @_;
 3718:     my $version;
 3719:     if (ref($dir_list) eq 'ARRAY') {
 3720:         foreach my $row (@{$dir_list}) {
 3721:             my ($file) = split(/\&/,$row,2);
 3722:             my ($file_name,$file_version,$file_ext) =
 3723: 	        &file_name_version_ext($file);
 3724:             if (($file_name eq $answer_name) && 
 3725: 	        ($file_ext eq $answer_ext)) {
 3726:                 # gets here if filename and extension match, 
 3727:                 # regardless of version
 3728:                 if ($file_version ne '') {
 3729:                     # a versioned file is found  so save it for later
 3730:                     if ($file_version > $version) {
 3731: 		        $version = $file_version;
 3732:                     }
 3733: 	        }
 3734:             }
 3735:         }
 3736:     }
 3737:     $version ++;
 3738:     return($version);
 3739: }
 3740: 
 3741: sub version_selected_portfile {
 3742:     my ($domain,$stu_name,$directory,$file_name,$version) = @_;
 3743:     my ($answer_name,$answer_ver,$answer_ext) =
 3744:         &file_name_version_ext($file_name);
 3745:     my $new_answer;
 3746:     $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name");
 3747:     if($env{'form.copy'} eq '-1') {
 3748:         $new_answer = 'problem getting file';
 3749:     } else {
 3750:         $new_answer = $answer_name.'.'.$version.'.'.$answer_ext;
 3751:         my $copy_result = &Apache::lonnet::finishuserfileupload(
 3752:                             $stu_name,$domain,'copy',
 3753: 		        '/portfolio'.$directory.$new_answer);
 3754:     }    
 3755:     return ($new_answer);
 3756: }
 3757: 
 3758: sub file_name_version_ext {
 3759:     my ($file)=@_;
 3760:     my @file_parts = split(/\./, $file);
 3761:     my ($name,$version,$ext);
 3762:     if (@file_parts > 1) {
 3763: 	$ext=pop(@file_parts);
 3764: 	if (@file_parts > 1 && $file_parts[-1] =~ /^\d+$/) {
 3765: 	    $version=pop(@file_parts);
 3766: 	}
 3767: 	$name=join('.',@file_parts);
 3768:     } else {
 3769: 	$name=join('.',@file_parts);
 3770:     }
 3771:     return($name,$version,$ext);
 3772: }
 3773: 
 3774: #--------------------------------------------------------------------------------------
 3775: #
 3776: #-------------------------- Next few routines handles grading by section or whole class
 3777: #
 3778: #--- Javascript to handle grading by section or whole class
 3779: sub viewgrades_js {
 3780:     my ($request) = shift;
 3781: 
 3782:     my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = ');
 3783:     &js_escape(\$alertmsg);
 3784:     $request->print(&Apache::lonhtmlcommon::scripttag(<<VIEWJAVASCRIPT));
 3785:    function writePoint(partid,weight,point) {
 3786: 	var radioButton = document.classgrade["RADVAL_"+partid];
 3787: 	var textbox = document.classgrade["TEXTVAL_"+partid];
 3788: 	if (point == "textval") {
 3789: 	    point = document.classgrade["TEXTVAL_"+partid].value;
 3790: 	    if (isNaN(point) || parseFloat(point) < 0) {
 3791: 		alert("$alertmsg"+parseFloat(point));
 3792: 		var resetbox = false;
 3793: 		for (var i=0; i<radioButton.length; i++) {
 3794: 		    if (radioButton[i].checked) {
 3795: 			textbox.value = i;
 3796: 			resetbox = true;
 3797: 		    }
 3798: 		}
 3799: 		if (!resetbox) {
 3800: 		    textbox.value = "";
 3801: 		}
 3802: 		return;
 3803: 	    }
 3804: 	    if (parseFloat(point) > parseFloat(weight)) {
 3805: 		var resp = confirm("You entered a value ("+parseFloat(point)+
 3806: 				   ") greater than the weight for the part. Accept?");
 3807: 		if (resp == false) {
 3808: 		    textbox.value = "";
 3809: 		    return;
 3810: 		}
 3811: 	    }
 3812: 	    for (var i=0; i<radioButton.length; i++) {
 3813: 		radioButton[i].checked=false;
 3814: 		if (parseFloat(point) == i) {
 3815: 		    radioButton[i].checked=true;
 3816: 		}
 3817: 	    }
 3818: 
 3819: 	} else {
 3820: 	    textbox.value = parseFloat(point);
 3821: 	}
 3822: 	for (i=0;i<document.classgrade.total.value;i++) {
 3823: 	    var user = document.classgrade["ctr"+i].value;
 3824: 	    user = user.replace(new RegExp(':', 'g'),"_");
 3825: 	    var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
 3826: 	    var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
 3827: 	    var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
 3828: 	    if (saveval != "correct") {
 3829: 		scorename.value = point;
 3830: 		if (selname[0].selected != true) {
 3831: 		    selname[0].selected = true;
 3832: 		}
 3833: 	    }
 3834: 	}
 3835: 	document.classgrade["SELVAL_"+partid][0].selected = true;
 3836:     }
 3837: 
 3838:     function writeRadText(partid,weight) {
 3839: 	var selval   = document.classgrade["SELVAL_"+partid];
 3840: 	var radioButton = document.classgrade["RADVAL_"+partid];
 3841:         var override = document.classgrade["FORCE_"+partid].checked;
 3842: 	var textbox = document.classgrade["TEXTVAL_"+partid];
 3843: 	if (selval[1].selected || selval[2].selected) {
 3844: 	    for (var i=0; i<radioButton.length; i++) {
 3845: 		radioButton[i].checked=false;
 3846: 
 3847: 	    }
 3848: 	    textbox.value = "";
 3849: 
 3850: 	    for (i=0;i<document.classgrade.total.value;i++) {
 3851: 		var user = document.classgrade["ctr"+i].value;
 3852: 		user = user.replace(new RegExp(':', 'g'),"_");
 3853: 		var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
 3854: 		var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
 3855: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
 3856: 		if ((saveval != "correct") || override) {
 3857: 		    scorename.value = "";
 3858: 		    if (selval[1].selected) {
 3859: 			selname[1].selected = true;
 3860: 		    } else {
 3861: 			selname[2].selected = true;
 3862: 			if (Number(document.classgrade["GD_"+user+"_"+partid+"_tries"].value)) 
 3863: 			{document.classgrade["GD_"+user+"_"+partid+"_tries"].value = '0';}
 3864: 		    }
 3865: 		}
 3866: 	    }
 3867: 	} else {
 3868: 	    for (i=0;i<document.classgrade.total.value;i++) {
 3869: 		var user = document.classgrade["ctr"+i].value;
 3870: 		user = user.replace(new RegExp(':', 'g'),"_");
 3871: 		var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
 3872: 		var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
 3873: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
 3874: 		if ((saveval != "correct") || override) {
 3875: 		    scorename.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
 3876: 		    selname[0].selected = true;
 3877: 		}
 3878: 	    }
 3879: 	}	    
 3880:     }
 3881: 
 3882:     function changeSelect(partid,user) {
 3883: 	var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
 3884: 	var textbox = document.classgrade["GD_"+user+'_'+partid+"_awarded"];
 3885: 	var point  = textbox.value;
 3886: 	var weight = document.classgrade["weight_"+partid].value;
 3887: 
 3888: 	if (isNaN(point) || parseFloat(point) < 0) {
 3889: 	    alert("$alertmsg"+parseFloat(point));
 3890: 	    textbox.value = "";
 3891: 	    return;
 3892: 	}
 3893: 	if (parseFloat(point) > parseFloat(weight)) {
 3894: 	    var resp = confirm("You entered a value ("+parseFloat(point)+
 3895: 			       ") greater than the weight of the part. Accept?");
 3896: 	    if (resp == false) {
 3897: 		textbox.value = "";
 3898: 		return;
 3899: 	    }
 3900: 	}
 3901: 	selval[0].selected = true;
 3902:     }
 3903: 
 3904:     function changeOneScore(partid,user) {
 3905: 	var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
 3906: 	if (selval[1].selected || selval[2].selected) {
 3907: 	    document.classgrade["GD_"+user+'_'+partid+"_awarded"].value = "";
 3908: 	    if (selval[2].selected) {
 3909: 		document.classgrade["GD_"+user+'_'+partid+"_tries"].value = "0";
 3910: 	    }
 3911:         }
 3912:     }
 3913: 
 3914:     function resetEntry(numpart) {
 3915: 	for (ctpart=0;ctpart<numpart;ctpart++) {
 3916: 	    var partid = document.classgrade["partid_"+ctpart].value;
 3917: 	    var radioButton = document.classgrade["RADVAL_"+partid];
 3918: 	    var textbox = document.classgrade["TEXTVAL_"+partid];
 3919: 	    var selval  = document.classgrade["SELVAL_"+partid];
 3920: 	    for (var i=0; i<radioButton.length; i++) {
 3921: 		radioButton[i].checked=false;
 3922: 
 3923: 	    }
 3924: 	    textbox.value = "";
 3925: 	    selval[0].selected = true;
 3926: 
 3927: 	    for (i=0;i<document.classgrade.total.value;i++) {
 3928: 		var user = document.classgrade["ctr"+i].value;
 3929: 		user = user.replace(new RegExp(':', 'g'),"_");
 3930: 		var resetscore = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
 3931: 		resetscore.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
 3932: 		var resettries = document.classgrade["GD_"+user+"_"+partid+"_tries"];
 3933: 		resettries.value = document.classgrade["GD_"+user+"_"+partid+"_tries_s"].value;
 3934: 		var saveselval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
 3935: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
 3936: 		if (saveselval == "excused") {
 3937: 		    if (selname[1].selected == false) { selname[1].selected = true;}
 3938: 		} else {
 3939: 		    if (selname[0].selected == false) {selname[0].selected = true};
 3940: 		}
 3941: 	    }
 3942: 	}
 3943:     }
 3944: 
 3945: VIEWJAVASCRIPT
 3946: }
 3947: 
 3948: #--- show scores for a section or whole class w/ option to change/update a score
 3949: sub viewgrades {
 3950:     my ($request,$symb) = @_;
 3951:     &viewgrades_js($request);
 3952: 
 3953:     #need to make sure we have the correct data for later EXT calls, 
 3954:     #thus invalidate the cache
 3955:     &Apache::lonnet::devalidatecourseresdata(
 3956:                  $env{'course.'.$env{'request.course.id'}.'.num'},
 3957:                  $env{'course.'.$env{'request.course.id'}.'.domain'});
 3958:     &Apache::lonnet::clear_EXT_cache_status();
 3959: 
 3960:     my $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>';
 3961: 
 3962:     #view individual student submission form - called using Javascript viewOneStudent
 3963:     $result.=&jscriptNform($symb);
 3964: 
 3965:     #beginning of class grading form
 3966:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
 3967:     $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n".
 3968: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 3969: 	'<input type="hidden" name="command" value="editgrades" />'."\n".
 3970: 	&build_section_inputs().
 3971: 	'<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n".
 3972: 
 3973:     #retrieve selected groups
 3974:     my (@groups,$group_display);
 3975:     @groups = &Apache::loncommon::get_env_multiple('form.group');
 3976:     if (grep(/^all$/,@groups)) {
 3977:         @groups = ('all');
 3978:     } elsif (grep(/^none$/,@groups)) {
 3979:         @groups = ('none');
 3980:     } elsif (@groups > 0) {
 3981:         $group_display = join(', ',@groups);
 3982:     }
 3983: 
 3984:     my ($common_header,$specific_header,@sections,$section_display);
 3985:     @sections = &Apache::loncommon::get_env_multiple('form.section');
 3986:     if (grep(/^all$/,@sections)) {
 3987:         @sections = ('all');
 3988:         if ($group_display) {
 3989:             $common_header = &mt('Assign Common Grade to Students in Group(s) [_1]',$group_display);
 3990:             $specific_header = &mt('Assign Grade to Specific Students in Group(s) [_1]',$group_display);
 3991:         } elsif (grep(/^none$/,@groups)) {
 3992:             $common_header = &mt('Assign Common Grade to Students not assigned to any groups');
 3993:             $specific_header = &mt('Assign Grade to Specific Students not assigned to any groups');
 3994:         } else {
 3995:             $common_header = &mt('Assign Common Grade to Class');
 3996:             $specific_header = &mt('Assign Grade to Specific Students in Class');
 3997:         }
 3998:     } elsif (grep(/^none$/,@sections)) {
 3999:         @sections = ('none');
 4000:         if ($group_display) {
 4001:             $common_header = &mt('Assign Common Grade to Students in no Section and in Group(s) [_1]',$group_display);
 4002:             $specific_header = &mt('Assign Grade to Specific Students in no Section and in Group(s)',$group_display);
 4003:         } elsif (grep(/^none$/,@groups)) {
 4004:             $common_header = &mt('Assign Common Grade to Students in no Section and in no Group');
 4005:             $specific_header = &mt('Assign Grade to Specific Students in no Section and in no Group');
 4006:         } else {
 4007:             $common_header = &mt('Assign Common Grade to Students in no Section');
 4008:             $specific_header = &mt('Assign Grade to Specific Students in no Section');
 4009:         }
 4010:     } else {
 4011:         $section_display = join (", ",@sections);
 4012:         if ($group_display) {
 4013:             $common_header = &mt('Assign Common Grade to Students in Section(s) [_1], and in Group(s) [_2]',
 4014:                                  $section_display,$group_display);
 4015:             $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1], and in Group(s) [_2]',
 4016:                                    $section_display,$group_display);
 4017:         } elsif (grep(/^none$/,@groups)) {
 4018:             $common_header = &mt('Assign Common Grade to Students in Section(s) [_1] and no Group',$section_display);
 4019:             $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1] and no Group',$section_display);
 4020:         } else {
 4021:             $common_header = &mt('Assign Common Grade to Students in Section(s) [_1]',$section_display);
 4022:             $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1]',$section_display);
 4023:         }
 4024:     }
 4025:     my %submit_types = &substatus_options();
 4026:     my $submission_status = $submit_types{$env{'form.submitonly'}};
 4027: 
 4028:     if ($env{'form.submitonly'} eq 'all') {
 4029:         $result.= '<h3>'.$common_header.'</h3>';
 4030:     } else {
 4031:         $result.= '<h3>'.$common_header.'&nbsp;'.&mt('(submission status: "[_1]")',$submission_status).'</h3>'; 
 4032:     }
 4033:     $result .= &Apache::loncommon::start_data_table();
 4034:     #radio buttons/text box for assigning points for a section or class.
 4035:     #handles different parts of a problem
 4036:     my $res_error;
 4037:     my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
 4038:     if ($res_error) {
 4039:         return &navmap_errormsg();
 4040:     }
 4041:     my %weight = ();
 4042:     my $ctsparts = 0;
 4043:     my %seen = ();
 4044:     my @part_response_id = &flatten_responseType($responseType);
 4045:     foreach my $part_response_id (@part_response_id) {
 4046:     	my ($partid,$respid) = @{ $part_response_id };
 4047: 	my $part_resp = join('_',@{ $part_response_id });
 4048: 	next if $seen{$partid};
 4049: 	$seen{$partid}++;
 4050: 	my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);
 4051: 	$weight{$partid} = $wgt eq '' ? '1' : $wgt;
 4052: 
 4053: 	my $display_part=&get_display_part($partid,$symb);
 4054: 	my $radio.='<table border="0"><tr>';  
 4055: 	my $ctr = 0;
 4056: 	while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across
 4057: 	    $radio.= '<td><label><input type="radio" name="RADVAL_'.$partid.'" '.
 4058: 		'onclick="javascript:writePoint(\''.$partid.'\','.$weight{$partid}.
 4059: 		','.$ctr.')" />'.$ctr."</label></td>\n";
 4060: 	    $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
 4061: 	    $ctr++;
 4062: 	}
 4063: 	$radio.='</tr></table>';
 4064: 	my $line = '<input type="text" name="TEXTVAL_'.
 4065: 	    $partid.'" size="4" '.'onchange="javascript:writePoint(\''.
 4066: 		$partid.'\','.$weight{$partid}.',\'textval\')" /> /'.
 4067: 	    $weight{$partid}.' '.&mt('(problem weight)').'</td>'."\n";
 4068: 	$line.= '<td><b>'.&mt('Grade Status').':</b>'.
 4069:                 '<select name="SELVAL_'.$partid.'" '.
 4070: 	        'onchange="javascript:writeRadText(\''.$partid.'\','.
 4071: 		$weight{$partid}.')"> '.
 4072: 	    '<option selected="selected"> </option>'.
 4073: 	    '<option value="excused">'.&mt('excused').'</option>'.
 4074: 	    '<option value="reset status">'.&mt('reset status').'</option>'.
 4075: 	    '</select></td>'.
 4076:             '<td><label><input type="checkbox" name="FORCE_'.$partid.'" />'.&mt('Override "Correct"').'</label>';
 4077: 	$line.='<input type="hidden" name="partid_'.
 4078: 	    $ctsparts.'" value="'.$partid.'" />'."\n";
 4079: 	$line.='<input type="hidden" name="weight_'.
 4080: 	    $partid.'" value="'.$weight{$partid}.'" />'."\n";
 4081: 
 4082: 	$result.=
 4083: 	    &Apache::loncommon::start_data_table_row()."\n".
 4084: 	    '<td><b>'.&mt('Part:').'</b></td><td>'.$display_part.'</td><td><b>'.&mt('Points:').'</b></td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>'.
 4085: 	    &Apache::loncommon::end_data_table_row()."\n";
 4086: 	$ctsparts++;
 4087:     }
 4088:     $result.=&Apache::loncommon::end_data_table()."\n".
 4089: 	'<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
 4090:     $result.='<input type="button" value="'.&mt('Revert to Default').'" '.
 4091: 	'onclick="javascript:resetEntry('.$ctsparts.');" />';
 4092: 
 4093:     #table listing all the students in a section/class
 4094:     #header of table
 4095:     if ($env{'form.submitonly'} eq 'all') { 
 4096:         $result.= '<h3>'.$specific_header.'</h3>';
 4097:     } else {
 4098:         $result.= '<h3>'.$specific_header.'&nbsp;'.&mt('(submission status: "[_1]")',$submission_status).'</h3>';
 4099:     }
 4100:     $result.= &Apache::loncommon::start_data_table().
 4101: 	      &Apache::loncommon::start_data_table_header_row().
 4102: 	      '<th>'.&mt('No.').'</th>'.
 4103: 	      '<th>'.&nameUserString('header')."</th>\n";
 4104:     my $partserror;
 4105:     my (@parts) = sort(&getpartlist($symb,\$partserror));
 4106:     if ($partserror) {
 4107:         return &navmap_errormsg();
 4108:     }
 4109:     my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
 4110:     my @partids = ();
 4111:     foreach my $part (@parts) {
 4112: 	my $display=&Apache::lonnet::metadata($url,$part.'.display');
 4113:         my $narrowtext = &mt('Tries');
 4114: 	$display =~ s|^Number of Attempts|$narrowtext <br />|; # makes the column narrower
 4115: 	if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }
 4116: 	my ($partid) = &split_part_type($part);
 4117:         push(@partids,$partid);
 4118: #
 4119: # FIXME: Looks like $display looks at English text
 4120: #
 4121: 	my $display_part=&get_display_part($partid,$symb);
 4122: 	if ($display =~ /^Partial Credit Factor/) {
 4123: 	    $result.='<th>'.
 4124:                 &mt('Score Part: [_1][_2](weight = [_3])',
 4125:                     $display_part,'<br />',$weight{$partid}).'</th>'."\n";
 4126: 	    next;
 4127: 	    
 4128: 	} else {
 4129: 	    if ($display =~ /Problem Status/) {
 4130: 		my $grade_status_mt = &mt('Grade Status');
 4131: 		$display =~ s{Problem Status}{$grade_status_mt<br />};
 4132: 	    }
 4133: 	    my $part_mt = &mt('Part:');
 4134: 	    $display =~s{\[Part: \Q$partid\E\]}{$part_mt $display_part};
 4135: 	}
 4136: 
 4137: 	$result.='<th>'.$display.'</th>'."\n";
 4138:     }
 4139:     $result.=&Apache::loncommon::end_data_table_header_row();
 4140: 
 4141:     my %last_resets = 
 4142: 	&get_last_resets($symb,$env{'request.course.id'},\@partids);
 4143: 
 4144:     #get info for each student
 4145:     #list all the students - with points and grade status
 4146:     my (undef,undef,$fullname) = &getclasslist(\@sections,'1',\@groups);
 4147:     my $ctr = 0;
 4148:     foreach (sort 
 4149: 	     {
 4150: 		 if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
 4151: 		     return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
 4152: 		 }
 4153: 		 return $a cmp $b;
 4154: 	     } (keys(%$fullname))) {
 4155: 	$result.=&viewstudentgrade($symb,$env{'request.course.id'},
 4156: 				   $_,$$fullname{$_},\@parts,\%weight,\$ctr,\%last_resets);
 4157:     }
 4158:     $result.=&Apache::loncommon::end_data_table();
 4159:     $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
 4160:     $result.='<input type="button" value="'.&mt('Save').'" '.
 4161: 	'onclick="javascript:submit();" target="_self" /></form>'."\n";
 4162:     if ($ctr == 0) {
 4163:         my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status'));
 4164:         $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>'.
 4165:                 '<span class="LC_warning">';
 4166:         if ($env{'form.submitonly'} eq 'all') {
 4167:             if (grep(/^all$/,@sections)) {
 4168:                 if (grep(/^all$/,@groups)) {
 4169:                     $result .= &mt('There are no students with enrollment status [_1] to modify or grade.',
 4170:                                    $stu_status);
 4171:                 } elsif (grep(/^none$/,@groups)) {
 4172:                     $result .= &mt('There are no students with no group assigned and with enrollment status [_1] to modify or grade.',
 4173:                                    $stu_status);
 4174:                 } else {
 4175:                     $result .= &mt('There are no students in group(s) [_1] with enrollment status [_2] to modify or grade.',
 4176:                                    $group_display,$stu_status);
 4177:                 }
 4178:             } elsif (grep(/^none$/,@sections)) {
 4179:                 if (grep(/^all$/,@groups)) {
 4180:                     $result .= &mt('There are no students in no section with enrollment status [_1] to modify or grade.',
 4181:                                    $stu_status);
 4182:                 } elsif (grep(/^none$/,@groups)) {
 4183:                     $result .= &mt('There are no students in no section and no group with enrollment status [_1] to modify or grade.',
 4184:                                    $stu_status);
 4185:                 } else {
 4186:                     $result .= &mt('There are no students in no section in group(s) [_1] with enrollment status [_2] to modify or grade.',
 4187:                                    $group_display,$stu_status);
 4188:                 }
 4189:             } else {
 4190:                 if (grep(/^all$/,@groups)) {
 4191:                     $result .= &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade.',
 4192:                                    $section_display,$stu_status);
 4193:                 } elsif (grep(/^none$/,@groups)) {
 4194:                     $result .= &mt('There are no students in section(s) [_1] and no group with enrollment status [_2] to modify or grade.',
 4195:                                    $section_display,$stu_status);
 4196:                 } else {
 4197:                     $result .= &mt('There are no students in section(s) [_1] and group(s) [_2] with enrollment status [_3] to modify or grade.',
 4198:                                    $section_display,$group_display,$stu_status);
 4199:                 }
 4200:             }
 4201:         } else {
 4202:             if (grep(/^all$/,@sections)) {
 4203:                 if (grep(/^all$/,@groups)) {
 4204:                     $result .= &mt('There are no students with enrollment status [_1] and submission status "[_2]" to modify or grade.',
 4205:                                    $stu_status,$submission_status);
 4206:                 } elsif (grep(/^none$/,@groups)) {
 4207:                     $result .= &mt('There are no students with no group assigned with enrollment status [_1] and submission status "[_2]" to modify or grade.',
 4208:                                    $stu_status,$submission_status);
 4209:                 } else {
 4210:                     $result .= &mt('There are no students in group(s) [_1] with enrollment status [_2] and submission status "[_3]" to modify or grade.',
 4211:                                    $group_display,$stu_status,$submission_status);
 4212:                 }
 4213:             } elsif (grep(/^none$/,@sections)) {
 4214:                 if (grep(/^all$/,@groups)) {
 4215:                     $result .= &mt('There are no students in no section with enrollment status [_1] and submission status "[_2]" to modify or grade.',
 4216:                                    $stu_status,$submission_status);
 4217:                 } elsif (grep(/^none$/,@groups)) {
 4218:                     $result .= &mt('There are no students in no section and no group with enrollment status [_1] and submission status "[_2]" to modify or grade.',
 4219:                                    $stu_status,$submission_status);
 4220:                 } else {
 4221:                     $result .= &mt('There are no students in no section in group(s) [_1] with enrollment status [_2] and submission status "[_3]" to modify or grade.',
 4222:                                    $group_display,$stu_status,$submission_status);
 4223:                 }
 4224:             } else {
 4225:                 if (grep(/^all$/,@groups)) {
 4226:                     $result .= &mt('There are no students in section(s) [_1] with enrollment status [_2] and submission status "[_3]" to modify or grade.',
 4227:                                    $section_display,$stu_status,$submission_status);
 4228:                 } elsif (grep(/^none$/,@groups)) {
 4229:                     $result .= &mt('There are no students in section(s) [_1] and no group with enrollment status [_2] and submission status "[_3]" to modify or grade.',
 4230:                                    $section_display,$stu_status,$submission_status);
 4231:                 } else {
 4232:                     $result .= &mt('There are no students in section(s) [_1] and group(s) [_2] with enrollment status [_3] and submission status "[_4]" to modify or grade.',
 4233:                                    $section_display,$group_display,$stu_status,$submission_status);
 4234:                 }
 4235:             }
 4236: 	}
 4237: 	$result .= '</span><br />';
 4238:     }
 4239:     return $result;
 4240: }
 4241: 
 4242: #--- call by previous routine to display each student who satisfies submission filter.
 4243: sub viewstudentgrade {
 4244:     my ($symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets) = @_;
 4245:     my ($uname,$udom) = split(/:/,$student);
 4246:     my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
 4247:     my $submitonly = $env{'form.submitonly'};
 4248:     unless (($submitonly eq 'all') || ($submitonly eq 'queued')) {
 4249:         my %partstatus = ();
 4250:         if (ref($parts) eq 'ARRAY') {
 4251:             foreach my $apart (@{$parts}) {
 4252:                 my ($part,$type) = &split_part_type($apart);
 4253:                 my ($status,undef) = split(/_/,$record{"resource.$part.solved"},2);
 4254:                 $status = 'nothing' if ($status eq '');
 4255:                 $partstatus{$part}      = $status;
 4256:                 my $subkey = "resource.$part.submitted_by";
 4257:                 $partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
 4258:             }
 4259:             my $submitted = 0;
 4260:             my $graded = 0;
 4261:             my $incorrect = 0;
 4262:             foreach my $key (keys(%partstatus)) {
 4263:                 $submitted = 1 if ($partstatus{$key} ne 'nothing');
 4264:                 $graded = 1 if ($partstatus{$key} =~ /^ungraded/);
 4265:                 $incorrect = 1 if ($partstatus{$key} =~ /^incorrect/);
 4266: 
 4267:                 my $partid = (split(/\./,$key))[1];
 4268:                 if ($partstatus{'resource.'.$partid.'.'.$key.'.submitted_by'} ne '') {
 4269:                     $submitted = 0;
 4270:                 }
 4271:             }
 4272:             return if (!$submitted && ($submitonly eq 'yes' ||
 4273:                                        $submitonly eq 'incorrect' ||
 4274:                                        $submitonly eq 'graded'));
 4275:             return if (!$graded && ($submitonly eq 'graded'));
 4276:             return if (!$incorrect && $submitonly eq 'incorrect');
 4277:         }
 4278:     }
 4279:     if ($submitonly eq 'queued') {
 4280:         my ($cdom,$cnum) = split(/_/,$courseid);
 4281:         my %queue_status =
 4282:             &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
 4283:                                                     $udom,$uname);
 4284:         return if (!defined($queue_status{'gradingqueue'}));
 4285:     }
 4286:     $$ctr++;
 4287:     my %aggregates = ();
 4288:     my $result=&Apache::loncommon::start_data_table_row().'<td align="right">'.
 4289: 	'<input type="hidden" name="ctr'.($$ctr-1).'" value="'.$student.'" />'.
 4290: 	"\n".$$ctr.'&nbsp;</td><td>&nbsp;'.
 4291: 	'<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
 4292: 	'\');" target="_self">'.$fullname.'</a> '.
 4293: 	'<span class="LC_internal_info">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</span></td>'."\n";
 4294:     $student=~s/:/_/; # colon doen't work in javascript for names
 4295:     foreach my $apart (@$parts) {
 4296: 	my ($part,$type) = &split_part_type($apart);
 4297: 	my $score=$record{"resource.$part.$type"};
 4298:         $result.='<td align="center">';
 4299:         my ($aggtries,$totaltries);
 4300:         unless (exists($aggregates{$part})) {
 4301: 	    $totaltries = $record{'resource.'.$part.'.tries'};
 4302: 
 4303: 	    $aggtries = $totaltries;
 4304:             if ($$last_resets{$part}) {  
 4305:                 $aggtries = &get_num_tries(\%record,$$last_resets{$part},
 4306: 					   $part);
 4307:             }
 4308:             $result.='<input type="hidden" name="'.
 4309:                 'GD_'.$student.'_'.$part.'_aggtries" value="'.$aggtries.'" />'."\n";
 4310:             $result.='<input type="hidden" name="'.
 4311:                 'GD_'.$student.'_'.$part.'_totaltries" value="'.$totaltries.'" />'."\n";
 4312:             $aggregates{$part} = 1;
 4313:         }
 4314: 	if ($type eq 'awarded') {
 4315: 	    my $pts = $score eq '' ? '' : &compute_points($score,$$weight{$part});
 4316: 	    $result.='<input type="hidden" name="'.
 4317: 		'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
 4318: 	    $result.='<input type="text" name="'.
 4319: 		'GD_'.$student.'_'.$part.'_awarded" '.
 4320:                 'onchange="javascript:changeSelect(\''.$part.'\',\''.$student.
 4321: 		'\')" value="'.$pts.'" size="4" /></td>'."\n";
 4322: 	} elsif ($type eq 'solved') {
 4323: 	    my ($status,$foo)=split(/_/,$score,2);
 4324: 	    $status = 'nothing' if ($status eq '');
 4325: 	    $result.='<input type="hidden" name="'.'GD_'.$student.'_'.
 4326: 		$part.'_solved_s" value="'.$status.'" />'."\n";
 4327: 	    $result.='&nbsp;<select name="'.
 4328: 		'GD_'.$student.'_'.$part.'_solved" '.
 4329:                 'onchange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";
 4330: 	    $result.= (($status eq 'excused') ? '<option> </option><option selected="selected" value="excused">'.&mt('excused').'</option>' 
 4331: 		: '<option selected="selected"> </option><option value="excused">'.&mt('excused').'</option>')."\n";
 4332: 	    $result.='<option value="reset status">'.&mt('reset status').'</option>';
 4333: 	    $result.="</select>&nbsp;</td>\n";
 4334: 	} else {
 4335: 	    $result.='<input type="hidden" name="'.
 4336: 		'GD_'.$student.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'.
 4337: 		    "\n";
 4338: 	    $result.='<input type="text" name="'.
 4339: 		'GD_'.$student.'_'.$part.'_'.$type.'" '.
 4340: 		'value="'.$score.'" size="4" /></td>'."\n";
 4341: 	}
 4342:     }
 4343:     $result.=&Apache::loncommon::end_data_table_row();
 4344:     return $result;
 4345: }
 4346: 
 4347: #--- change scores for all the students in a section/class
 4348: #    record does not get update if unchanged
 4349: sub editgrades {
 4350:     my ($request,$symb) = @_;
 4351: 
 4352:     my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
 4353:     my $title='<h2>'.&mt('Current Grade Status').'</h2>';
 4354:     $title.='<h4><b>'.&mt('Section:').'</b> '.$section_display.'</h4>'."\n";
 4355: 
 4356:     my $result= &Apache::loncommon::start_data_table().
 4357: 	&Apache::loncommon::start_data_table_header_row().
 4358: 	'<th rowspan="2" valign="middle">'.&mt('No.').'</th>'.
 4359: 	'<th rowspan="2" valign="middle">'.&nameUserString('header')."</th>\n";
 4360:     my %scoreptr = (
 4361: 		    'correct'  =>'correct_by_override',
 4362: 		    'incorrect'=>'incorrect_by_override',
 4363: 		    'excused'  =>'excused',
 4364: 		    'ungraded' =>'ungraded_attempted',
 4365:                     'credited' =>'credit_attempted',
 4366: 		    'nothing'  => '',
 4367: 		    );
 4368:     my ($classlist,undef,$fullname) = &getclasslist($env{'form.section'},'0');
 4369: 
 4370:     my (@partid);
 4371:     my %weight = ();
 4372:     my %columns = ();
 4373:     my ($i,$ctr,$count,$rec_update) = (0,0,0,0);
 4374: 
 4375:     my $partserror;
 4376:     my (@parts) = sort(&getpartlist($symb,\$partserror));
 4377:     if ($partserror) {
 4378:         return &navmap_errormsg();
 4379:     }
 4380:     my $header;
 4381:     while ($ctr < $env{'form.totalparts'}) {
 4382: 	my $partid = $env{'form.partid_'.$ctr};
 4383: 	push(@partid,$partid);
 4384: 	$weight{$partid} = $env{'form.weight_'.$partid};
 4385: 	$ctr++;
 4386:     }
 4387:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
 4388:     my $totcolspan = 0;
 4389:     foreach my $partid (@partid) {
 4390: 	$header .= '<th align="center">'.&mt('Old Score').'</th>'.
 4391: 	    '<th align="center">'.&mt('New Score').'</th>';
 4392: 	$columns{$partid}=2;
 4393: 	foreach my $stores (@parts) {
 4394: 	    my ($part,$type) = &split_part_type($stores);
 4395: 	    if ($part !~ m/^\Q$partid\E/) { next;}
 4396: 	    if ($type eq 'awarded' || $type eq 'solved') { next; }
 4397: 	    my $display=&Apache::lonnet::metadata($url,$stores.'.display');
 4398: 	    $display =~ s/\[Part: \Q$part\E\]//;
 4399:             my $narrowtext = &mt('Tries');
 4400: 	    $display =~ s/Number of Attempts/$narrowtext/;
 4401: 	    $header .= '<th align="center">'.&mt('Old').' '.$display.'</th>'.
 4402: 		'<th align="center">'.&mt('New').' '.$display.'</th>';
 4403: 	    $columns{$partid}+=2;
 4404: 	}
 4405:         $totcolspan += $columns{$partid};
 4406:     }
 4407:     foreach my $partid (@partid) {
 4408: 	my $display_part=&get_display_part($partid,$symb);
 4409: 	$result .= '<th colspan="'.$columns{$partid}.'" align="center">'.
 4410: 	    &mt('Part: [_1] (Weight = [_2])',$display_part,$weight{$partid}).
 4411: 	    '</th>';
 4412: 
 4413:     }
 4414:     $result .= &Apache::loncommon::end_data_table_header_row().
 4415: 	&Apache::loncommon::start_data_table_header_row().
 4416: 	$header.
 4417: 	&Apache::loncommon::end_data_table_header_row();
 4418:     my @noupdate;
 4419:     my ($updateCtr,$noupdateCtr) = (1,1);
 4420:     for ($i=0; $i<$env{'form.total'}; $i++) {
 4421: 	my $user = $env{'form.ctr'.$i};
 4422: 	my ($uname,$udom)=split(/:/,$user);
 4423: 	my %newrecord;
 4424: 	my $updateflag = 0;
 4425:         my $usec=$classlist->{"$uname:$udom"}[5];
 4426:         my $canmodify = &canmodify($usec);
 4427:         my $line = '<td'.($canmodify?'':' colspan="2"').'>'.
 4428:                    &nameUserString(undef,$$fullname{$user},$uname,$udom).'</td>';
 4429:         if (!$canmodify) {
 4430:             push(@noupdate,
 4431:                  $line."<td colspan=\"$totcolspan\"><span class=\"LC_warning\">".
 4432:                  &mt('Not allowed to modify student')."</span></td>");
 4433:             next;
 4434:         }
 4435:         my %aggregate = ();
 4436:         my $aggregateflag = 0;
 4437: 	$user=~s/:/_/; # colon doen't work in javascript for names
 4438: 	foreach (@partid) {
 4439: 	    my $old_aw    = $env{'form.GD_'.$user.'_'.$_.'_awarded_s'};
 4440: 	    my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1);
 4441: 	    my $old_part  = $old_aw eq '' ? '' : $old_part_pcr;
 4442: 	    my $old_score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
 4443: 	    my $awarded   = $env{'form.GD_'.$user.'_'.$_.'_awarded'};
 4444: 	    my $pcr       = $awarded/($weight{$_} ne '0' ? $weight{$_} : 1);
 4445: 	    my $partial   = $awarded eq '' ? '' : $pcr;
 4446: 	    my $score;
 4447: 	    if ($partial eq '') {
 4448: 		$score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
 4449: 	    } elsif ($partial > 0) {
 4450: 		$score = 'correct_by_override';
 4451: 	    } elsif ($partial == 0) {
 4452: 		$score = 'incorrect_by_override';
 4453: 	    }
 4454: 	    my $dropMenu = $env{'form.GD_'.$user.'_'.$_.'_solved'};
 4455: 	    $score = 'excused' if (($dropMenu eq 'excused') && ($score ne 'excused'));
 4456: 
 4457: 	    $newrecord{'resource.'.$_.'.regrader'}=
 4458: 		"$env{'user.name'}:$env{'user.domain'}";
 4459: 	    if ($dropMenu eq 'reset status' &&
 4460: 		$old_score ne '') { # ignore if no previous attempts => nothing to reset
 4461: 		$newrecord{'resource.'.$_.'.tries'} = '';
 4462: 		$newrecord{'resource.'.$_.'.solved'} = '';
 4463: 		$newrecord{'resource.'.$_.'.award'} = '';
 4464: 		$newrecord{'resource.'.$_.'.awarded'} = '';
 4465: 		$updateflag = 1;
 4466:                 if ($env{'form.GD_'.$user.'_'.$_.'_aggtries'} > 0) {
 4467:                     my $aggtries = $env{'form.GD_'.$user.'_'.$_.'_aggtries'};
 4468:                     my $totaltries = $env{'form.GD_'.$user.'_'.$_.'_totaltries'};
 4469:                     my $solvedstatus = $env{'form.GD_'.$user.'_'.$_.'_solved_s'};
 4470:                     &decrement_aggs($symb,$_,\%aggregate,$aggtries,$totaltries,$solvedstatus);
 4471:                     $aggregateflag = 1;
 4472:                 }
 4473: 	    } elsif (!($old_part eq $partial && $old_score eq $score)) {
 4474: 		$updateflag = 1;
 4475: 		$newrecord{'resource.'.$_.'.awarded'}  = $partial if $partial ne '';
 4476: 		$newrecord{'resource.'.$_.'.solved'}   = $score;
 4477: 		$rec_update++;
 4478: 	    }
 4479: 
 4480: 	    $line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
 4481: 		'<td align="center">'.$awarded.
 4482: 		($score eq 'excused' ? $score : '').'&nbsp;</td>';
 4483: 
 4484: 
 4485: 	    my $partid=$_;
 4486: 	    foreach my $stores (@parts) {
 4487: 		my ($part,$type) = &split_part_type($stores);
 4488: 		if ($part !~ m/^\Q$partid\E/) { next;}
 4489: 		if ($type eq 'awarded' || $type eq 'solved') { next; }
 4490: 		my $old_aw    = $env{'form.GD_'.$user.'_'.$part.'_'.$type.'_s'};
 4491: 		my $awarded   = $env{'form.GD_'.$user.'_'.$part.'_'.$type};
 4492: 		if ($awarded ne '' && $awarded ne $old_aw) {
 4493: 		    $newrecord{'resource.'.$part.'.'.$type}= $awarded;
 4494: 		    $newrecord{'resource.'.$part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
 4495: 		    $updateflag=1;
 4496: 		}
 4497: 		$line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
 4498: 		    '<td align="center">'.$awarded.'&nbsp;</td>';
 4499: 	    }
 4500: 	}
 4501: 	$line.="\n";
 4502: 
 4503: 	my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
 4504: 	my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
 4505: 
 4506: 	if ($updateflag) {
 4507: 	    $count++;
 4508: 	    &Apache::lonnet::cstore(\%newrecord,$symb,$env{'request.course.id'},
 4509: 				    $udom,$uname);
 4510: 
 4511: 	    if (&Apache::bridgetask::in_queue('gradingqueue',$symb,$cdom,
 4512: 					      $cnum,$udom,$uname)) {
 4513: 		# need to figure out if should be in queue.
 4514: 		my %record =  
 4515: 		    &Apache::lonnet::restore($symb,$env{'request.course.id'},
 4516: 					     $udom,$uname);
 4517: 		my $all_graded = 1;
 4518: 		my $none_graded = 1;
 4519: 		foreach my $part (@parts) {
 4520: 		    if ( $record{'resource.'.$part.'.awarded'} eq '' ) {
 4521: 			$all_graded = 0;
 4522: 		    } else {
 4523: 			$none_graded = 0;
 4524: 		    }
 4525: 		}
 4526: 
 4527: 		if ($all_graded || $none_graded) {
 4528: 		    &Apache::bridgetask::remove_from_queue('gradingqueue',
 4529: 							   $symb,$cdom,$cnum,
 4530: 							   $udom,$uname);
 4531: 		}
 4532: 	    }
 4533: 
 4534: 	    $result.=&Apache::loncommon::start_data_table_row().
 4535: 		'<td align="right">&nbsp;'.$updateCtr.'&nbsp;</td>'.$line.
 4536: 		&Apache::loncommon::end_data_table_row();
 4537: 	    $updateCtr++;
 4538: 	} else {
 4539: 	    push(@noupdate,
 4540: 		 '<td align="right">&nbsp;'.$noupdateCtr.'&nbsp;</td>'.$line);
 4541: 	    $noupdateCtr++;
 4542: 	}
 4543:         if ($aggregateflag) {
 4544:             &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
 4545: 				  $cdom,$cnum);
 4546:         }
 4547:     }
 4548:     if (@noupdate) {
 4549:         my $numcols=$totcolspan+2;
 4550: 	$result .= &Apache::loncommon::start_data_table_row('LC_empty_row').
 4551: 	    '<td align="center" colspan="'.$numcols.'">'.
 4552: 	    &mt('No Changes Occurred For the Students Below').
 4553: 	    '</td>'.
 4554: 	    &Apache::loncommon::end_data_table_row();
 4555: 	foreach my $line (@noupdate) {
 4556: 	    $result.=
 4557: 		&Apache::loncommon::start_data_table_row().
 4558: 		$line.
 4559: 		&Apache::loncommon::end_data_table_row();
 4560: 	}
 4561:     }
 4562:     $result .= &Apache::loncommon::end_data_table();
 4563:     my $msg = '<p><b>'.
 4564: 	&mt('Number of records updated = [_1] for [quant,_2,student].',
 4565: 	    $rec_update,$count).'</b><br />'.
 4566: 	'<b>'.&mt('Total number of students = [_1]',$env{'form.total'}).
 4567: 	'</b></p>';
 4568:     return $title.$msg.$result;
 4569: }
 4570: 
 4571: sub split_part_type {
 4572:     my ($partstr) = @_;
 4573:     my ($temp,@allparts)=split(/_/,$partstr);
 4574:     my $type=pop(@allparts);
 4575:     my $part=join('_',@allparts);
 4576:     return ($part,$type);
 4577: }
 4578: 
 4579: #------------- end of section for handling grading by section/class ---------
 4580: #
 4581: #----------------------------------------------------------------------------
 4582: 
 4583: 
 4584: #----------------------------------------------------------------------------
 4585: #
 4586: #-------------------------- Next few routines handles grading by csv upload
 4587: #
 4588: #--- Javascript to handle csv upload
 4589: sub csvupload_javascript_reverse_associate {
 4590:     my $error1=&mt('You need to specify the username or the student/employee ID');
 4591:     my $error2=&mt('You need to specify at least one grading field');
 4592:   &js_escape(\$error1);
 4593:   &js_escape(\$error2);
 4594:   return(<<ENDPICK);
 4595:   function verify(vf) {
 4596:     var foundsomething=0;
 4597:     var founduname=0;
 4598:     var foundID=0;
 4599:     for (i=0;i<=vf.nfields.value;i++) {
 4600:       tw=eval('vf.f'+i+'.selectedIndex');
 4601:       if (i==0 && tw!=0) { foundID=1; }
 4602:       if (i==1 && tw!=0) { founduname=1; }
 4603:       if (i!=0 && i!=1 && i!=2 && tw!=0) { foundsomething=1; }
 4604:     }
 4605:     if (founduname==0 && foundID==0) {
 4606: 	alert('$error1');
 4607: 	return;
 4608:     }
 4609:     if (foundsomething==0) {
 4610: 	alert('$error2');
 4611: 	return;
 4612:     }
 4613:     vf.submit();
 4614:   }
 4615:   function flip(vf,tf) {
 4616:     var nw=eval('vf.f'+tf+'.selectedIndex');
 4617:     var i;
 4618:     for (i=0;i<=vf.nfields.value;i++) {
 4619:       //can not pick the same destination field for both name and domain
 4620:       if (((i ==0)||(i ==1)) && 
 4621:           ((tf==0)||(tf==1)) && 
 4622:           (i!=tf) &&
 4623:           (eval('vf.f'+i+'.selectedIndex')==nw)) {
 4624:         eval('vf.f'+i+'.selectedIndex=0;')
 4625:       }
 4626:     }
 4627:   }
 4628: ENDPICK
 4629: }
 4630: 
 4631: sub csvupload_javascript_forward_associate {
 4632:     my $error1=&mt('You need to specify the username or the student/employee ID');
 4633:     my $error2=&mt('You need to specify at least one grading field');
 4634:   &js_escape(\$error1);
 4635:   &js_escape(\$error2);
 4636:   return(<<ENDPICK);
 4637:   function verify(vf) {
 4638:     var foundsomething=0;
 4639:     var founduname=0;
 4640:     var foundID=0;
 4641:     for (i=0;i<=vf.nfields.value;i++) {
 4642:       tw=eval('vf.f'+i+'.selectedIndex');
 4643:       if (tw==1) { foundID=1; }
 4644:       if (tw==2) { founduname=1; }
 4645:       if (tw>3) { foundsomething=1; }
 4646:     }
 4647:     if (founduname==0 && foundID==0) {
 4648: 	alert('$error1');
 4649: 	return;
 4650:     }
 4651:     if (foundsomething==0) {
 4652: 	alert('$error2');
 4653: 	return;
 4654:     }
 4655:     vf.submit();
 4656:   }
 4657:   function flip(vf,tf) {
 4658:     var nw=eval('vf.f'+tf+'.selectedIndex');
 4659:     var i;
 4660:     //can not pick the same destination field twice
 4661:     for (i=0;i<=vf.nfields.value;i++) {
 4662:       if ((i!=tf) && (eval('vf.f'+i+'.selectedIndex')==nw)) {
 4663:         eval('vf.f'+i+'.selectedIndex=0;')
 4664:       }
 4665:     }
 4666:   }
 4667: ENDPICK
 4668: }
 4669: 
 4670: sub csvuploadmap_header {
 4671:     my ($request,$symb,$datatoken,$distotal)= @_;
 4672:     my $javascript;
 4673:     if ($env{'form.upfile_associate'} eq 'reverse') {
 4674: 	$javascript=&csvupload_javascript_reverse_associate();
 4675:     } else {
 4676: 	$javascript=&csvupload_javascript_forward_associate();
 4677:     }
 4678: 
 4679:     my $checked=(($env{'form.noFirstLine'})?' checked="checked"':'');
 4680:     my $ignore=&mt('Ignore First Line');
 4681:     $symb = &Apache::lonenc::check_encrypt($symb);
 4682:     $request->print('<form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">'.
 4683:                     &mt('Total number of records found in file: [_1]',$distotal).'<hr />'.
 4684:                     &mt('Associate entries from the uploaded file with as many fields as you can.'));
 4685:     my $reverse=&mt("Reverse Association");
 4686:     $request->print(<<ENDPICK);
 4687: <br />
 4688: <input type="button" value="$reverse" onclick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" />
 4689: <label><input type="checkbox" name="noFirstLine" $checked />$ignore</label>
 4690: <input type="hidden" name="associate"  value="" />
 4691: <input type="hidden" name="phase"      value="three" />
 4692: <input type="hidden" name="datatoken"  value="$datatoken" />
 4693: <input type="hidden" name="fileupload" value="$env{'form.fileupload'}" />
 4694: <input type="hidden" name="upfiletype" value="$env{'form.upfiletype'}" />
 4695: <input type="hidden" name="upfile_associate" 
 4696:                                        value="$env{'form.upfile_associate'}" />
 4697: <input type="hidden" name="symb"       value="$symb" />
 4698: <input type="hidden" name="command"    value="csvuploadoptions" />
 4699: <hr />
 4700: ENDPICK
 4701:     $request->print(&Apache::lonhtmlcommon::scripttag($javascript));
 4702:     return '';
 4703: 
 4704: }
 4705: 
 4706: sub csvupload_fields {
 4707:     my ($symb,$errorref) = @_;
 4708:     my (@parts) = &getpartlist($symb,$errorref);
 4709:     if (ref($errorref)) {
 4710:         if ($$errorref) {
 4711:             return;
 4712:         }
 4713:     }
 4714: 
 4715:     my @fields=(['ID','Student/Employee ID'],
 4716: 		['username','Student Username'],
 4717: 		['domain','Student Domain']);
 4718:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
 4719:     foreach my $part (sort(@parts)) {
 4720: 	my @datum;
 4721: 	my $display=&Apache::lonnet::metadata($url,$part.'.display');
 4722: 	my $name=$part;
 4723: 	if  (!$display) { $display = $name; }
 4724: 	@datum=($name,$display);
 4725: 	if ($name=~/^stores_(.*)_awarded/) {
 4726: 	    push(@fields,['stores_'.$1.'_points',"Points [Part: $1]"]);
 4727: 	}
 4728: 	push(@fields,\@datum);
 4729:     }
 4730:     return (@fields);
 4731: }
 4732: 
 4733: sub csvuploadmap_footer {
 4734:     my ($request,$i,$keyfields) =@_;
 4735:     my $buttontext = &mt('Assign Grades');
 4736:     $request->print(<<ENDPICK);
 4737: </table>
 4738: <input type="hidden" name="nfields" value="$i" />
 4739: <input type="hidden" name="keyfields" value="$keyfields" />
 4740: <input type="button" onclick="javascript:verify(this.form)" value="$buttontext" /><br />
 4741: </form>
 4742: ENDPICK
 4743: }
 4744: 
 4745: sub checkforfile_js {
 4746:     my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
 4747:     &js_escape(\$alertmsg);
 4748:     my $result = &Apache::lonhtmlcommon::scripttag(<<CSVFORMJS);
 4749:     function checkUpload(formname) {
 4750: 	if (formname.upfile.value == "") {
 4751: 	    alert("$alertmsg");
 4752: 	    return false;
 4753: 	}
 4754: 	formname.submit();
 4755:     }
 4756: CSVFORMJS
 4757:     return $result;
 4758: }
 4759: 
 4760: sub upcsvScores_form {
 4761:     my ($request,$symb) = @_;
 4762:     if (!$symb) {return '';}
 4763:     my $result=&checkforfile_js();
 4764:     $result.=&Apache::loncommon::start_data_table().
 4765:              &Apache::loncommon::start_data_table_header_row().
 4766:              '<th>'.&mt('Specify a file containing the class scores for current resource.').'</th>'.
 4767:              &Apache::loncommon::end_data_table_header_row().
 4768:              &Apache::loncommon::start_data_table_row().'<td>';
 4769:     my $upload=&mt("Upload Scores");
 4770:     my $upfile_select=&Apache::loncommon::upfile_select_html();
 4771:     my $ignore=&mt('Ignore First Line');
 4772:     $symb = &Apache::lonenc::check_encrypt($symb);
 4773:     $result.=<<ENDUPFORM;
 4774: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 4775: <input type="hidden" name="symb" value="$symb" />
 4776: <input type="hidden" name="command" value="csvuploadmap" />
 4777: $upfile_select
 4778: <br /><input type="button" onclick="javascript:checkUpload(this.form);" value="$upload" />
 4779: <label><input type="checkbox" name="noFirstLine" />$ignore</label>
 4780: </form>
 4781: ENDUPFORM
 4782:     $result.=&Apache::loncommon::help_open_topic("Course_Convert_To_CSV",
 4783:                            &mt("How do I create a CSV file from a spreadsheet")).
 4784:             '</td>'.
 4785:             &Apache::loncommon::end_data_table_row().
 4786:             &Apache::loncommon::end_data_table();
 4787:     return $result;
 4788: }
 4789: 
 4790: 
 4791: sub csvuploadmap {
 4792:     my ($request,$symb) = @_;
 4793:     if (!$symb) {return '';}
 4794: 
 4795:     my $datatoken;
 4796:     if (!$env{'form.datatoken'}) {
 4797: 	$datatoken=&Apache::loncommon::upfile_store($request);
 4798:     } else {
 4799:         $datatoken=&Apache::loncommon::valid_datatoken($env{'form.datatoken'});
 4800:         if ($datatoken ne '') { 
 4801: 	    &Apache::loncommon::load_tmp_file($request,$datatoken);
 4802:         }
 4803:     }
 4804:     my @records=&Apache::loncommon::upfile_record_sep();
 4805:     if ($env{'form.noFirstLine'}) { shift(@records); }
 4806:     &csvuploadmap_header($request,$symb,$datatoken,$#records+1);
 4807:     my ($i,$keyfields);
 4808:     if (@records) {
 4809:         my $fieldserror;
 4810: 	my @fields=&csvupload_fields($symb,\$fieldserror);
 4811:         if ($fieldserror) {
 4812:             $request->print(&navmap_errormsg());
 4813:             return;
 4814:         }
 4815: 	if ($env{'form.upfile_associate'} eq 'reverse') {	
 4816: 	    &Apache::loncommon::csv_print_samples($request,\@records);
 4817: 	    $i=&Apache::loncommon::csv_print_select_table($request,\@records,
 4818: 							  \@fields);
 4819: 	    foreach (@fields) { $keyfields.=$_->[0].','; }
 4820: 	    chop($keyfields);
 4821: 	} else {
 4822: 	    unshift(@fields,['none','']);
 4823: 	    $i=&Apache::loncommon::csv_samples_select_table($request,\@records,
 4824: 							    \@fields);
 4825:             foreach my $rec (@records) {
 4826:                 my %temp = &Apache::loncommon::record_sep($rec);
 4827:                 if (%temp) {
 4828:                     $keyfields=join(',',sort(keys(%temp)));
 4829:                     last;
 4830:                 }
 4831:             }
 4832: 	}
 4833:     }
 4834:     &csvuploadmap_footer($request,$i,$keyfields);
 4835: 
 4836:     return '';
 4837: }
 4838: 
 4839: sub csvuploadoptions {
 4840:     my ($request,$symb)= @_;
 4841:     my $overwrite=&mt('Overwrite any existing score');
 4842:     my $checked=(($env{'form.noFirstLine'})?'1':'0');
 4843:     my $ignore=&mt('Ignore First Line');
 4844:     $request->print(<<ENDPICK);
 4845: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 4846: <input type="hidden" name="command"    value="csvuploadassign" />
 4847: <p>
 4848: <label>
 4849:    <input type="checkbox" name="overwite_scores" checked="checked" />
 4850:    $overwrite
 4851: </label>
 4852: </p>
 4853: ENDPICK
 4854:     my %fields=&get_fields();
 4855:     if (!defined($fields{'domain'})) {
 4856: 	my $domform = &Apache::loncommon::select_dom_form($env{'request.role.domain'},'default_domain');
 4857:         $request->print("\n<p>".&mt('Users are in domain: [_1]',$domform)."</p>\n");
 4858:     }
 4859:     foreach my $key (sort(keys(%env))) {
 4860: 	if ($key !~ /^form\.(.*)$/) { next; }
 4861: 	my $cleankey=$1;
 4862: 	if ($cleankey eq 'command') { next; }
 4863: 	$request->print('<input type="hidden" name="'.$cleankey.
 4864: 			'"  value="'.$env{$key}.'" />'."\n");
 4865:     }
 4866:     # FIXME do a check for any duplicated user ids...
 4867:     # FIXME do a check for any invalid user ids?...
 4868:     $request->print('<input type="submit" value="'.&mt('Assign Grades').'" /><br />
 4869: <hr /></form>'."\n");
 4870:     return '';
 4871: }
 4872: 
 4873: sub get_fields {
 4874:     my %fields;
 4875:     my @keyfields = split(/\,/,$env{'form.keyfields'});
 4876:     for (my $i=0; $i<=$env{'form.nfields'}; $i++) {
 4877: 	if ($env{'form.upfile_associate'} eq 'reverse') {
 4878: 	    if ($env{'form.f'.$i} ne 'none') {
 4879: 		$fields{$keyfields[$i]}=$env{'form.f'.$i};
 4880: 	    }
 4881: 	} else {
 4882: 	    if ($env{'form.f'.$i} ne 'none') {
 4883: 		$fields{$env{'form.f'.$i}}=$keyfields[$i];
 4884: 	    }
 4885: 	}
 4886:     }
 4887:     return %fields;
 4888: }
 4889: 
 4890: sub csvuploadassign {
 4891:     my ($request,$symb) = @_;
 4892:     if (!$symb) {return '';}
 4893:     my $error_msg = '';
 4894:     my $datatoken = &Apache::loncommon::valid_datatoken($env{'form.datatoken'});
 4895:     if ($datatoken ne '') {
 4896:         &Apache::loncommon::load_tmp_file($request,$datatoken);
 4897:     }
 4898:     my @gradedata = &Apache::loncommon::upfile_record_sep();
 4899:     if ($env{'form.noFirstLine'}) { shift(@gradedata); }
 4900:     my %fields=&get_fields();
 4901:     my $courseid=$env{'request.course.id'};
 4902:     my ($classlist) = &getclasslist('all',0);
 4903:     my @notallowed;
 4904:     my @skipped;
 4905:     my @warnings;
 4906:     my $countdone=0;
 4907:     foreach my $grade (@gradedata) {
 4908: 	my %entries=&Apache::loncommon::record_sep($grade);
 4909: 	my $domain;
 4910: 	if ($entries{$fields{'domain'}}) {
 4911: 	    $domain=$entries{$fields{'domain'}};
 4912: 	} else {
 4913: 	    $domain=$env{'form.default_domain'};
 4914: 	}
 4915: 	$domain=~s/\s//g;
 4916: 	my $username=$entries{$fields{'username'}};
 4917: 	$username=~s/\s//g;
 4918: 	if (!$username) {
 4919: 	    my $id=$entries{$fields{'ID'}};
 4920: 	    $id=~s/\s//g;
 4921: 	    my %ids=&Apache::lonnet::idget($domain,$id);
 4922: 	    $username=$ids{$id};
 4923: 	}
 4924: 	if (!exists($$classlist{"$username:$domain"})) {
 4925: 	    my $id=$entries{$fields{'ID'}};
 4926: 	    $id=~s/\s//g;
 4927: 	    if ($id) {
 4928: 		push(@skipped,"$id:$domain");
 4929: 	    } else {
 4930: 		push(@skipped,"$username:$domain");
 4931: 	    }
 4932: 	    next;
 4933: 	}
 4934: 	my $usec=$classlist->{"$username:$domain"}[5];
 4935: 	if (!&canmodify($usec)) {
 4936: 	    push(@notallowed,"$username:$domain");
 4937: 	    next;
 4938: 	}
 4939: 	my %points;
 4940: 	my %grades;
 4941: 	foreach my $dest (keys(%fields)) {
 4942: 	    if ($dest eq 'ID' || $dest eq 'username' ||
 4943: 		$dest eq 'domain') { next; }
 4944: 	    if ($entries{$fields{$dest}} =~ /^\s*$/) { next; }
 4945: 	    if ($dest=~/stores_(.*)_points/) {
 4946: 		my $part=$1;
 4947: 		my $wgt =&Apache::lonnet::EXT('resource.'.$part.'.weight',
 4948: 					      $symb,$domain,$username);
 4949:                 if ($wgt) {
 4950:                     $entries{$fields{$dest}}=~s/\s//g;
 4951:                     my $pcr=$entries{$fields{$dest}} / $wgt;
 4952:                     my $award=($pcr == 0) ? 'incorrect_by_override'
 4953:                                           : 'correct_by_override';
 4954:                     if ($pcr>1) {
 4955:                         push(@warnings,&mt("[_1]: point value larger than weight","$username:$domain"));
 4956:                     }
 4957:                     $grades{"resource.$part.awarded"}=$pcr;
 4958:                     $grades{"resource.$part.solved"}=$award;
 4959:                     $points{$part}=1;
 4960:                 } else {
 4961:                     $error_msg = "<br />" .
 4962:                         &mt("Some point values were assigned"
 4963:                             ." for problems with a weight "
 4964:                             ."of zero. These values were "
 4965:                             ."ignored.");
 4966:                 }
 4967: 	    } else {
 4968: 		if ($dest=~/stores_(.*)_awarded/) { if ($points{$1}) {next;} }
 4969: 		if ($dest=~/stores_(.*)_solved/)  { if ($points{$1}) {next;} }
 4970: 		my $store_key=$dest;
 4971: 		$store_key=~s/^stores/resource/;
 4972: 		$store_key=~s/_/\./g;
 4973: 		$grades{$store_key}=$entries{$fields{$dest}};
 4974: 	    }
 4975: 	}
 4976: 	if (! %grades) {
 4977:            push(@skipped,&mt("[_1]: no data to save","$username:$domain")); 
 4978:         } else {
 4979: 	   $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
 4980: 	   my $result=&Apache::lonnet::cstore(\%grades,$symb,
 4981: 					   $env{'request.course.id'},
 4982: 					   $domain,$username);
 4983: 	   if ($result eq 'ok') {
 4984: # Successfully stored
 4985: 	      $request->print('.');
 4986: # Remove from grading queue
 4987:               &Apache::bridgetask::remove_from_queue('gradingqueue',$symb,
 4988:                                              $env{'course.'.$env{'request.course.id'}.'.domain'},
 4989:                                              $env{'course.'.$env{'request.course.id'}.'.num'},
 4990:                                              $domain,$username);
 4991: 	   } else {
 4992: 	      $request->print("<p><span class=\"LC_error\">".
 4993:                               &mt("Failed to save data for student [_1]. Message when trying to save was: [_2]",
 4994:                                   "$username:$domain",$result)."</span></p>");
 4995: 	   }
 4996: 	   $request->rflush();
 4997: 	   $countdone++;
 4998:         }
 4999:     }
 5000:     $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt("Saved scores for [quant,_1,student]",$countdone),$countdone==0));
 5001:     if (@warnings) {
 5002:         $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('Warnings generated for the following saved scores:'),1).'<br />');
 5003:         $request->print(join(', ',@warnings));
 5004:     }
 5005:     if (@skipped) {
 5006: 	$request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('No scores stored for the following username(s):'),1).'<br />');
 5007:         $request->print(join(', ',@skipped));
 5008:     }
 5009:     if (@notallowed) {
 5010: 	$request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('Modification of scores not allowed for the following username(s):'),1).'<br />');
 5011: 	$request->print(join(', ',@notallowed));
 5012:     }
 5013:     $request->print("<br />\n");
 5014:     return $error_msg;
 5015: }
 5016: #------------- end of section for handling csv file upload ---------
 5017: #
 5018: #-------------------------------------------------------------------
 5019: #
 5020: #-------------- Next few routines handle grading by page/sequence
 5021: #
 5022: #--- Select a page/sequence and a student to grade
 5023: sub pickStudentPage {
 5024:     my ($request,$symb) = @_;
 5025: 
 5026:     my $alertmsg = &mt('Please select the student you wish to grade.');
 5027:     &js_escape(\$alertmsg);
 5028:     $request->print(&Apache::lonhtmlcommon::scripttag(<<LISTJAVASCRIPT));
 5029: 
 5030: function checkPickOne(formname) {
 5031:     if (radioSelection(formname.student) == null) {
 5032: 	alert("$alertmsg");
 5033: 	return;
 5034:     }
 5035:     ptr = pullDownSelection(formname.selectpage);
 5036:     formname.page.value = formname["page"+ptr].value;
 5037:     formname.title.value = formname["title"+ptr].value;
 5038:     formname.submit();
 5039: }
 5040: 
 5041: LISTJAVASCRIPT
 5042:     &commonJSfunctions($request);
 5043: 
 5044:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
 5045:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
 5046:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
 5047:     my $getgroup  = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
 5048: 
 5049:     my $result='<h3><span class="LC_info">&nbsp;'.
 5050: 	&mt('Manual Grading by Page or Sequence').'</span></h3>';
 5051: 
 5052:     $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";
 5053:     my $map_error;
 5054:     my ($titles,$symbx) = &getSymbMap($map_error);
 5055:     if ($map_error) {
 5056:         $request->print(&navmap_errormsg());
 5057:         return; 
 5058:     }
 5059:     my ($curpage) =&Apache::lonnet::decode_symb($symb); 
 5060: #    my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); 
 5061: #    my $type=($curpage =~ /\.(page|sequence)/);
 5062: 
 5063:     # Collection of hidden fields
 5064:     my $ctr=0;
 5065:     foreach (@$titles) {
 5066: 	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
 5067: 	$result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n";
 5068: 	$result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n";
 5069: 	$ctr++;
 5070:     }
 5071:     $result.='<input type="hidden" name="page" />'."\n".
 5072: 	'<input type="hidden" name="title" />'."\n";
 5073: 
 5074:     $result.=&build_section_inputs();
 5075:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
 5076:     $result.='<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
 5077:         '<input type="hidden" name="command" value="displayPage" />'."\n".
 5078:         '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
 5079: 
 5080:     # Show grading options
 5081:     $result.=&Apache::lonhtmlcommon::start_pick_box();
 5082:     my $select = '<select name="selectpage">'."\n";
 5083:     $ctr=0;
 5084:     foreach (@$titles) {
 5085:         my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
 5086:         $select.='<option value="'.$ctr.'"'.
 5087:             ($$symbx{$_} =~ /$curpage$/ ? ' selected="selected"' : '').
 5088:             '>'.$showtitle.'</option>'."\n";
 5089:         $ctr++;
 5090:     }
 5091:     $select.= '</select>';
 5092: 
 5093:     $result.=
 5094:         &Apache::lonhtmlcommon::row_title(&mt('Problems from'))
 5095:        .$select
 5096:        .&Apache::lonhtmlcommon::row_closure();
 5097: 
 5098:     $result.=
 5099:         &Apache::lonhtmlcommon::row_title(&mt('View Problem Text'))
 5100:        .'<label><input type="radio" name="vProb" value="no"'
 5101:            .' checked="checked" /> '.&mt('no').' </label>'."\n"
 5102:        .'<label><input type="radio" name="vProb" value="yes" />'
 5103:            .&mt('yes').'</label>'."\n"
 5104:        .&Apache::lonhtmlcommon::row_closure();
 5105: 
 5106:     $result.=
 5107:         &Apache::lonhtmlcommon::row_title(&mt('View Submissions'))
 5108:        .'<label><input type="radio" name="lastSub" value="none" /> '
 5109:            .&mt('none').' </label>'."\n"
 5110:        .'<label><input type="radio" name="lastSub" value="datesub"'
 5111:            .' checked="checked" /> '.&mt('all submissions').'</label>'."\n"
 5112:        .'<label><input type="radio" name="lastSub" value="all" /> '
 5113:            .&mt('all submissions with details').' </label>'
 5114:        .&Apache::lonhtmlcommon::row_closure();
 5115: 
 5116:     $result.=
 5117:         &Apache::lonhtmlcommon::row_title(&mt('Use CODE'))
 5118:        .'<input type="text" name="CODE" value="" />'
 5119:        .&Apache::lonhtmlcommon::row_closure(1)
 5120:        .&Apache::lonhtmlcommon::end_pick_box();
 5121: 
 5122:     # Show list of students to select for grading
 5123:     $result.='<br /><input type="button" '.
 5124:              'onclick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /><br />'."\n";
 5125: 
 5126:     $request->print($result);
 5127: 
 5128:     my $studentTable.='&nbsp;<b>'.&mt('Select a student you wish to grade and then click on the Next button.').'</b><br />'.
 5129: 	&Apache::loncommon::start_data_table().
 5130: 	&Apache::loncommon::start_data_table_header_row().
 5131: 	'<th align="right">&nbsp;'.&mt('No.').'</th>'.
 5132: 	'<th>'.&nameUserString('header').'</th>'.
 5133: 	'<th align="right">&nbsp;'.&mt('No.').'</th>'.
 5134: 	'<th>'.&nameUserString('header').'</th>'.
 5135: 	&Apache::loncommon::end_data_table_header_row();
 5136:  
 5137:     my (undef,undef,$fullname) = &getclasslist($getsec,'1',$getgroup);
 5138:     my $ptr = 1;
 5139:     foreach my $student (sort 
 5140: 			 {
 5141: 			     if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
 5142: 				 return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
 5143: 			     }
 5144: 			     return $a cmp $b;
 5145: 			 } (keys(%$fullname))) {
 5146: 	my ($uname,$udom) = split(/:/,$student);
 5147: 	$studentTable.=($ptr%2==1 ? &Apache::loncommon::start_data_table_row()
 5148:                                   : '</td>');
 5149: 	$studentTable.='<td align="right">'.$ptr.'&nbsp;</td>';
 5150: 	$studentTable.='<td>&nbsp;<label><input type="radio" name="student" value="'.$student.'" /> '
 5151: 	    .&nameUserString(undef,$$fullname{$student},$uname,$udom)."</label>\n";
 5152: 	$studentTable.=
 5153: 	    ($ptr%2 == 0 ? '</td>'.&Apache::loncommon::end_data_table_row() 
 5154:                          : '');
 5155: 	$ptr++;
 5156:     }
 5157:     if ($ptr%2 == 0) {
 5158: 	$studentTable.='</td><td>&nbsp;</td><td>&nbsp;</td>'.
 5159: 	    &Apache::loncommon::end_data_table_row();
 5160:     }
 5161:     $studentTable.=&Apache::loncommon::end_data_table()."\n";
 5162:     $studentTable.='<input type="button" '.
 5163:                    'onclick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /></form>'."\n";
 5164: 
 5165:     $request->print($studentTable);
 5166: 
 5167:     return '';
 5168: }
 5169: 
 5170: sub getSymbMap {
 5171:     my ($map_error) = @_;
 5172:     my $navmap = Apache::lonnavmaps::navmap->new();
 5173:     unless (ref($navmap)) {
 5174:         if (ref($map_error)) {
 5175:             $$map_error = 'navmap';
 5176:         }
 5177:         return;
 5178:     }
 5179:     my %symbx = ();
 5180:     my @titles = ();
 5181:     my $minder = 0;
 5182: 
 5183:     # Gather every sequence that has problems.
 5184:     my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); },
 5185: 					       1,0,1);
 5186:     for my $sequence ($navmap->getById('0.0'), @sequences) {
 5187: 	if ($navmap->hasResource($sequence, sub { shift->is_problem(); }, 0) ) {
 5188: 	    my $title = $minder.'.'.
 5189: 		&HTML::Entities::encode($sequence->compTitle(),'"\'&');
 5190: 	    push(@titles, $title); # minder in case two titles are identical
 5191: 	    $symbx{$title} = &HTML::Entities::encode($sequence->symb(),'"\'&');
 5192: 	    $minder++;
 5193: 	}
 5194:     }
 5195:     return \@titles,\%symbx;
 5196: }
 5197: 
 5198: #
 5199: #--- Displays a page/sequence w/wo problems, w/wo submissions
 5200: sub displayPage {
 5201:     my ($request,$symb) = @_;
 5202:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
 5203:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
 5204:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
 5205:     my $pageTitle = $env{'form.page'};
 5206:     my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
 5207:     my ($uname,$udom) = split(/:/,$env{'form.student'});
 5208:     my $usec=$classlist->{$env{'form.student'}}[5];
 5209: 
 5210:     #need to make sure we have the correct data for later EXT calls, 
 5211:     #thus invalidate the cache
 5212:     &Apache::lonnet::devalidatecourseresdata(
 5213:                  $env{'course.'.$env{'request.course.id'}.'.num'},
 5214:                  $env{'course.'.$env{'request.course.id'}.'.domain'});
 5215:     &Apache::lonnet::clear_EXT_cache_status();
 5216: 
 5217:     if (!&canview($usec)) {
 5218: 	$request->print(
 5219:             '<span class="LC_warning">'.
 5220:             &mt('Unable to view requested student. ([_1])',
 5221:                 $env{'form.student'}).
 5222:             '</span>');
 5223:         return;
 5224:     }
 5225:     my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
 5226:     $result.='<h3>&nbsp;'.&mt('Student: [_1]',&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom)).
 5227: 	'</h3>'."\n";
 5228:     $env{'form.CODE'} = uc($env{'form.CODE'});
 5229:     if (&Apache::lonnet::validCODE(uc($env{'form.CODE'}))) {
 5230: 	$result.='<h3>&nbsp;'.&mt('CODE: [_1]',$env{'form.CODE'}).'</h3>'."\n";
 5231:     } else {
 5232: 	delete($env{'form.CODE'});
 5233:     }
 5234:     &sub_page_js($request);
 5235:     $request->print($result);
 5236: 
 5237:     my $navmap = Apache::lonnavmaps::navmap->new();
 5238:     unless (ref($navmap)) {
 5239:         $request->print(&navmap_errormsg());
 5240:         return;
 5241:     }
 5242:     my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});
 5243:     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
 5244:     if (!$map) {
 5245: 	$request->print('<span class="LC_warning">'.&mt('Unable to view requested sequence. ([_1])',$resUrl).'</span>');
 5246: 	return; 
 5247:     }
 5248:     my $iterator = $navmap->getIterator($map->map_start(),
 5249: 					$map->map_finish());
 5250: 
 5251:     my $studentTable='<form action="/adm/grades" method="post" name="gradePage">'."\n".
 5252: 	'<input type="hidden" name="command" value="gradeByPage" />'."\n".
 5253: 	'<input type="hidden" name="fullname" value="'.$$fullname{$env{'form.student'}}.'" />'."\n".
 5254: 	'<input type="hidden" name="student" value="'.$env{'form.student'}.'" />'."\n".
 5255: 	'<input type="hidden" name="page"    value="'.$pageTitle.'" />'."\n".
 5256: 	'<input type="hidden" name="title"   value="'.$env{'form.title'}.'" />'."\n".
 5257: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 5258: 	'<input type="hidden" name="overRideScore" value="no" />'."\n";
 5259: 
 5260:     if (defined($env{'form.CODE'})) {
 5261: 	$studentTable.=
 5262: 	    '<input type="hidden" name="CODE" value="'.$env{'form.CODE'}.'" />'."\n";
 5263:     }
 5264:     my $checkIcon = '<img alt="'.&mt('Check Mark').
 5265: 	'" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
 5266: 
 5267:     $studentTable.='&nbsp;<span class="LC_info">'.
 5268:         &mt('Problems graded correct by the computer are marked with a [_1] symbol.',$checkIcon).
 5269:         '</span>'."\n".
 5270: 	&Apache::loncommon::start_data_table().
 5271: 	&Apache::loncommon::start_data_table_header_row().
 5272: 	'<th>'.&mt('Prob.').'</th>'.
 5273: 	'<th>&nbsp;'.($env{'form.vProb'} eq 'no' ? &mt('Title') : &mt('Problem Text')).'/'.&mt('Grade').'</th>'.
 5274: 	&Apache::loncommon::end_data_table_header_row();
 5275: 
 5276:     &Apache::lonxml::clear_problem_counter();
 5277:     my ($depth,$question,$prob) = (1,1,1);
 5278:     $iterator->next(); # skip the first BEGIN_MAP
 5279:     my $curRes = $iterator->next(); # for "current resource"
 5280:     while ($depth > 0) {
 5281:         if($curRes == $iterator->BEGIN_MAP) { $depth++; }
 5282:         if($curRes == $iterator->END_MAP) { $depth--; }
 5283: 
 5284:         if (ref($curRes) && $curRes->is_problem()) {
 5285: 	    my $parts = $curRes->parts();
 5286:             my $title = $curRes->compTitle();
 5287: 	    my $symbx = $curRes->symb();
 5288: 	    $studentTable.=
 5289: 		&Apache::loncommon::start_data_table_row().
 5290: 		'<td align="center" valign="top" >'.$prob.
 5291: 		(scalar(@{$parts}) == 1 ? '' 
 5292: 		                        : '<br />('.&mt('[_1]parts',
 5293: 							scalar(@{$parts}).'&nbsp;').')'
 5294: 		 ).
 5295: 		 '</td>';
 5296: 	    $studentTable.='<td valign="top">';
 5297: 	    my %form = ('CODE' => $env{'form.CODE'},);
 5298: 	    if ($env{'form.vProb'} eq 'yes' ) {
 5299: 		$studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
 5300: 					     undef,'both',\%form);
 5301: 	    } else {
 5302: 		my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'},%form);
 5303: 		$companswer =~ s|<form(.*?)>||g;
 5304: 		$companswer =~ s|</form>||g;
 5305: #		while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a>
 5306: #		    $companswer =~ s/$1/ /ms;
 5307: #		    $request->print('match='.$1."<br />\n");
 5308: #		}
 5309: #		$companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;
 5310: 		$studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br />&nbsp;<b>'.&mt('Correct answer').':</b><br />'.$companswer;
 5311: 	    }
 5312: 
 5313: 	    my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
 5314: 
 5315: 	    if ($env{'form.lastSub'} eq 'datesub') {
 5316: 		if ($record{'version'} eq '') {
 5317: 		    $studentTable.='<br />&nbsp;<span class="LC_warning">'.&mt('No recorded submission for this problem.').'</span><br />';
 5318: 		} else {
 5319: 		    my %responseType = ();
 5320: 		    foreach my $partid (@{$parts}) {
 5321: 			my @responseIds =$curRes->responseIds($partid);
 5322: 			my @responseType =$curRes->responseType($partid);
 5323: 			my %responseIds;
 5324: 			for (my $i=0;$i<=$#responseIds;$i++) {
 5325: 			    $responseIds{$responseIds[$i]}=$responseType[$i];
 5326: 			}
 5327: 			$responseType{$partid} = \%responseIds;
 5328: 		    }
 5329: 		    $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom);
 5330: 
 5331: 		}
 5332: 	    } elsif ($env{'form.lastSub'} eq 'all') {
 5333: 		my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
 5334:                 my $identifier = (&canmodify($usec)? $prob : '');
 5335: 		$studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom,
 5336: 									$env{'request.course.id'},
 5337: 									'','.submission',undef,
 5338:                                                                         $usec,$identifier);
 5339:  
 5340: 	    }
 5341: 	    if (&canmodify($usec)) {
 5342:             $studentTable.=&gradeBox_start();
 5343: 		foreach my $partid (@{$parts}) {
 5344: 		    $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record);
 5345: 		    $studentTable.='<input type="hidden" name="q_'.$question.'" value="'.$partid.'" />'."\n";
 5346: 		    $question++;
 5347: 		}
 5348:             $studentTable.=&gradeBox_end();
 5349: 		$prob++;
 5350: 	    }
 5351: 	    $studentTable.='</td></tr>';
 5352: 
 5353: 	}
 5354:         $curRes = $iterator->next();
 5355:     }
 5356: 
 5357:     $studentTable.=
 5358:         '</table>'."\n".
 5359:         '<input type="button" value="'.&mt('Save').'" '.
 5360:         'onclick="javascript:checkSubmitPage(this.form,'.$question.');" />'.
 5361:         '</form>'."\n";
 5362:     $request->print($studentTable);
 5363: 
 5364:     return '';
 5365: }
 5366: 
 5367: sub displaySubByDates {
 5368:     my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;
 5369:     my $isCODE=0;
 5370:     my $isTask = ($symb =~/\.task$/);
 5371:     if (exists($record->{'resource.CODE'})) { $isCODE=1; }
 5372:     my $studentTable=&Apache::loncommon::start_data_table().
 5373: 	&Apache::loncommon::start_data_table_header_row().
 5374: 	'<th>'.&mt('Date/Time').'</th>'.
 5375: 	($isCODE?'<th>'.&mt('CODE').'</th>':'').
 5376:         ($isTask?'<th>'.&mt('Version').'</th>':'').
 5377: 	'<th>'.&mt('Submission').'</th>'.
 5378: 	'<th>'.&mt('Status').'</th>'.
 5379: 	&Apache::loncommon::end_data_table_header_row();
 5380:     my ($version);
 5381:     my %mark;
 5382:     my %orders;
 5383:     $mark{'correct_by_student'} = $checkIcon;
 5384:     if (!exists($$record{'1:timestamp'})) {
 5385: 	return '<br />&nbsp;<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br />';
 5386:     }
 5387: 
 5388:     my $interaction;
 5389:     my $no_increment = 1;
 5390:     my (%lastrndseed,%lasttype);
 5391:     for ($version=1;$version<=$$record{'version'};$version++) {
 5392: 	my $timestamp = 
 5393: 	    &Apache::lonlocal::locallocaltime($$record{$version.':timestamp'});
 5394: 	if (exists($$record{$version.':resource.0.version'})) {
 5395: 	    $interaction = $$record{$version.':resource.0.version'};
 5396: 	}
 5397:         if ($isTask && $env{'form.previousversion'}) {
 5398:             next unless ($interaction == $env{'form.previousversion'});
 5399:         }
 5400: 	my $where = ($isTask ? "$version:resource.$interaction"
 5401: 		             : "$version:resource");
 5402: 	$studentTable.=&Apache::loncommon::start_data_table_row().
 5403: 	    '<td>'.$timestamp.'</td>';
 5404: 	if ($isCODE) {
 5405: 	    $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';
 5406: 	}
 5407:         if ($isTask) {
 5408:             $studentTable.='<td>'.$interaction.'</td>';
 5409:         }
 5410: 	my @versionKeys = split(/\:/,$$record{$version.':keys'});
 5411: 	my @displaySub = ();
 5412: 	foreach my $partid (@{$parts}) {
 5413:             my ($hidden,$type);
 5414:             $type = $$record{$version.':resource.'.$partid.'.type'};
 5415:             if (($type eq 'anonsurvey') || ($type eq 'anonsurveycred')) {
 5416:                 $hidden = 1;
 5417:             }
 5418: 	    my @matchKey;
 5419:             if ($isTask) {
 5420:                 @matchKey = sort(grep(/^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys));
 5421:             } else {
 5422: 		@matchKey = sort(grep(/^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys));
 5423:             }
 5424: #	    next if ($$record{"$version:resource.$partid.solved"} eq '');
 5425: 	    my $display_part=&get_display_part($partid,$symb);
 5426: 	    foreach my $matchKey (@matchKey) {
 5427: 		if (exists($$record{$version.':'.$matchKey}) &&
 5428: 		    $$record{$version.':'.$matchKey} ne '') {
 5429:                     
 5430: 		    my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
 5431: 				               : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
 5432:                     $displaySub[0].='<span class="LC_nobreak">';
 5433:                     $displaySub[0].='<b>'.&mt('Part: [_1]',$display_part).'</b>'
 5434:                                    .' <span class="LC_internal_info">'
 5435:                                    .'('.&mt('Response ID: [_1]',$responseId).')'
 5436:                                    .'</span>'
 5437:                                    .' <b>';
 5438:                     if ($hidden) {
 5439:                         $displaySub[0].= &mt('Anonymous Survey').'</b>';
 5440:                     } else {
 5441:                         my ($trial,$rndseed,$newvariation);
 5442:                         if ($type eq 'randomizetry') {
 5443:                             $trial = $$record{"$where.$partid.tries"};
 5444:                             $rndseed = $$record{"$where.$partid.rndseed"};
 5445:                         }
 5446: 		        if ($$record{"$where.$partid.tries"} eq '') {
 5447: 			    $displaySub[0].=&mt('Trial not counted');
 5448: 		        } else {
 5449: 			    $displaySub[0].=&mt('Trial: [_1]',
 5450: 					    $$record{"$where.$partid.tries"});
 5451:                             if (($rndseed ne '')  && ($lastrndseed{$partid} ne '')) {
 5452:                                 if (($rndseed ne $lastrndseed{$partid}) &&
 5453:                                     (($type eq 'randomizetry') || ($lasttype{$partid} eq 'randomizetry'))) {
 5454:                                     $newvariation = '&nbsp;('.&mt('New variation this try').')';
 5455:                                 }
 5456:                             }
 5457:                             $lastrndseed{$partid} = $rndseed;
 5458:                             $lasttype{$partid} = $type;
 5459: 		        }
 5460: 		        my $responseType=($isTask ? 'Task'
 5461:                                               : $responseType->{$partid}->{$responseId});
 5462: 		        if (!exists($orders{$partid})) { $orders{$partid}={}; }
 5463: 		        if ((!exists($orders{$partid}->{$responseId})) || ($trial)) {
 5464: 			    $orders{$partid}->{$responseId}=
 5465: 			        &get_order($partid,$responseId,$symb,$uname,$udom,
 5466:                                            $no_increment,$type,$trial,$rndseed);
 5467: 		        }
 5468: 		        $displaySub[0].='</b>'.$newvariation.'</span>'; # /nobreak
 5469: 		        $displaySub[0].='&nbsp; '.
 5470: 			    &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom,$type,$trial,$rndseed).'<br />';
 5471:                     }
 5472: 		}
 5473: 	    }
 5474: 	    if (exists($$record{"$where.$partid.checkedin"})) {
 5475: 		$displaySub[1].=&mt('Checked in by [_1] into slot [_2]',
 5476: 				    $$record{"$where.$partid.checkedin"},
 5477: 				    $$record{"$where.$partid.checkedin.slot"}).
 5478: 					'<br />';
 5479: 	    }
 5480: 	    if (exists $$record{"$where.$partid.award"}) {
 5481: 		$displaySub[1].='<b>'.&mt('Part:').'</b>&nbsp;'.$display_part.' &nbsp;'.
 5482: 		    lc($$record{"$where.$partid.award"}).' '.
 5483: 		    $mark{$$record{"$where.$partid.solved"}}.
 5484: 		    '<br />';
 5485: 	    }
 5486: 	    if (exists $$record{"$where.$partid.regrader"}) {
 5487: 		$displaySub[2].=$$record{"$where.$partid.regrader"}.
 5488: 		    ' (<b>'.&mt('Part').':</b> '.$display_part.')';
 5489: 	    } elsif ($$record{"$version:resource.$partid.regrader"} =~ /\S/) {
 5490: 		$displaySub[2].=
 5491: 		    $$record{"$version:resource.$partid.regrader"}.
 5492: 		    ' (<b>'.&mt('Part').':</b> '.$display_part.')';
 5493: 	    }
 5494: 	}
 5495: 	# needed because old essay regrader has not parts info
 5496: 	if (exists $$record{"$version:resource.regrader"}) {
 5497: 	    $displaySub[2].=$$record{"$version:resource.regrader"};
 5498: 	}
 5499: 	$studentTable.='<td>'.$displaySub[0].'&nbsp;</td><td>'.$displaySub[1];
 5500: 	if ($displaySub[2]) {
 5501: 	    $studentTable.=&mt('Manually graded by [_1]',$displaySub[2]);
 5502: 	}
 5503: 	$studentTable.='&nbsp;</td>'.
 5504: 	    &Apache::loncommon::end_data_table_row();
 5505:     }
 5506:     $studentTable.=&Apache::loncommon::end_data_table();
 5507:     return $studentTable;
 5508: }
 5509: 
 5510: sub updateGradeByPage {
 5511:     my ($request,$symb) = @_;
 5512: 
 5513:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
 5514:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
 5515:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
 5516:     my $pageTitle = $env{'form.page'};
 5517:     my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
 5518:     my ($uname,$udom) = split(/:/,$env{'form.student'});
 5519:     my $usec=$classlist->{$env{'form.student'}}[5];
 5520:     if (!&canmodify($usec)) {
 5521: 	$request->print('<span class="LC_warning">'.&mt('Unable to modify requested student ([_1])',$env{'form.student'}).'</span>');
 5522: 	return;
 5523:     }
 5524:     my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
 5525:     $result.='<h3>&nbsp;'.&mt('Student: ').&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
 5526: 	'</h3>'."\n";
 5527: 
 5528:     $request->print($result);
 5529: 
 5530: 
 5531:     my $navmap = Apache::lonnavmaps::navmap->new();
 5532:     unless (ref($navmap)) {
 5533:         $request->print(&navmap_errormsg());
 5534:         return;
 5535:     }
 5536:     my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});
 5537:     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
 5538:     if (!$map) {
 5539: 	$request->print('<span class="LC_warning">'.&mt('Unable to grade requested sequence ([_1]).',$resUrl).'</span>');
 5540: 	return; 
 5541:     }
 5542:     my $iterator = $navmap->getIterator($map->map_start(),
 5543: 					$map->map_finish());
 5544: 
 5545:     my $studentTable=
 5546: 	&Apache::loncommon::start_data_table().
 5547: 	&Apache::loncommon::start_data_table_header_row().
 5548: 	'<th align="center">&nbsp;'.&mt('Prob.').'&nbsp;</th>'.
 5549: 	'<th>&nbsp;'.&mt('Title').'&nbsp;</th>'.
 5550: 	'<th>&nbsp;'.&mt('Previous Score').'&nbsp;</th>'.
 5551: 	'<th>&nbsp;'.&mt('New Score').'&nbsp;</th>'.
 5552: 	&Apache::loncommon::end_data_table_header_row();
 5553: 
 5554:     $iterator->next(); # skip the first BEGIN_MAP
 5555:     my $curRes = $iterator->next(); # for "current resource"
 5556:     my ($depth,$question,$prob,$changeflag,$hideflag)= (1,1,1,0,0);
 5557:     while ($depth > 0) {
 5558:         if($curRes == $iterator->BEGIN_MAP) { $depth++; }
 5559:         if($curRes == $iterator->END_MAP) { $depth--; }
 5560: 
 5561:         if (ref($curRes) && $curRes->is_problem()) {
 5562: 	    my $parts = $curRes->parts();
 5563:             my $title = $curRes->compTitle();
 5564: 	    my $symbx = $curRes->symb();
 5565: 	    $studentTable.=
 5566: 		&Apache::loncommon::start_data_table_row().
 5567: 		'<td align="center" valign="top" >'.$prob.
 5568: 		(scalar(@{$parts}) == 1 ? '' 
 5569:                                         : '<br />('.&mt('[quant,_1,part]',scalar(@{$parts}))
 5570: 		.')').'</td>';
 5571: 	    $studentTable.='<td valign="top">&nbsp;<b>'.$title.'</b>&nbsp;</td>';
 5572: 
 5573: 	    my %newrecord=();
 5574: 	    my @displayPts=();
 5575:             my %aggregate = ();
 5576:             my $aggregateflag = 0;
 5577:             if ($env{'form.HIDE'.$prob}) {
 5578:                 my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
 5579:                 my ($version,$parts) = split(/:/,$env{'form.HIDE'.$prob},2);
 5580:                 my $numchgs = &makehidden($version,$parts,\%record,$symbx,$udom,$uname,1);
 5581:                 $hideflag += $numchgs;
 5582:             }
 5583: 	    foreach my $partid (@{$parts}) {
 5584: 		my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
 5585: 		my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
 5586: 
 5587: 		my $wgt = $env{'form.WGT'.$question.'_'.$partid} != 0 ? 
 5588: 		    $env{'form.WGT'.$question.'_'.$partid} : 1;
 5589: 		my $partial = $newpts/$wgt;
 5590: 		my $score;
 5591: 		if ($partial > 0) {
 5592: 		    $score = 'correct_by_override';
 5593: 		} elsif ($newpts ne '') { #empty is taken as 0
 5594: 		    $score = 'incorrect_by_override';
 5595: 		}
 5596: 		my $dropMenu = $env{'form.GD_SEL'.$question.'_'.$partid};
 5597: 		if ($dropMenu eq 'excused') {
 5598: 		    $partial = '';
 5599: 		    $score = 'excused';
 5600: 		} elsif ($dropMenu eq 'reset status'
 5601: 			 && $env{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists
 5602: 		    $newrecord{'resource.'.$partid.'.tries'} = 0;
 5603: 		    $newrecord{'resource.'.$partid.'.solved'} = '';
 5604: 		    $newrecord{'resource.'.$partid.'.award'} = '';
 5605: 		    $newrecord{'resource.'.$partid.'.awarded'} = 0;
 5606: 		    $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}";
 5607: 		    $changeflag++;
 5608: 		    $newpts = '';
 5609:                     
 5610:                     my $aggtries =  $env{'form.aggtries'.$question.'_'.$partid};
 5611:                     my $totaltries = $env{'form.totaltries'.$question.'_'.$partid};
 5612:                     my $solvedstatus = $env{'form.solved'.$question.'_'.$partid};
 5613:                     if ($aggtries > 0) {
 5614:                         &decrement_aggs($symbx,$partid,\%aggregate,$aggtries,$totaltries,$solvedstatus);
 5615:                         $aggregateflag = 1;
 5616:                     }
 5617: 		}
 5618: 		my $display_part=&get_display_part($partid,$curRes->symb());
 5619: 		my $oldstatus = $env{'form.solved'.$question.'_'.$partid};
 5620: 		$displayPts[0].='&nbsp;<b>'.&mt('Part').':</b> '.$display_part.' = '.
 5621: 		    (($oldstatus eq 'excused') ? 'excused' : $oldpts).
 5622: 		    '&nbsp;<br />';
 5623: 		$displayPts[1].='&nbsp;<b>'.&mt('Part').':</b> '.$display_part.' = '.
 5624: 		     (($score eq 'excused') ? 'excused' : $newpts).
 5625: 		    '&nbsp;<br />';
 5626: 		$question++;
 5627: 		next if ($dropMenu eq 'reset status' || ($newpts eq $oldpts && $score ne 'excused'));
 5628: 
 5629: 		$newrecord{'resource.'.$partid.'.awarded'}  = $partial if $partial ne '';
 5630: 		$newrecord{'resource.'.$partid.'.solved'}   = $score if $score ne '';
 5631: 		$newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}"
 5632: 		    if (scalar(keys(%newrecord)) > 0);
 5633: 
 5634: 		$changeflag++;
 5635: 	    }
 5636: 	    if (scalar(keys(%newrecord)) > 0) {
 5637: 		my %record = 
 5638: 		    &Apache::lonnet::restore($symbx,$env{'request.course.id'},
 5639: 					     $udom,$uname);
 5640: 
 5641: 		if (&Apache::lonnet::validCODE($env{'form.CODE'})) {
 5642: 		    $newrecord{'resource.CODE'} = $env{'form.CODE'};
 5643: 		} elsif (&Apache::lonnet::validCODE($record{'resource.CODE'})) {
 5644: 		    $newrecord{'resource.CODE'} = '';
 5645: 		}
 5646: 		&Apache::lonnet::cstore(\%newrecord,$symbx,$env{'request.course.id'},
 5647: 					$udom,$uname);
 5648: 		%record = &Apache::lonnet::restore($symbx,
 5649: 						   $env{'request.course.id'},
 5650: 						   $udom,$uname);
 5651: 		&check_and_remove_from_queue($parts,\%record,undef,$symbx,
 5652: 					     $cdom,$cnum,$udom,$uname);
 5653: 	    }
 5654: 	    
 5655:             if ($aggregateflag) {
 5656:                 &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
 5657:                       $env{'course.'.$env{'request.course.id'}.'.domain'},
 5658:                       $env{'course.'.$env{'request.course.id'}.'.num'});
 5659:             }
 5660: 
 5661: 	    $studentTable.='<td valign="top">'.$displayPts[0].'</td>'.
 5662: 		'<td valign="top">'.$displayPts[1].'</td>'.
 5663: 		&Apache::loncommon::end_data_table_row();
 5664: 
 5665: 	    $prob++;
 5666: 	}
 5667:         $curRes = $iterator->next();
 5668:     }
 5669: 
 5670:     $studentTable.=&Apache::loncommon::end_data_table();
 5671:     my $grademsg=($changeflag == 0 ? &mt('No score was changed or updated.') :
 5672: 		  &mt('The scores were changed for [quant,_1,problem].',
 5673: 		  $changeflag).'<br />');
 5674:     my $hidemsg=($hideflag == 0 ? '' :
 5675:                  &mt('Submissions were marked "hidden" for [quant,_1,transaction].',
 5676:                      $hideflag).'<br />');
 5677:     $request->print($hidemsg.$grademsg.$studentTable);
 5678: 
 5679:     return '';
 5680: }
 5681: 
 5682: #-------- end of section for handling grading by page/sequence ---------
 5683: #
 5684: #-------------------------------------------------------------------
 5685: 
 5686: #-------------------- Bubblesheet (Scantron) Grading -------------------
 5687: #
 5688: #------ start of section for handling grading by page/sequence ---------
 5689: 
 5690: =pod
 5691: 
 5692: =head1 Bubble sheet grading routines
 5693: 
 5694:   For this documentation:
 5695: 
 5696:    'scanline' refers to the full line of characters
 5697:    from the file that we are parsing that represents one entire sheet
 5698: 
 5699:    'bubble line' refers to the data
 5700:    representing the line of bubbles that are on the physical bubblesheet
 5701: 
 5702: 
 5703: The overall process is that a scanned in bubblesheet data is uploaded
 5704: into a course. When a user wants to grade, they select a
 5705: sequence/folder of resources, a file of bubblesheet info, and pick
 5706: one of the predefined configurations for what each scanline looks
 5707: like.
 5708: 
 5709: Next each scanline is checked for any errors of either 'missing
 5710: bubbles' (it's an error because it may have been mis-scanned
 5711: because too light bubbling), 'double bubble' (each bubble line should
 5712: have no more than one letter picked), invalid or duplicated CODE,
 5713: invalid student/employee ID
 5714: 
 5715: If the CODE option is used that determines the randomization of the
 5716: homework problems, either way the student/employee ID is looked up into a
 5717: username:domain.
 5718: 
 5719: During the validation phase the instructor can choose to skip scanlines. 
 5720: 
 5721: After the validation phase, there are now 3 bubblesheet files
 5722: 
 5723:   scantron_original_filename (unmodified original file)
 5724:   scantron_corrected_filename (file where the corrected information has replaced the original information)
 5725:   scantron_skipped_filename (contains the exact text of scanlines that where skipped)
 5726: 
 5727: Also there is a separate hash nohist_scantrondata that contains extra
 5728: correction information that isn't representable in the bubblesheet
 5729: file (see &scantron_getfile() for more information)
 5730: 
 5731: After all scanlines are either valid, marked as valid or skipped, then
 5732: foreach line foreach problem in the picked sequence, an ssi request is
 5733: made that simulates a user submitting their selected letter(s) against
 5734: the homework problem.
 5735: 
 5736: =over 4
 5737: 
 5738: 
 5739: 
 5740: =item defaultFormData
 5741: 
 5742:   Returns html hidden inputs used to hold context/default values.
 5743: 
 5744:  Arguments:
 5745:   $symb - $symb of the current resource 
 5746: 
 5747: =cut
 5748: 
 5749: sub defaultFormData {
 5750:     my ($symb)=@_;
 5751:     return '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />';
 5752: }
 5753: 
 5754: 
 5755: =pod 
 5756: 
 5757: =item getSequenceDropDown
 5758: 
 5759:    Return html dropdown of possible sequences to grade
 5760:  
 5761:  Arguments:
 5762:    $symb - $symb of the current resource
 5763:    $map_error - ref to scalar which will container error if
 5764:                 $navmap object is unavailable in &getSymbMap().
 5765: 
 5766: =cut
 5767: 
 5768: sub getSequenceDropDown {
 5769:     my ($symb,$map_error)=@_;
 5770:     my $result='<select name="selectpage">'."\n";
 5771:     my ($titles,$symbx) = &getSymbMap($map_error);
 5772:     if (ref($map_error)) {
 5773:         return if ($$map_error);
 5774:     }
 5775:     my ($curpage)=&Apache::lonnet::decode_symb($symb); 
 5776:     my $ctr=0;
 5777:     foreach (@$titles) {
 5778: 	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
 5779: 	$result.='<option value="'.$$symbx{$_}.'" '.
 5780: 	    ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
 5781: 	    '>'.$showtitle.'</option>'."\n";
 5782: 	$ctr++;
 5783:     }
 5784:     $result.= '</select>';
 5785:     return $result;
 5786: }
 5787: 
 5788: my %bubble_lines_per_response;     # no. bubble lines for each response.
 5789:                                    # key is zero-based index - 0, 1, 2 ...
 5790: 
 5791: my %first_bubble_line;             # First bubble line no. for each bubble.
 5792: 
 5793: my %subdivided_bubble_lines;       # no. bubble lines for optionresponse, 
 5794:                                    # matchresponse or rankresponse, where 
 5795:                                    # an individual response can have multiple 
 5796:                                    # lines
 5797: 
 5798: my %responsetype_per_response;     # responsetype for each response
 5799: 
 5800: my %masterseq_id_responsenum;      # src_id (e.g., 12.3_0.11 etc.) for each
 5801:                                    # numbered response. Needed when randomorder
 5802:                                    # or randompick are in use. Key is ID, value 
 5803:                                    # is response number.
 5804: 
 5805: # Save and restore the bubble lines array to the form env.
 5806: 
 5807: 
 5808: sub save_bubble_lines {
 5809:     foreach my $line (keys(%bubble_lines_per_response)) {
 5810: 	$env{"form.scantron.bubblelines.$line"}  = $bubble_lines_per_response{$line};
 5811: 	$env{"form.scantron.first_bubble_line.$line"} =
 5812: 	    $first_bubble_line{$line};
 5813:         $env{"form.scantron.sub_bubblelines.$line"} = 
 5814:             $subdivided_bubble_lines{$line};
 5815:         $env{"form.scantron.responsetype.$line"} =
 5816:             $responsetype_per_response{$line};
 5817:     }
 5818:     foreach my $resid (keys(%masterseq_id_responsenum)) {
 5819:         my $line = $masterseq_id_responsenum{$resid};
 5820:         $env{"form.scantron.residpart.$line"} = $resid;
 5821:     }
 5822: }
 5823: 
 5824: 
 5825: sub restore_bubble_lines {
 5826:     my $line = 0;
 5827:     %bubble_lines_per_response = ();
 5828:     %masterseq_id_responsenum = ();
 5829:     while ($env{"form.scantron.bubblelines.$line"}) {
 5830: 	my $value = $env{"form.scantron.bubblelines.$line"};
 5831: 	$bubble_lines_per_response{$line} = $value;
 5832: 	$first_bubble_line{$line}  =
 5833: 	    $env{"form.scantron.first_bubble_line.$line"};
 5834:         $subdivided_bubble_lines{$line} =
 5835:             $env{"form.scantron.sub_bubblelines.$line"};
 5836:         $responsetype_per_response{$line} =
 5837:             $env{"form.scantron.responsetype.$line"};
 5838:         my $id = $env{"form.scantron.residpart.$line"};
 5839:         $masterseq_id_responsenum{$id} = $line;
 5840: 	$line++;
 5841:     }
 5842: }
 5843: 
 5844: =pod 
 5845: 
 5846: =item scantron_filenames
 5847: 
 5848:    Returns a list of the scantron files in the current course 
 5849: 
 5850: =cut
 5851: 
 5852: sub scantron_filenames {
 5853:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 5854:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 5855:     my $getpropath = 1;
 5856:     my ($dirlist,$listerror) = &Apache::lonnet::dirlist('userfiles',$cdom,
 5857:                                                         $cname,$getpropath);
 5858:     my @possiblenames;
 5859:     if (ref($dirlist) eq 'ARRAY') {
 5860:         foreach my $filename (sort(@{$dirlist})) {
 5861: 	    ($filename)=split(/&/,$filename);
 5862: 	    if ($filename!~/^scantron_orig_/) { next ; }
 5863: 	    $filename=~s/^scantron_orig_//;
 5864: 	    push(@possiblenames,$filename);
 5865:         }
 5866:     }
 5867:     return @possiblenames;
 5868: }
 5869: 
 5870: =pod 
 5871: 
 5872: =item scantron_uploads
 5873: 
 5874:    Returns  html drop-down list of scantron files in current course.
 5875: 
 5876:  Arguments:
 5877:    $file2grade - filename to set as selected in the dropdown
 5878: 
 5879: =cut
 5880: 
 5881: sub scantron_uploads {
 5882:     my ($file2grade) = @_;
 5883:     my $result=	'<select name="scantron_selectfile">';
 5884:     $result.="<option></option>";
 5885:     foreach my $filename (sort(&scantron_filenames())) {
 5886: 	$result.="<option".($filename eq $file2grade ? ' selected="selected"':'').">$filename</option>\n";
 5887:     }
 5888:     $result.="</select>";
 5889:     return $result;
 5890: }
 5891: 
 5892: =pod 
 5893: 
 5894: =item scantron_scantab
 5895: 
 5896:   Returns html drop down of the scantron formats in the scantronformat.tab
 5897:   file.
 5898: 
 5899: =cut
 5900: 
 5901: sub scantron_scantab {
 5902:     my $result='<select name="scantron_format">'."\n";
 5903:     $result.='<option></option>'."\n";
 5904:     my @lines = &Apache::lonnet::get_scantronformat_file();
 5905:     if (@lines > 0) {
 5906:         foreach my $line (@lines) {
 5907:             next if (($line =~ /^\#/) || ($line eq ''));
 5908: 	    my ($name,$descrip)=split(/:/,$line);
 5909: 	    $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";
 5910:         }
 5911:     }
 5912:     $result.='</select>'."\n";
 5913:     return $result;
 5914: }
 5915: 
 5916: =pod 
 5917: 
 5918: =item scantron_CODElist
 5919: 
 5920:   Returns html drop down of the saved CODE lists from current course,
 5921:   generated from earlier printings.
 5922: 
 5923: =cut
 5924: 
 5925: sub scantron_CODElist {
 5926:     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
 5927:     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
 5928:     my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum);
 5929:     my $namechoice='<option></option>';
 5930:     foreach my $name (sort {uc($a) cmp uc($b)} @names) {
 5931: 	if ($name =~ /^error: 2 /) { next; }
 5932: 	if ($name =~ /^type\0/) { next; }
 5933: 	$namechoice.='<option value="'.$name.'">'.$name.'</option>';
 5934:     }
 5935:     $namechoice='<select name="scantron_CODElist">'.$namechoice.'</select>';
 5936:     return $namechoice;
 5937: }
 5938: 
 5939: =pod 
 5940: 
 5941: =item scantron_CODEunique
 5942: 
 5943:   Returns the html for "Each CODE to be used once" radio.
 5944: 
 5945: =cut
 5946: 
 5947: sub scantron_CODEunique {
 5948:     my $result='<span class="LC_nobreak">
 5949:                  <label><input type="radio" name="scantron_CODEunique"
 5950:                         value="yes" checked="checked" />'.&mt('Yes').' </label>
 5951:                 </span>
 5952:                 <span class="LC_nobreak">
 5953:                  <label><input type="radio" name="scantron_CODEunique"
 5954:                         value="no" />'.&mt('No').' </label>
 5955:                 </span>';
 5956:     return $result;
 5957: }
 5958: 
 5959: =pod 
 5960: 
 5961: =item scantron_selectphase
 5962: 
 5963:   Generates the initial screen to start the bubblesheet process.
 5964:   Allows for - starting a grading run.
 5965:              - downloading existing scan data (original, corrected
 5966:                                                 or skipped info)
 5967: 
 5968:              - uploading new scan data
 5969: 
 5970:  Arguments:
 5971:   $r          - The Apache request object
 5972:   $file2grade - name of the file that contain the scanned data to score
 5973: 
 5974: =cut
 5975: 
 5976: sub scantron_selectphase {
 5977:     my ($r,$file2grade,$symb) = @_;
 5978:     if (!$symb) {return '';}
 5979:     my $map_error;
 5980:     my $sequence_selector=&getSequenceDropDown($symb,\$map_error);
 5981:     if ($map_error) {
 5982:         $r->print('<br />'.&navmap_errormsg().'<br />');
 5983:         return;
 5984:     }
 5985:     my $default_form_data=&defaultFormData($symb);
 5986:     my $file_selector=&scantron_uploads($file2grade);
 5987:     my $format_selector=&scantron_scantab();
 5988:     my $CODE_selector=&scantron_CODElist();
 5989:     my $CODE_unique=&scantron_CODEunique();
 5990:     my $result;
 5991: 
 5992:     $ssi_error = 0;
 5993: 
 5994:     if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
 5995:         &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
 5996: 
 5997:         # Chunk of form to prompt for a scantron file upload.
 5998: 
 5999:         $r->print('
 6000:     <br />');
 6001:         my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
 6002:         my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};
 6003:         my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
 6004:         &js_escape(\$alertmsg);
 6005:         my ($formatoptions,$formattitle,$formatjs) = &scantron_upload_dataformat($cdom);
 6006:         $r->print(&Apache::lonhtmlcommon::scripttag('
 6007:     function checkUpload(formname) {
 6008:         if (formname.upfile.value == "") {
 6009:             alert("'.$alertmsg.'");
 6010:             return false;
 6011:         }
 6012:         formname.submit();
 6013:     }'."\n".$formatjs));
 6014:         $r->print('
 6015:               <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
 6016:                 '.$default_form_data.'
 6017:                 <input name="courseid" type="hidden" value="'.$cnum.'" />
 6018:                 <input name="domainid" type="hidden" value="'.$cdom.'" />
 6019:                 <input name="command" value="scantronupload_save" type="hidden" />
 6020:               '.&Apache::loncommon::start_data_table('LC_scantron_action').'
 6021:               '.&Apache::loncommon::start_data_table_header_row().'
 6022:                 <th>
 6023:                 &nbsp;'.&mt('Specify a bubblesheet data file to upload.').'
 6024:                 </th>
 6025:               '.&Apache::loncommon::end_data_table_header_row().'
 6026:               '.&Apache::loncommon::start_data_table_row().'
 6027:             <td>
 6028:                 '.&mt('File to upload: [_1]','<input type="file" name="upfile" size="50" />').'<br />'."\n");
 6029:         if ($formatoptions) {
 6030:             $r->print('</td>
 6031:                  '.&Apache::loncommon::end_data_table_row().'
 6032:                  '.&Apache::loncommon::start_data_table_row().'
 6033:                  <td>'.$formattitle.('&nbsp;'x2).$formatoptions.'
 6034:                  </td>
 6035:                  '.&Apache::loncommon::end_data_table_row().'
 6036:                  '.&Apache::loncommon::start_data_table_row().'
 6037:                  <td>'
 6038:             );
 6039:         } else {
 6040:             $r->print(' <br />');
 6041:         }
 6042:         $r->print('<input type="button" onclick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
 6043:               </td>
 6044:              '.&Apache::loncommon::end_data_table_row().'
 6045:              '.&Apache::loncommon::end_data_table().'
 6046:              </form>'
 6047:         );
 6048: 
 6049:     }
 6050: 
 6051:     # Chunk of form to prompt for a file to grade and how:
 6052: 
 6053:     $result.= '
 6054:     <br />
 6055:     <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
 6056:     <input type="hidden" name="command" value="scantron_warning" />
 6057:     '.$default_form_data.'
 6058:     '.&Apache::loncommon::start_data_table('LC_scantron_action').'
 6059:        '.&Apache::loncommon::start_data_table_header_row().'
 6060:             <th colspan="2">
 6061:               &nbsp;'.&mt('Specify file and which Folder/Sequence to grade').'
 6062:             </th>
 6063:        '.&Apache::loncommon::end_data_table_header_row().'
 6064:        '.&Apache::loncommon::start_data_table_row().'
 6065:             <td> '.&mt('Sequence to grade:').' </td><td> '.$sequence_selector.' </td>
 6066:        '.&Apache::loncommon::end_data_table_row().'
 6067:        '.&Apache::loncommon::start_data_table_row().'
 6068:             <td> '.&mt('Filename of bubblesheet data file:').' </td><td> '.$file_selector.' </td>
 6069:        '.&Apache::loncommon::end_data_table_row().'
 6070:        '.&Apache::loncommon::start_data_table_row().'
 6071:             <td> '.&mt('Format of bubblesheet data file:').' </td><td> '.$format_selector.' </td>
 6072:        '.&Apache::loncommon::end_data_table_row().'
 6073:        '.&Apache::loncommon::start_data_table_row().'
 6074:             <td> '.&mt('Saved CODEs to validate against:').' </td><td> '.$CODE_selector.' </td>
 6075:        '.&Apache::loncommon::end_data_table_row().'
 6076:        '.&Apache::loncommon::start_data_table_row().'
 6077:             <td> '.&mt('Each CODE is only to be used once:').'</td><td> '.$CODE_unique.' </td>
 6078:        '.&Apache::loncommon::end_data_table_row().'
 6079:        '.&Apache::loncommon::start_data_table_row().'
 6080: 	    <td> '.&mt('Options:').' </td>
 6081:             <td>
 6082: 	       <label><input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> '.&mt('Do only previously skipped records').'</label> <br />
 6083:                <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> '.&mt('Remove all existing corrections').'</label> <br />
 6084:                <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> '.&mt('Skip hidden resources when grading').'</label>
 6085: 	    </td>
 6086:        '.&Apache::loncommon::end_data_table_row().'
 6087:        '.&Apache::loncommon::start_data_table_row().'
 6088:             <td colspan="2">
 6089:               <input type="submit" value="'.&mt('Grading: Validate Bubblesheet Records').'" />
 6090:             </td>
 6091:        '.&Apache::loncommon::end_data_table_row().'
 6092:     '.&Apache::loncommon::end_data_table().'
 6093:     </form>
 6094: ';
 6095:    
 6096:     $r->print($result);
 6097: 
 6098:     # Chunk of the form that prompts to view a scoring office file,
 6099:     # corrected file, skipped records in a file.
 6100: 
 6101:     $r->print('
 6102:    <br />
 6103:    <form action="/adm/grades" name="scantron_download">
 6104:      '.$default_form_data.'
 6105:      <input type="hidden" name="command" value="scantron_download" />
 6106:      '.&Apache::loncommon::start_data_table('LC_scantron_action').'
 6107:        '.&Apache::loncommon::start_data_table_header_row().'
 6108:               <th>
 6109:                 &nbsp;'.&mt('Download a scoring office file').'
 6110:               </th>
 6111:        '.&Apache::loncommon::end_data_table_header_row().'
 6112:        '.&Apache::loncommon::start_data_table_row().'
 6113:               <td> '.&mt('Filename of scoring office file: [_1]',$file_selector).' 
 6114:                 <br />
 6115:                 <input type="submit" value="'.&mt('Download: Show List of Associated Files').'" />
 6116:        '.&Apache::loncommon::end_data_table_row().'
 6117:      '.&Apache::loncommon::end_data_table().'
 6118:    </form>
 6119:    <br />
 6120: ');
 6121: 
 6122:     &Apache::lonpickcode::code_list($r,2);
 6123: 
 6124:     $r->print('<br /><form method="post" name="checkscantron" action="">'.
 6125:              $default_form_data."\n".
 6126:              &Apache::loncommon::start_data_table('LC_scantron_action')."\n".
 6127:              &Apache::loncommon::start_data_table_header_row()."\n".
 6128:              '<th colspan="2">
 6129:               &nbsp;'.&mt('Review bubblesheet data and submissions for a previously graded folder/sequence')."\n".
 6130:              '</th>'."\n".
 6131:               &Apache::loncommon::end_data_table_header_row()."\n".
 6132:               &Apache::loncommon::start_data_table_row()."\n".
 6133:               '<td> '.&mt('Graded folder/sequence:').' </td>'."\n".
 6134:               '<td> '.$sequence_selector.' </td>'.
 6135:               &Apache::loncommon::end_data_table_row()."\n".
 6136:               &Apache::loncommon::start_data_table_row()."\n".
 6137:               '<td> '.&mt('Filename of scoring office file:').' </td>'."\n".
 6138:               '<td> '.$file_selector.' </td>'."\n".
 6139:               &Apache::loncommon::end_data_table_row()."\n".
 6140:               &Apache::loncommon::start_data_table_row()."\n".
 6141:               '<td> '.&mt('Format of data file:').' </td>'."\n".
 6142:               '<td> '.$format_selector.' </td>'."\n".
 6143:               &Apache::loncommon::end_data_table_row()."\n".
 6144:               &Apache::loncommon::start_data_table_row()."\n".
 6145:               '<td> '.&mt('Options').' </td>'."\n".
 6146:               '<td> <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> '.&mt('Skip hidden resources').'</label></td>'.
 6147:               &Apache::loncommon::end_data_table_row()."\n".
 6148:               &Apache::loncommon::start_data_table_row()."\n".
 6149:               '<td colspan="2">'."\n".
 6150:               '<input type="hidden" name="command" value="checksubmissions" />'."\n".
 6151:               '<input type="submit" value="'.&mt('Review Bubblesheet Data and Submission Records').'" />'."\n".
 6152:               '</td>'."\n".
 6153:               &Apache::loncommon::end_data_table_row()."\n".
 6154:               &Apache::loncommon::end_data_table()."\n".
 6155:               '</form><br />');
 6156:     return;
 6157: }
 6158: 
 6159: =pod 
 6160: 
 6161: =item username_to_idmap
 6162: 
 6163:     creates a hash keyed by student/employee ID with values of the corresponding
 6164:     student username:domain.
 6165: 
 6166:   Arguments:
 6167: 
 6168:     $classlist - reference to the class list hash. This is a hash
 6169:                  keyed by student name:domain  whose elements are references
 6170:                  to arrays containing various chunks of information
 6171:                  about the student. (See loncoursedata for more info).
 6172: 
 6173:   Returns
 6174:     %idmap - the constructed hash
 6175: 
 6176: =cut
 6177: 
 6178: sub username_to_idmap {
 6179:     my ($classlist)= @_;
 6180:     my %idmap;
 6181:     foreach my $student (keys(%$classlist)) {
 6182:         my $id = $classlist->{$student}->[&Apache::loncoursedata::CL_ID];
 6183:         unless ($id eq '') {
 6184:             if (!exists($idmap{$id})) {
 6185:                 $idmap{$id} = $student;
 6186:             } else {
 6187:                 my $status = $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS];
 6188:                 if ($status eq 'Active') {
 6189:                     $idmap{$id} = $student;
 6190:                 }
 6191:             }
 6192:         }
 6193:     }
 6194:     return %idmap;
 6195: }
 6196: 
 6197: =pod
 6198: 
 6199: =item scantron_fixup_scanline
 6200: 
 6201:    Process a requested correction to a scanline.
 6202: 
 6203:   Arguments:
 6204:     $scantron_config   - hash from &Apache::lonnet::get_scantron_config()
 6205:     $scan_data         - hash of correction information 
 6206:                           (see &scantron_getfile())
 6207:     $line              - existing scanline
 6208:     $whichline         - line number of the passed in scanline
 6209:     $field             - type of change to process 
 6210:                          (either 
 6211:                           'ID'     -> correct the student/employee ID
 6212:                           'CODE'   -> correct the CODE
 6213:                           'answer' -> fixup the submitted answers)
 6214:     
 6215:    $args               - hash of additional info,
 6216:                           - 'ID' 
 6217:                                'newid' -> studentID to use in replacement
 6218:                                           of existing one
 6219:                           - 'CODE' 
 6220:                                'CODE_ignore_dup' - set to true if duplicates
 6221:                                                    should be ignored.
 6222: 	                       'CODE' - is new code or 'use_unfound'
 6223:                                         if the existing unfound code should
 6224:                                         be used as is
 6225:                           - 'answer'
 6226:                                'response' - new answer or 'none' if blank
 6227:                                'question' - the bubble line to change
 6228:                                'questionnum' - the question identifier,
 6229:                                                may include subquestion. 
 6230: 
 6231:   Returns:
 6232:     $line - the modified scanline
 6233: 
 6234:   Side effects: 
 6235:     $scan_data - may be updated
 6236: 
 6237: =cut
 6238: 
 6239: 
 6240: sub scantron_fixup_scanline {
 6241:     my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
 6242:     if ($field eq 'ID') {
 6243: 	if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
 6244: 	    return ($line,1,'New value too large');
 6245: 	}
 6246: 	if (length($args->{'newid'}) < $$scantron_config{'IDlength'}) {
 6247: 	    $args->{'newid'}=sprintf('%-'.$$scantron_config{'IDlength'}.'s',
 6248: 				     $args->{'newid'});
 6249: 	}
 6250: 	substr($line,$$scantron_config{'IDstart'}-1,
 6251: 	       $$scantron_config{'IDlength'})=$args->{'newid'};
 6252: 	if ($args->{'newid'}=~/^\s*$/) {
 6253: 	    &scan_data($scan_data,"$whichline.user",
 6254: 		       $args->{'username'}.':'.$args->{'domain'});
 6255: 	}
 6256:     } elsif ($field eq 'CODE') {
 6257: 	if ($args->{'CODE_ignore_dup'}) {
 6258: 	    &scan_data($scan_data,"$whichline.CODE_ignore_dup",'1');
 6259: 	}
 6260: 	&scan_data($scan_data,"$whichline.useCODE",'1');
 6261: 	if ($args->{'CODE'} ne 'use_unfound') {
 6262: 	    if (length($args->{'CODE'}) > $$scantron_config{'CODElength'}) {
 6263: 		return ($line,1,'New CODE value too large');
 6264: 	    }
 6265: 	    if (length($args->{'CODE'}) < $$scantron_config{'CODElength'}) {
 6266: 		$args->{'CODE'}=sprintf('%-'.$$scantron_config{'CODElength'}.'s',$args->{'CODE'});
 6267: 	    }
 6268: 	    substr($line,$$scantron_config{'CODEstart'}-1,
 6269: 		   $$scantron_config{'CODElength'})=$args->{'CODE'};
 6270: 	}
 6271:     } elsif ($field eq 'answer') {
 6272: 	my $length=$scantron_config->{'Qlength'};
 6273: 	my $off=$scantron_config->{'Qoff'};
 6274: 	my $on=$scantron_config->{'Qon'};
 6275: 	my $answer=${off}x$length;
 6276: 	if ($args->{'response'} eq 'none') {
 6277: 	    &scan_data($scan_data,
 6278: 		       "$whichline.no_bubble.".$args->{'questionnum'},'1');
 6279: 	} else {
 6280: 	    if ($on eq 'letter') {
 6281: 		my @alphabet=('A'..'Z');
 6282: 		$answer=$alphabet[$args->{'response'}];
 6283: 	    } elsif ($on eq 'number') {
 6284: 		$answer=$args->{'response'}+1;
 6285: 		if ($answer == 10) { $answer = '0'; }
 6286: 	    } else {
 6287: 		substr($answer,$args->{'response'},1)=$on;
 6288: 	    }
 6289: 	    &scan_data($scan_data,
 6290: 		       "$whichline.no_bubble.".$args->{'questionnum'},undef,'1');
 6291: 	}
 6292: 	my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};
 6293: 	substr($line,$where-1,$length)=$answer;
 6294:     }
 6295:     return $line;
 6296: }
 6297: 
 6298: =pod
 6299: 
 6300: =item scan_data
 6301: 
 6302:     Edit or look up  an item in the scan_data hash.
 6303: 
 6304:   Arguments:
 6305:     $scan_data  - The hash (see scantron_getfile)
 6306:     $key        - shorthand of the key to edit (actual key is
 6307:                   scantronfilename_key).
 6308:     $data        - New value of the hash entry.
 6309:     $delete      - If true, the entry is removed from the hash.
 6310: 
 6311:   Returns:
 6312:     The new value of the hash table field (undefined if deleted).
 6313: 
 6314: =cut
 6315: 
 6316: 
 6317: sub scan_data {
 6318:     my ($scan_data,$key,$value,$delete)=@_;
 6319:     my $filename=$env{'form.scantron_selectfile'};
 6320:     if (defined($value)) {
 6321: 	$scan_data->{$filename.'_'.$key} = $value;
 6322:     }
 6323:     if ($delete) { delete($scan_data->{$filename.'_'.$key}); }
 6324:     return $scan_data->{$filename.'_'.$key};
 6325: }
 6326: 
 6327: # ----- These first few routines are general use routines.----
 6328: 
 6329: # Return the number of occurences of a pattern in a string.
 6330: 
 6331: sub occurence_count {
 6332:     my ($string, $pattern) = @_;
 6333: 
 6334:     my @matches = ($string =~ /$pattern/g);
 6335: 
 6336:     return scalar(@matches);
 6337: }
 6338: 
 6339: 
 6340: # Take a string known to have digits and convert all the
 6341: # digits into letters in the range J,A..I.
 6342: 
 6343: sub digits_to_letters {
 6344:     my ($input) = @_;
 6345: 
 6346:     my @alphabet = ('J', 'A'..'I');
 6347: 
 6348:     my @input    = split(//, $input);
 6349:     my $output ='';
 6350:     for (my $i = 0; $i < scalar(@input); $i++) {
 6351: 	if ($input[$i] =~ /\d/) {
 6352: 	    $output .= $alphabet[$input[$i]];
 6353: 	} else {
 6354: 	    $output .= $input[$i];
 6355: 	}
 6356:     }
 6357:     return $output;
 6358: }
 6359: 
 6360: =pod 
 6361: 
 6362: =item scantron_parse_scanline
 6363: 
 6364:   Decodes a scanline from the selected scantron file
 6365: 
 6366:  Arguments:
 6367:     line             - The text of the scantron file line to process
 6368:     whichline        - Line number
 6369:     scantron_config  - Hash describing the format of the scantron lines.
 6370:     scan_data        - Hash of extra information about the scanline
 6371:                        (see scantron_getfile for more information)
 6372:     just_header      - True if should not process question answers but only
 6373:                        the stuff to the left of the answers.
 6374:     randomorder      - True if randomorder in use
 6375:     randompick       - True if randompick in use
 6376:     sequence         - Exam folder URL
 6377:     master_seq       - Ref to array containing symbs in exam folder
 6378:     symb_to_resource - Ref to hash of symbs for resources in exam folder
 6379:                        (corresponding values are resource objects)
 6380:     partids_by_symb  - Ref to hash of symb -> array ref of partIDs
 6381:     orderedforcode   - Ref to hash of arrays. keys are CODEs and values
 6382:                        are refs to an array of resource objects, ordered
 6383:                        according to order used for CODE, when randomorder
 6384:                        and or randompick are in use.
 6385:     respnumlookup    - Ref to hash mapping question numbers in bubble lines
 6386:                        for current line to question number used for same question
 6387:                         in "Master Sequence" (as seen by Course Coordinator).
 6388:     startline        - Ref to hash where key is question number (0 is first)
 6389:                        and value is number of first bubble line for current 
 6390:                        student or code-based randompick and/or randomorder.
 6391:     totalref         - Ref of scalar used to score total number of bubble
 6392:                        lines needed for responses in a scan line (used when
 6393:                        randompick in use. 
 6394: 
 6395:  Returns:
 6396:    Hash containing the result of parsing the scanline
 6397: 
 6398:    Keys are all proceeded by the string 'scantron.'
 6399: 
 6400:        CODE    - the CODE in use for this scanline
 6401:        useCODE - 1 if the CODE is invalid but it usage has been forced
 6402:                  by the operator
 6403:        CODE_ignore_dup - 1 if the CODE is a duplicated use when unique
 6404:                             CODEs were selected, but the usage has been
 6405:                             forced by the operator
 6406:        ID  - student/employee ID
 6407:        PaperID - if used, the ID number printed on the sheet when the 
 6408:                  paper was scanned
 6409:        FirstName - first name from the sheet
 6410:        LastName  - last name from the sheet
 6411: 
 6412:      if just_header was not true these key may also exist
 6413: 
 6414:        missingerror - a list of bubble ranges that are considered to be answers
 6415:                       to a single question that don't have any bubbles filled in.
 6416:                       Of the form questionnumber:firstbubblenumber:count.
 6417:        doubleerror  - a list of bubble ranges that are considered to be answers
 6418:                       to a single question that have more than one bubble filled in.
 6419:                       Of the form questionnumber::firstbubblenumber:count
 6420:    
 6421:                 In the above, count is the number of bubble responses in the
 6422:                 input line needed to represent the possible answers to the question.
 6423:                 e.g. a radioresponse with 15 choices in an answer sheet with 10 choices
 6424:                 per line would have count = 2.
 6425: 
 6426:        maxquest     - the number of the last bubble line that was parsed
 6427: 
 6428:        (<number> starts at 1)
 6429:        <number>.answer - zero or more letters representing the selected
 6430:                          letters from the scanline for the bubble line 
 6431:                          <number>.
 6432:                          if blank there was either no bubble or there where
 6433:                          multiple bubbles, (consult the keys missingerror and
 6434:                          doubleerror if this is an error condition)
 6435: 
 6436: =cut
 6437: 
 6438: sub scantron_parse_scanline {
 6439:     my ($line,$whichline,$scantron_config,$scan_data,$just_header,$idmap,
 6440:         $randomorder,$randompick,$sequence,$master_seq,$symb_to_resource,
 6441:         $partids_by_symb,$orderedforcode,$respnumlookup,$startline,$totalref)=@_;
 6442: 
 6443:     my %record;
 6444:     my $data=substr($line,0,$$scantron_config{'Qstart'}-1); # stuff before answers
 6445:     if (!($$scantron_config{'CODElocation'} eq 0 ||
 6446: 	  $$scantron_config{'CODElocation'} eq 'none')) {
 6447: 	if ($$scantron_config{'CODElocation'} < 0 ||
 6448: 	    $$scantron_config{'CODElocation'} eq 'letter' ||
 6449: 	    $$scantron_config{'CODElocation'} eq 'number') {
 6450: 	    $record{'scantron.CODE'}=substr($data,
 6451: 					    $$scantron_config{'CODEstart'}-1,
 6452: 					    $$scantron_config{'CODElength'});
 6453: 	    if (&scan_data($scan_data,"$whichline.useCODE")) {
 6454: 		$record{'scantron.useCODE'}=1;
 6455: 	    }
 6456: 	    if (&scan_data($scan_data,"$whichline.CODE_ignore_dup")) {
 6457: 		$record{'scantron.CODE_ignore_dup'}=1;
 6458: 	    }
 6459: 	} else {
 6460: 	    #FIXME interpret first N questions
 6461: 	}
 6462:     }
 6463:     $record{'scantron.ID'}=substr($data,$$scantron_config{'IDstart'}-1,
 6464: 				  $$scantron_config{'IDlength'});
 6465:     $record{'scantron.PaperID'}=
 6466: 	substr($data,$$scantron_config{'PaperID'}-1,
 6467: 	       $$scantron_config{'PaperIDlength'});
 6468:     $record{'scantron.FirstName'}=
 6469: 	substr($data,$$scantron_config{'FirstName'}-1,
 6470: 	       $$scantron_config{'FirstNamelength'});
 6471:     $record{'scantron.LastName'}=
 6472: 	substr($data,$$scantron_config{'LastName'}-1,
 6473: 	       $$scantron_config{'LastNamelength'});
 6474:     if ($just_header) { return \%record; }
 6475: 
 6476:     my @alphabet=('A'..'Z');
 6477:     my $questnum=0;
 6478:     my $ansnum  =1;		# Multiple 'answer lines'/question.
 6479: 
 6480:     my $lastpos = $env{'form.scantron_maxbubble'}*$$scantron_config{'Qlength'};
 6481:     if ($randompick || $randomorder) {
 6482:         my $total = &get_respnum_lookups($sequence,$scan_data,$idmap,$line,\%record,
 6483:                                          $master_seq,$symb_to_resource,
 6484:                                          $partids_by_symb,$orderedforcode,
 6485:                                          $respnumlookup,$startline);
 6486:         if ($total) {
 6487:             $lastpos = $total*$$scantron_config{'Qlength'};
 6488:         }
 6489:         if (ref($totalref)) {
 6490:             $$totalref = $total;
 6491:         }
 6492:     }
 6493:     my $questions=substr($line,$$scantron_config{'Qstart'}-1,$lastpos);  # Answers
 6494:     chomp($questions);		# Get rid of any trailing \n.
 6495:     $questions =~ s/\r$//;      # Get rid of trailing \r too (MAC or Win uploads).
 6496:     while (length($questions)) {
 6497:         my $answers_needed;
 6498:         if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
 6499:             $answers_needed = $bubble_lines_per_response{$respnumlookup->{$questnum}};
 6500:         } else {
 6501:             $answers_needed = $bubble_lines_per_response{$questnum};
 6502:         }
 6503:         my $answer_length  = ($$scantron_config{'Qlength'} * $answers_needed)
 6504:                              || 1;
 6505:         $questnum++;
 6506:         my $quest_id = $questnum;
 6507:         my $currentquest = substr($questions,0,$answer_length);
 6508:         $questions       = substr($questions,$answer_length);
 6509:         if (length($currentquest) < $answer_length) { next; }
 6510: 
 6511:         my $subdivided;
 6512:         if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
 6513:             $subdivided = $subdivided_bubble_lines{$respnumlookup->{$questnum-1}};
 6514:         } else {
 6515:             $subdivided = $subdivided_bubble_lines{$questnum-1};
 6516:         }
 6517:         if ($subdivided =~ /,/) {
 6518:             my $subquestnum = 1;
 6519:             my $subquestions = $currentquest;
 6520:             my @subanswers_needed = split(/,/,$subdivided);
 6521:             foreach my $subans (@subanswers_needed) {
 6522:                 my $subans_length =
 6523:                     ($$scantron_config{'Qlength'} * $subans)  || 1;
 6524:                 my $currsubquest = substr($subquestions,0,$subans_length);
 6525:                 $subquestions   = substr($subquestions,$subans_length);
 6526:                 $quest_id = "$questnum.$subquestnum";
 6527:                 if (($$scantron_config{'Qon'} eq 'letter') ||
 6528:                     ($$scantron_config{'Qon'} eq 'number')) {
 6529:                     $ansnum = &scantron_validator_lettnum($ansnum, 
 6530:                         $questnum,$quest_id,$subans,$currsubquest,$whichline,
 6531:                         \@alphabet,\%record,$scantron_config,$scan_data,
 6532:                         $randomorder,$randompick,$respnumlookup);
 6533:                 } else {
 6534:                     $ansnum = &scantron_validator_positional($ansnum,
 6535:                         $questnum,$quest_id,$subans,$currsubquest,$whichline,
 6536:                         \@alphabet,\%record,$scantron_config,$scan_data,
 6537:                         $randomorder,$randompick,$respnumlookup);
 6538:                 }
 6539:                 $subquestnum ++;
 6540:             }
 6541:         } else {
 6542:             if (($$scantron_config{'Qon'} eq 'letter') ||
 6543:                 ($$scantron_config{'Qon'} eq 'number')) {
 6544:                 $ansnum = &scantron_validator_lettnum($ansnum,$questnum,
 6545:                     $quest_id,$answers_needed,$currentquest,$whichline,
 6546:                     \@alphabet,\%record,$scantron_config,$scan_data,
 6547:                     $randomorder,$randompick,$respnumlookup);
 6548:             } else {
 6549:                 $ansnum = &scantron_validator_positional($ansnum,$questnum,
 6550:                     $quest_id,$answers_needed,$currentquest,$whichline,
 6551:                     \@alphabet,\%record,$scantron_config,$scan_data,
 6552:                     $randomorder,$randompick,$respnumlookup);
 6553:             }
 6554:         }
 6555:     }
 6556:     $record{'scantron.maxquest'}=$questnum;
 6557:     return \%record;
 6558: }
 6559: 
 6560: sub get_master_seq {
 6561:     my ($resources,$master_seq,$symb_to_resource) = @_;
 6562:     return unless ((ref($resources) eq 'ARRAY') && (ref($master_seq) eq 'ARRAY') &&
 6563:                    (ref($symb_to_resource) eq 'HASH'));
 6564:     my $resource_error;
 6565:     foreach my $resource (@{$resources}) {
 6566:         my $ressymb;
 6567:         if (ref($resource)) {
 6568:             $ressymb = $resource->symb();
 6569:             push(@{$master_seq},$ressymb);
 6570:             $symb_to_resource->{$ressymb} = $resource;
 6571:         } else {
 6572:             $resource_error = 1;
 6573:             last;
 6574:         }
 6575:     }
 6576:     return $resource_error;
 6577: }
 6578: 
 6579: sub get_respnum_lookups {
 6580:     my ($sequence,$scan_data,$idmap,$line,$record,$master_seq,$symb_to_resource,
 6581:         $partids_by_symb,$orderedforcode,$respnumlookup,$startline) = @_;
 6582:     return unless ((ref($record) eq 'HASH') && (ref($master_seq) eq 'ARRAY') &&
 6583:                    (ref($symb_to_resource) eq 'HASH') && (ref($partids_by_symb) eq 'HASH') &&
 6584:                    (ref($orderedforcode) eq 'HASH') && (ref($respnumlookup) eq 'HASH') &&
 6585:                    (ref($startline) eq 'HASH'));
 6586:     my ($user,$scancode);
 6587:     if ((exists($record->{'scantron.CODE'})) &&
 6588:         (&Apache::lonnet::validCODE($record->{'scantron.CODE'}))) {
 6589:         $scancode = $record->{'scantron.CODE'};
 6590:     } else {
 6591:         $user = &scantron_find_student($record,$scan_data,$idmap,$line);
 6592:     }
 6593:     my @mapresources =
 6594:         &users_order($user,$scancode,$sequence,$master_seq,$symb_to_resource,
 6595:                      $orderedforcode);
 6596:     my $total = 0;
 6597:     my $count = 0;
 6598:     foreach my $resource (@mapresources) {
 6599:         my $id = $resource->id();
 6600:         my $symb = $resource->symb();
 6601:         if (ref($partids_by_symb->{$symb}) eq 'ARRAY') {
 6602:             foreach my $partid (@{$partids_by_symb->{$symb}}) {
 6603:                 my $respnum = $masterseq_id_responsenum{$id.'_'.$partid};
 6604:                 if ($respnum ne '') {
 6605:                     $respnumlookup->{$count} = $respnum;
 6606:                     $startline->{$count} = $total;
 6607:                     $total += $bubble_lines_per_response{$respnum};
 6608:                     $count ++;
 6609:                 }
 6610:             }
 6611:         }
 6612:     }
 6613:     return $total;
 6614: }
 6615: 
 6616: sub scantron_validator_lettnum {
 6617:     my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,$whichline,
 6618:         $alphabet,$record,$scantron_config,$scan_data,$randomorder,
 6619:         $randompick,$respnumlookup) = @_;
 6620: 
 6621:     # Qon 'letter' implies for each slot in currquest we have:
 6622:     #    ? or * for doubles, a letter in A-Z for a bubble, and
 6623:     #    about anything else (esp. a value of Qoff) for missing
 6624:     #    bubbles.
 6625:     #
 6626:     # Qon 'number' implies each slot gives a digit that indexes the
 6627:     #    bubbles filled, or Qoff, or a non-number for unbubbled lines,
 6628:     #    and * or ? for double bubbles on a single line.
 6629:     #
 6630: 
 6631:     my $matchon;
 6632:     if ($$scantron_config{'Qon'} eq 'letter') {
 6633:         $matchon = '[A-Z]';
 6634:     } elsif ($$scantron_config{'Qon'} eq 'number') {
 6635:         $matchon = '\d';
 6636:     }
 6637:     my $occurrences = 0;
 6638:     my $responsenum = $questnum-1;
 6639:     if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
 6640:        $responsenum = $respnumlookup->{$questnum-1}
 6641:     }
 6642:     if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
 6643:         ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
 6644:         ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
 6645:         ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
 6646:         ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
 6647:         ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
 6648:         my @singlelines = split('',$currquest);
 6649:         foreach my $entry (@singlelines) {
 6650:             $occurrences = &occurence_count($entry,$matchon);
 6651:             if ($occurrences > 1) {
 6652:                 last;
 6653:             }
 6654:         }
 6655:     } else {
 6656:         $occurrences = &occurence_count($currquest,$matchon); 
 6657:     }
 6658:     if (($currquest =~ /\?/ || $currquest =~ /\*/) || ($occurrences > 1)) {
 6659:         push(@{$record->{'scantron.doubleerror'}},$quest_id);
 6660:         for (my $ans=0; $ans<$answers_needed; $ans++) {
 6661:             my $bubble = substr($currquest,$ans,1);
 6662:             if ($bubble =~ /$matchon/ ) {
 6663:                 if ($$scantron_config{'Qon'} eq 'number') {
 6664:                     if ($bubble == 0) {
 6665:                         $bubble = 10; 
 6666:                     }
 6667:                     $record->{"scantron.$ansnum.answer"} = 
 6668:                         $alphabet->[$bubble-1];
 6669:                 } else {
 6670:                     $record->{"scantron.$ansnum.answer"} = $bubble;
 6671:                 }
 6672:             } else {
 6673:                 $record->{"scantron.$ansnum.answer"}='';
 6674:             }
 6675:             $ansnum++;
 6676:         }
 6677:     } elsif (!defined($currquest)
 6678:             || (&occurence_count($currquest, $$scantron_config{'Qoff'}) == length($currquest))
 6679:             || (&occurence_count($currquest,$matchon) == 0)) {
 6680:         for (my $ans=0; $ans<$answers_needed; $ans++ ) {
 6681:             $record->{"scantron.$ansnum.answer"}='';
 6682:             $ansnum++;
 6683:         }
 6684:         if (!&scan_data($scan_data,"$whichline.no_bubble.$quest_id")) {
 6685:             push(@{$record->{'scantron.missingerror'}},$quest_id);
 6686:         }
 6687:     } else {
 6688:         if ($$scantron_config{'Qon'} eq 'number') {
 6689:             $currquest = &digits_to_letters($currquest);            
 6690:         }
 6691:         for (my $ans=0; $ans<$answers_needed; $ans++) {
 6692:             my $bubble = substr($currquest,$ans,1);
 6693:             $record->{"scantron.$ansnum.answer"} = $bubble;
 6694:             $ansnum++;
 6695:         }
 6696:     }
 6697:     return $ansnum;
 6698: }
 6699: 
 6700: sub scantron_validator_positional {
 6701:     my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,
 6702:         $whichline,$alphabet,$record,$scantron_config,$scan_data,
 6703:         $randomorder,$randompick,$respnumlookup) = @_;
 6704: 
 6705:     # Otherwise there's a positional notation;
 6706:     # each bubble line requires Qlength items, and there are filled in
 6707:     # bubbles for each case where there 'Qon' characters.
 6708:     #
 6709: 
 6710:     my @array=split($$scantron_config{'Qon'},$currquest,-1);
 6711: 
 6712:     # If the split only gives us one element.. the full length of the
 6713:     # answer string, no bubbles are filled in:
 6714: 
 6715:     if ($answers_needed eq '') {
 6716:         return;
 6717:     }
 6718: 
 6719:     if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {
 6720:         for (my $ans=0; $ans<$answers_needed; $ans++ ) {
 6721:             $record->{"scantron.$ansnum.answer"}='';
 6722:             $ansnum++;
 6723:         }
 6724:         if (!&scan_data($scan_data,"$whichline.no_bubble.$quest_id")) {
 6725:             push(@{$record->{"scantron.missingerror"}},$quest_id);
 6726:         }
 6727:     } elsif (scalar(@array) == 2) {
 6728:         my $location = length($array[0]);
 6729:         my $line_num = int($location / $$scantron_config{'Qlength'});
 6730:         my $bubble   = $alphabet->[$location % $$scantron_config{'Qlength'}];
 6731:         for (my $ans=0; $ans<$answers_needed; $ans++) {
 6732:             if ($ans eq $line_num) {
 6733:                 $record->{"scantron.$ansnum.answer"} = $bubble;
 6734:             } else {
 6735:                 $record->{"scantron.$ansnum.answer"} = ' ';
 6736:             }
 6737:             $ansnum++;
 6738:          }
 6739:     } else {
 6740:         #  If there's more than one instance of a bubble character
 6741:         #  That's a double bubble; with positional notation we can
 6742:         #  record all the bubbles filled in as well as the
 6743:         #  fact this response consists of multiple bubbles.
 6744:         #
 6745:         my $responsenum = $questnum-1;
 6746:         if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
 6747:             $responsenum = $respnumlookup->{$questnum-1}
 6748:         }
 6749:         if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
 6750:             ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
 6751:             ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
 6752:             ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
 6753:             ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
 6754:             ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
 6755:             my $doubleerror = 0;
 6756:             while (($currquest >= $$scantron_config{'Qlength'}) && 
 6757:                    (!$doubleerror)) {
 6758:                my $currline = substr($currquest,0,$$scantron_config{'Qlength'});
 6759:                $currquest = substr($currquest,$$scantron_config{'Qlength'});
 6760:                my @currarray = split($$scantron_config{'Qon'},$currline,-1);
 6761:                if (length(@currarray) > 2) {
 6762:                    $doubleerror = 1;
 6763:                } 
 6764:             }
 6765:             if ($doubleerror) {
 6766:                 push(@{$record->{'scantron.doubleerror'}},$quest_id);
 6767:             }
 6768:         } else {
 6769:             push(@{$record->{'scantron.doubleerror'}},$quest_id);
 6770:         }
 6771:         my $item = $ansnum;
 6772:         for (my $ans=0; $ans<$answers_needed; $ans++) {
 6773:             $record->{"scantron.$item.answer"} = '';
 6774:             $item ++;
 6775:         }
 6776: 
 6777:         my @ans=@array;
 6778:         my $i=0;
 6779:         my $increment = 0;
 6780:         while ($#ans) {
 6781:             $i+=length($ans[0]) + $increment;
 6782:             my $line   = int($i/$$scantron_config{'Qlength'} + $ansnum);
 6783:             my $bubble = $i%$$scantron_config{'Qlength'};
 6784:             $record->{"scantron.$line.answer"}.=$alphabet->[$bubble];
 6785:             shift(@ans);
 6786:             $increment = 1;
 6787:         }
 6788:         $ansnum += $answers_needed;
 6789:     }
 6790:     return $ansnum;
 6791: }
 6792: 
 6793: =pod
 6794: 
 6795: =item scantron_add_delay
 6796: 
 6797:    Adds an error message that occurred during the grading phase to a
 6798:    queue of messages to be shown after grading pass is complete
 6799: 
 6800:  Arguments:
 6801:    $delayqueue  - arrary ref of hash ref of error messages
 6802:    $scanline    - the scanline that caused the error
 6803:    $errormesage - the error message
 6804:    $errorcode   - a numeric code for the error
 6805: 
 6806:  Side Effects:
 6807:    updates the $delayqueue to have a new hash ref of the error
 6808: 
 6809: =cut
 6810: 
 6811: sub scantron_add_delay {
 6812:     my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;
 6813:     push(@$delayqueue,
 6814: 	 {'line' => $scanline, 'emsg' => $errormessage,
 6815: 	  'ecode' => $errorcode }
 6816: 	 );
 6817: }
 6818: 
 6819: =pod
 6820: 
 6821: =item scantron_find_student
 6822: 
 6823:    Finds the username for the current scanline
 6824: 
 6825:   Arguments:
 6826:    $scantron_record - hash result from scantron_parse_scanline
 6827:    $scan_data       - hash of correction information 
 6828:                       (see &scantron_getfile() form more information)
 6829:    $idmap           - hash from &username_to_idmap()
 6830:    $line            - number of current scanline
 6831:  
 6832:   Returns:
 6833:    Either 'username:domain' or undef if unknown
 6834: 
 6835: =cut
 6836: 
 6837: sub scantron_find_student {
 6838:     my ($scantron_record,$scan_data,$idmap,$line)=@_;
 6839:     my $scanID=$$scantron_record{'scantron.ID'};
 6840:     if ($scanID =~ /^\s*$/) {
 6841:  	return &scan_data($scan_data,"$line.user");
 6842:     }
 6843:     foreach my $id (keys(%$idmap)) {
 6844:  	if (lc($id) eq lc($scanID)) {
 6845:  	    return $$idmap{$id};
 6846:  	}
 6847:     }
 6848:     return undef;
 6849: }
 6850: 
 6851: =pod
 6852: 
 6853: =item scantron_filter
 6854: 
 6855:    Filter sub for lonnavmaps, filters out hidden resources if ignore
 6856:    hidden resources was selected
 6857: 
 6858: =cut
 6859: 
 6860: sub scantron_filter {
 6861:     my ($curres)=@_;
 6862: 
 6863:     if (ref($curres) && $curres->is_problem()) {
 6864: 	# if the user has asked to not have either hidden
 6865: 	# or 'randomout' controlled resources to be graded
 6866: 	# don't include them
 6867: 	if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
 6868: 	    && $curres->randomout) {
 6869: 	    return 0;
 6870: 	}
 6871: 	return 1;
 6872:     }
 6873:     return 0;
 6874: }
 6875: 
 6876: =pod
 6877: 
 6878: =item scantron_process_corrections
 6879: 
 6880:    Gets correction information out of submitted form data and corrects
 6881:    the scanline
 6882: 
 6883: =cut
 6884: 
 6885: sub scantron_process_corrections {
 6886:     my ($r) = @_;
 6887:     my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
 6888:     my ($scanlines,$scan_data)=&scantron_getfile();
 6889:     my $classlist=&Apache::loncoursedata::get_classlist();
 6890:     my $which=$env{'form.scantron_line'};
 6891:     my $line=&scantron_get_line($scanlines,$scan_data,$which);
 6892:     my ($skip,$err,$errmsg);
 6893:     if ($env{'form.scantron_skip_record'}) {
 6894: 	$skip=1;
 6895:     } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) {
 6896: 	my $newstudent=$env{'form.scantron_username'}.':'.
 6897: 	    $env{'form.scantron_domain'};
 6898: 	my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID];
 6899: 	($line,$err,$errmsg)=
 6900: 	    &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
 6901: 				     'ID',{'newid'=>$newid,
 6902: 				    'username'=>$env{'form.scantron_username'},
 6903: 				    'domain'=>$env{'form.scantron_domain'}});
 6904:     } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)CODE$/) {
 6905: 	my $resolution=$env{'form.scantron_CODE_resolution'};
 6906: 	my $newCODE;
 6907: 	my %args;
 6908: 	if      ($resolution eq 'use_unfound') {
 6909: 	    $newCODE='use_unfound';
 6910: 	} elsif ($resolution eq 'use_found') {
 6911: 	    $newCODE=$env{'form.scantron_CODE_selectedvalue'};
 6912: 	} elsif ($resolution eq 'use_typed') {
 6913: 	    $newCODE=$env{'form.scantron_CODE_newvalue'};
 6914: 	} elsif ($resolution =~ /^use_closest_(\d+)/) {
 6915: 	    $newCODE=$env{"form.scantron_CODE_closest_$1"};
 6916: 	}
 6917: 	if ($env{'form.scantron_corrections'} eq 'duplicateCODE') {
 6918: 	    $args{'CODE_ignore_dup'}=1;
 6919: 	}
 6920: 	$args{'CODE'}=$newCODE;
 6921: 	($line,$err,$errmsg)=
 6922: 	    &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
 6923: 				     'CODE',\%args);
 6924:     } elsif ($env{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) {
 6925: 	foreach my $question (split(',',$env{'form.scantron_questions'})) {
 6926: 	    ($line,$err,$errmsg)=
 6927: 		&scantron_fixup_scanline(\%scantron_config,$scan_data,$line,
 6928: 					 $which,'answer',
 6929: 					 { 'question'=>$question,
 6930: 		      		   'response'=>$env{"form.scantron_correct_Q_$question"},
 6931:                                    'questionnum'=>$env{"form.scantron_questionnum_Q_$question"}});
 6932: 	    if ($err) { last; }
 6933: 	}
 6934:     }
 6935:     if ($err) {
 6936: 	$r->print(
 6937:             '<p class="LC_error">'
 6938:            .&mt('Unable to accept last correction, an error occurred: [_1]',
 6939:                 $errmsg)
 6940:            .'</p>');
 6941:     } else {
 6942: 	&scantron_put_line($scanlines,$scan_data,$which,$line,$skip);
 6943: 	&scantron_putfile($scanlines,$scan_data);
 6944:     }
 6945: }
 6946: 
 6947: =pod
 6948: 
 6949: =item reset_skipping_status
 6950: 
 6951:    Forgets the current set of remember skipped scanlines (and thus
 6952:    reverts back to considering all lines in the
 6953:    scantron_skipped_<filename> file)
 6954: 
 6955: =cut
 6956: 
 6957: sub reset_skipping_status {
 6958:     my ($scanlines,$scan_data)=&scantron_getfile();
 6959:     &scan_data($scan_data,'remember_skipping',undef,1);
 6960:     &scantron_putfile(undef,$scan_data);
 6961: }
 6962: 
 6963: =pod
 6964: 
 6965: =item start_skipping
 6966: 
 6967:    Marks a scanline to be skipped. 
 6968: 
 6969: =cut
 6970: 
 6971: sub start_skipping {
 6972:     my ($scan_data,$i)=@_;
 6973:     my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
 6974:     if ($env{'form.scantron_options_redo'} =~ /^redo_/) {
 6975: 	$remembered{$i}=2;
 6976:     } else {
 6977: 	$remembered{$i}=1;
 6978:     }
 6979:     &scan_data($scan_data,'remember_skipping',join(':',%remembered));
 6980: }
 6981: 
 6982: =pod
 6983: 
 6984: =item should_be_skipped
 6985: 
 6986:    Checks whether a scanline should be skipped.
 6987: 
 6988: =cut
 6989: 
 6990: sub should_be_skipped {
 6991:     my ($scanlines,$scan_data,$i)=@_;
 6992:     if ($env{'form.scantron_options_redo'} !~ /^redo_/) {
 6993: 	# not redoing old skips
 6994: 	if ($scanlines->{'skipped'}[$i]) { return 1; }
 6995: 	return 0;
 6996:     }
 6997:     my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
 6998: 
 6999:     if (exists($remembered{$i}) && $remembered{$i} != 2 ) {
 7000: 	return 0;
 7001:     }
 7002:     return 1;
 7003: }
 7004: 
 7005: =pod
 7006: 
 7007: =item remember_current_skipped
 7008: 
 7009:    Discovers what scanlines are in the scantron_skipped_<filename>
 7010:    file and remembers them into scan_data for later use.
 7011: 
 7012: =cut
 7013: 
 7014: sub remember_current_skipped {
 7015:     my ($scanlines,$scan_data)=&scantron_getfile();
 7016:     my %to_remember;
 7017:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 7018: 	if ($scanlines->{'skipped'}[$i]) {
 7019: 	    $to_remember{$i}=1;
 7020: 	}
 7021:     }
 7022: 
 7023:     &scan_data($scan_data,'remember_skipping',join(':',%to_remember));
 7024:     &scantron_putfile(undef,$scan_data);
 7025: }
 7026: 
 7027: =pod
 7028: 
 7029: =item check_for_error
 7030: 
 7031:     Checks if there was an error when attempting to remove a specific
 7032:     scantron_.. bubblesheet data file. Prints out an error if
 7033:     something went wrong.
 7034: 
 7035: =cut
 7036: 
 7037: sub check_for_error {
 7038:     my ($r,$result)=@_;
 7039:     if ($result ne 'ok' && $result ne 'not_found' ) {
 7040: 	$r->print(&mt("An error occurred ([_1]) when trying to remove the existing corrections.",$result));
 7041:     }
 7042: }
 7043: 
 7044: =pod
 7045: 
 7046: =item scantron_warning_screen
 7047: 
 7048:    Interstitial screen to make sure the operator has selected the
 7049:    correct options before we start the validation phase.
 7050: 
 7051: =cut
 7052: 
 7053: sub scantron_warning_screen {
 7054:     my ($button_text,$symb)=@_;
 7055:     my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
 7056:     my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
 7057:     my $CODElist;
 7058:     if ($scantron_config{'CODElocation'} &&
 7059: 	$scantron_config{'CODEstart'} &&
 7060: 	$scantron_config{'CODElength'}) {
 7061: 	$CODElist=$env{'form.scantron_CODElist'};
 7062: 	if ($env{'form.scantron_CODElist'} eq '') { $CODElist='<span class="LC_warning">'.&mt('None').'</span>'; }
 7063: 	$CODElist=
 7064: 	    '<tr><td><b>'.&mt('List of CODES to validate against:').'</b></td><td><tt>'.
 7065: 	    $env{'form.scantron_CODElist'}.'</tt></td></tr>';
 7066:     }
 7067:     my $lastbubblepoints;
 7068:     if ($env{'form.scantron_lastbubblepoints'} ne '') {
 7069:         $lastbubblepoints =
 7070:             '<tr><td><b>'.&mt('Hand-graded items: points from last bubble in row').'</b></td><td><tt>'.
 7071:             $env{'form.scantron_lastbubblepoints'}.'</tt></td></tr>';
 7072:     }
 7073:     return ('
 7074: <p>
 7075: <span class="LC_warning">
 7076: '.&mt("Please double check the information below before clicking on '[_1]'",&mt($button_text)).'</span>
 7077: </p>
 7078: <table>
 7079: <tr><td><b>'.&mt('Sequence to be Graded:').'</b></td><td>'.$title.'</td></tr>
 7080: <tr><td><b>'.&mt('Data File that will be used:').'</b></td><td><tt>'.$env{'form.scantron_selectfile'}.'</tt></td></tr>
 7081: '.$CODElist.$lastbubblepoints.'
 7082: </table>
 7083: <p> '.&mt("If this information is correct, please click on '[_1]'.",&mt($button_text)).'<br />
 7084: '.&mt('If something is incorrect, please return to [_1]Grade/Manage/Review Bubblesheets[_2] to start over.','<a href="/adm/grades?symb='.$symb.'&command=scantron_selectphase" class="LC_info">','</a>').'</p>
 7085: 
 7086: <br />
 7087: ');
 7088: }
 7089: 
 7090: =pod
 7091: 
 7092: =item scantron_do_warning
 7093: 
 7094:    Check if the operator has picked something for all required
 7095:    fields. Error out if something is missing.
 7096: 
 7097: =cut
 7098: 
 7099: sub scantron_do_warning {
 7100:     my ($r,$symb)=@_;
 7101:     if (!$symb) {return '';}
 7102:     my $default_form_data=&defaultFormData($symb);
 7103:     $r->print(&scantron_form_start().$default_form_data);
 7104:     if ( $env{'form.selectpage'} eq '' ||
 7105: 	 $env{'form.scantron_selectfile'} eq '' ||
 7106: 	 $env{'form.scantron_format'} eq '' ) {
 7107: 	$r->print("<p>".&mt('You have forgotten to specify some information. Please go Back and try again.')."</p>");
 7108: 	if ( $env{'form.selectpage'} eq '') {
 7109: 	    $r->print('<p><span class="LC_error">'.&mt('You have not selected a Sequence to grade').'</span></p>');
 7110: 	} 
 7111: 	if ( $env{'form.scantron_selectfile'} eq '') {
 7112: 	    $r->print('<p><span class="LC_error">'.&mt("You have not selected a file that contains the student's response data.").'</span></p>');
 7113: 	} 
 7114: 	if ( $env{'form.scantron_format'} eq '') {
 7115: 	    $r->print('<p><span class="LC_error">'.&mt("You have not selected the format of the student's response data.").'</span></p>');
 7116: 	} 
 7117:     } else {
 7118: 	my $warning=&scantron_warning_screen('Grading: Validate Records',$symb);
 7119:         my $bubbledbyhand=&hand_bubble_option();
 7120: 	$r->print('
 7121: '.$warning.$bubbledbyhand.'
 7122: <input type="submit" name="submit" value="'.&mt('Grading: Validate Records').'" />
 7123: <input type="hidden" name="command" value="scantron_validate" />
 7124: ');
 7125:     }
 7126:     $r->print("</form><br />");
 7127:     return '';
 7128: }
 7129: 
 7130: =pod
 7131: 
 7132: =item scantron_form_start
 7133: 
 7134:     html hidden input for remembering all selected grading options
 7135: 
 7136: =cut
 7137: 
 7138: sub scantron_form_start {
 7139:     my ($max_bubble)=@_;
 7140:     my $result= <<SCANTRONFORM;
 7141: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
 7142:   <input type="hidden" name="selectpage" value="$env{'form.selectpage'}" />
 7143:   <input type="hidden" name="scantron_format" value="$env{'form.scantron_format'}" />
 7144:   <input type="hidden" name="scantron_selectfile" value="$env{'form.scantron_selectfile'}" />
 7145:   <input type="hidden" name="scantron_maxbubble" value="$max_bubble" />
 7146:   <input type="hidden" name="scantron_CODElist" value="$env{'form.scantron_CODElist'}" />
 7147:   <input type="hidden" name="scantron_CODEunique" value="$env{'form.scantron_CODEunique'}" />
 7148:   <input type="hidden" name="scantron_options_redo" value="$env{'form.scantron_options_redo'}" />
 7149:   <input type="hidden" name="scantron_options_ignore" value="$env{'form.scantron_options_ignore'}" />
 7150:   <input type="hidden" name="scantron_options_hidden" value="$env{'form.scantron_options_hidden'}" />
 7151: SCANTRONFORM
 7152: 
 7153:   my $line = 0;
 7154:     while (defined($env{"form.scantron.bubblelines.$line"})) {
 7155:        my $chunk =
 7156: 	   '<input type="hidden" name="scantron.bubblelines.'.$line.'" value="'.$env{"form.scantron.bubblelines.$line"}.'" />'."\n";
 7157:        $chunk .=
 7158: 	   '<input type="hidden" name="scantron.first_bubble_line.'.$line.'" value="'.$env{"form.scantron.first_bubble_line.$line"}.'" />'."\n";
 7159:        $chunk .= 
 7160:            '<input type="hidden" name="scantron.sub_bubblelines.'.$line.'" value="'.$env{"form.scantron.sub_bubblelines.$line"}.'" />'."\n";
 7161:        $chunk .=
 7162:            '<input type="hidden" name="scantron.responsetype.'.$line.'" value="'.$env{"form.scantron.responsetype.$line"}.'" />'."\n";
 7163:        $chunk .=
 7164:            '<input type="hidden" name="scantron.residpart.'.$line.'" value="'.$env{"form.scantron.residpart.$line"}.'" />'."\n";
 7165:        $result .= $chunk;
 7166:        $line++;
 7167:     }
 7168:     return $result;
 7169: }
 7170: 
 7171: =pod
 7172: 
 7173: =item scantron_validate_file
 7174: 
 7175:     Dispatch routine for doing validation of a bubblesheet data file.
 7176: 
 7177:     Also processes any necessary information resets that need to
 7178:     occur before validation begins (ignore previous corrections,
 7179:     restarting the skipped records processing)
 7180: 
 7181: =cut
 7182: 
 7183: sub scantron_validate_file {
 7184:     my ($r,$symb) = @_;
 7185:     if (!$symb) {return '';}
 7186:     my $default_form_data=&defaultFormData($symb);
 7187:     
 7188:     # do the detection of only doing skipped records first before we delete
 7189:     # them when doing the corrections reset
 7190:     if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {
 7191: 	&reset_skipping_status();
 7192:     }
 7193:     if ($env{'form.scantron_options_redo'} eq 'redo_skipped') {
 7194: 	&remember_current_skipped();
 7195: 	$env{'form.scantron_options_redo'}='redo_skipped_ready';
 7196:     }
 7197: 
 7198:     if ($env{'form.scantron_options_ignore'} eq 'ignore_corrections') {
 7199: 	&check_for_error($r,&scantron_remove_file('corrected'));
 7200: 	&check_for_error($r,&scantron_remove_file('skipped'));
 7201: 	&check_for_error($r,&scantron_remove_scan_data());
 7202: 	$env{'form.scantron_options_ignore'}='done';
 7203:     }
 7204: 
 7205:     if ($env{'form.scantron_corrections'}) {
 7206: 	&scantron_process_corrections($r);
 7207:     }
 7208:     $r->print('<p>'.&mt('Gathering necessary information.').'</p>');$r->rflush();
 7209:     #get the student pick code ready
 7210:     $r->print(&Apache::loncommon::studentbrowser_javascript());
 7211:     my $nav_error;
 7212:     my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
 7213:     my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
 7214:     if ($nav_error) {
 7215:         $r->print(&navmap_errormsg());
 7216:         return '';
 7217:     }
 7218:     my $result=&scantron_form_start($max_bubble).$default_form_data;
 7219:     if ($env{'form.scantron_lastbubblepoints'} ne '') {
 7220:         $result .= '<input type="hidden" name="scantron_lastbubblepoints" value="'.$env{'form.scantron_lastbubblepoints'}.'" />';
 7221:     }
 7222:     $r->print($result);
 7223:     
 7224:     my @validate_phases=( 'sequence',
 7225: 			  'ID',
 7226: 			  'CODE',
 7227: 			  'doublebubble',
 7228: 			  'missingbubbles');
 7229:     if (!$env{'form.validatepass'}) {
 7230: 	$env{'form.validatepass'} = 0;
 7231:     }
 7232:     my $currentphase=$env{'form.validatepass'};
 7233: 
 7234: 
 7235:     my $stop=0;
 7236:     while (!$stop && $currentphase < scalar(@validate_phases)) {
 7237: 	$r->print(&mt('Validating '.$validate_phases[$currentphase]).'<br />');
 7238: 	$r->rflush();
 7239: 
 7240: 	my $which="scantron_validate_".$validate_phases[$currentphase];
 7241: 	{
 7242: 	    no strict 'refs';
 7243: 	    ($stop,$currentphase)=&$which($r,$currentphase);
 7244: 	}
 7245:     }
 7246:     if (!$stop) {
 7247: 	my $warning=&scantron_warning_screen('Start Grading',$symb);
 7248: 	$r->print(&mt('Validation process complete.').'<br />'.
 7249:                   $warning.
 7250:                   &mt('Perform verification for each student after storage of submissions?').
 7251:                   '&nbsp;<span class="LC_nobreak"><label>'.
 7252:                   '<input type="radio" name="verifyrecord" value="1" />'.&mt('Yes').'</label>'.
 7253:                   ('&nbsp;'x3).'<label>'.
 7254:                   '<input type="radio" name="verifyrecord" value="0" checked="checked" />'.&mt('No').
 7255:                   '</label></span><br />'.
 7256:                   &mt('Grading will take longer if you use verification.').'<br />'.
 7257:                   &mt('Otherwise, Grade/Manage/Review Bubblesheets [_1] Review bubblesheet data can be used once grading is complete.','&raquo;').'<br /><br />'.
 7258:                   '<input type="submit" name="submit" value="'.&mt('Start Grading').'" />'.
 7259:                   '<input type="hidden" name="command" value="scantron_process" />'."\n");
 7260:     } else {
 7261: 	$r->print('<input type="hidden" name="command" value="scantron_validate" />');
 7262: 	$r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />");
 7263:     }
 7264:     if ($stop) {
 7265: 	if ($validate_phases[$currentphase] eq 'sequence') {
 7266: 	    $r->print('<input type="submit" name="submit" value="'.&mt('Ignore').' &rarr; " />');
 7267: 	    $r->print(' '.&mt('this error').' <br />');
 7268: 
 7269:             $r->print('<p>'.&mt('Or return to [_1]Grade/Manage/Review Bubblesheets[_2] to start over.','<a href="/adm/grades?symb='.$symb.'&command=scantron_selectphase" class="LC_info">','</a>').'</p>');
 7270: 	} else {
 7271:             if ($validate_phases[$currentphase] eq 'doublebubble' || $validate_phases[$currentphase] eq 'missingbubbles') {
 7272: 	        $r->print('<input type="button" name="submitbutton" value="'.&mt('Continue').' &rarr;" onclick="javascript:verify_bubble_radio(this.form)" />');
 7273:             } else {
 7274:                 $r->print('<input type="submit" name="submit" value="'.&mt('Continue').' &rarr;" />');
 7275:             }
 7276: 	    $r->print(' '.&mt('using corrected info').' <br />');
 7277: 	    $r->print("<input type='submit' value='".&mt("Skip")."' name='scantron_skip_record' />");
 7278: 	    $r->print(" ".&mt("this scanline saving it for later."));
 7279: 	}
 7280:     }
 7281:     $r->print(" </form><br />");
 7282:     return '';
 7283: }
 7284: 
 7285: 
 7286: =pod
 7287: 
 7288: =item scantron_remove_file
 7289: 
 7290:    Removes the requested bubblesheet data file, makes sure that
 7291:    scantron_original_<filename> is never removed
 7292: 
 7293: 
 7294: =cut
 7295: 
 7296: sub scantron_remove_file {
 7297:     my ($which)=@_;
 7298:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 7299:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 7300:     my $file='scantron_';
 7301:     if ($which eq 'corrected' || $which eq 'skipped') {
 7302: 	$file.=$which.'_';
 7303:     } else {
 7304: 	return 'refused';
 7305:     }
 7306:     $file.=$env{'form.scantron_selectfile'};
 7307:     return &Apache::lonnet::removeuserfile($cname,$cdom,$file);
 7308: }
 7309: 
 7310: 
 7311: =pod
 7312: 
 7313: =item scantron_remove_scan_data
 7314: 
 7315:    Removes all scan_data correction for the requested bubblesheet
 7316:    data file.  (In the case that both the are doing skipped records we need
 7317:    to remember the old skipped lines for the time being so that element
 7318:    persists for a while.)
 7319: 
 7320: =cut
 7321: 
 7322: sub scantron_remove_scan_data {
 7323:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 7324:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 7325:     my @keys=&Apache::lonnet::getkeys('nohist_scantrondata',$cdom,$cname);
 7326:     my @todelete;
 7327:     my $filename=$env{'form.scantron_selectfile'};
 7328:     foreach my $key (@keys) {
 7329: 	if ($key=~/^\Q$filename\E_/) {
 7330: 	    if ($env{'form.scantron_options_redo'} eq 'redo_skipped_ready' &&
 7331: 		$key=~/remember_skipping/) {
 7332: 		next;
 7333: 	    }
 7334: 	    push(@todelete,$key);
 7335: 	}
 7336:     }
 7337:     my $result;
 7338:     if (@todelete) {
 7339: 	$result = &Apache::lonnet::del('nohist_scantrondata',
 7340: 				       \@todelete,$cdom,$cname);
 7341:     } else {
 7342: 	$result = 'ok';
 7343:     }
 7344:     return $result;
 7345: }
 7346: 
 7347: 
 7348: =pod
 7349: 
 7350: =item scantron_getfile
 7351: 
 7352:     Fetches the requested bubblesheet data file (all 3 versions), and
 7353:     the scan_data hash
 7354:   
 7355:   Arguments:
 7356:     None
 7357: 
 7358:   Returns:
 7359:     2 hash references
 7360: 
 7361:      - first one has 
 7362:          orig      -
 7363:          corrected -
 7364:          skipped   -  each of which points to an array ref of the specified
 7365:                       file broken up into individual lines
 7366:          count     - number of scanlines
 7367:  
 7368:      - second is the scan_data hash possible keys are
 7369:        ($number refers to scanline numbered $number and thus the key affects
 7370:         only that scanline
 7371:         $bubline refers to the specific bubble line element and the aspects
 7372:         refers to that specific bubble line element)
 7373: 
 7374:        $number.user - username:domain to use
 7375:        $number.CODE_ignore_dup 
 7376:                     - ignore the duplicate CODE error 
 7377:        $number.useCODE
 7378:                     - use the CODE in the scanline as is
 7379:        $number.no_bubble.$bubline
 7380:                     - it is valid that there is no bubbled in bubble
 7381:                       at $number $bubline
 7382:        remember_skipping
 7383:                     - a frozen hash containing keys of $number and values
 7384:                       of either 
 7385:                         1 - we are on a 'do skipped records pass' and plan
 7386:                             on processing this line
 7387:                         2 - we are on a 'do skipped records pass' and this
 7388:                             scanline has been marked to skip yet again
 7389: 
 7390: =cut
 7391: 
 7392: sub scantron_getfile {
 7393:     #FIXME really would prefer a scantron directory
 7394:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 7395:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 7396:     my $lines;
 7397:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
 7398: 		       'scantron_orig_'.$env{'form.scantron_selectfile'});
 7399:     my %scanlines;
 7400:     $scanlines{'orig'}=[(split("\n",$lines,-1))];
 7401:     my $temp=$scanlines{'orig'};
 7402:     $scanlines{'count'}=$#$temp;
 7403: 
 7404:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
 7405: 		       'scantron_corrected_'.$env{'form.scantron_selectfile'});
 7406:     if ($lines eq '-1') {
 7407: 	$scanlines{'corrected'}=[];
 7408:     } else {
 7409: 	$scanlines{'corrected'}=[(split("\n",$lines,-1))];
 7410:     }
 7411:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
 7412: 		       'scantron_skipped_'.$env{'form.scantron_selectfile'});
 7413:     if ($lines eq '-1') {
 7414: 	$scanlines{'skipped'}=[];
 7415:     } else {
 7416: 	$scanlines{'skipped'}=[(split("\n",$lines,-1))];
 7417:     }
 7418:     my @tmp=&Apache::lonnet::dump('nohist_scantrondata',$cdom,$cname);
 7419:     if ($tmp[0] =~ /^(error:|no_such_host)/) { @tmp=(); }
 7420:     my %scan_data = @tmp;
 7421:     return (\%scanlines,\%scan_data);
 7422: }
 7423: 
 7424: =pod
 7425: 
 7426: =item lonnet_putfile
 7427: 
 7428:    Wrapper routine to call &Apache::lonnet::finishuserfileupload
 7429: 
 7430:  Arguments:
 7431:    $contents - data to store
 7432:    $filename - filename to store $contents into
 7433: 
 7434:  Returns:
 7435:    result value from &Apache::lonnet::finishuserfileupload
 7436: 
 7437: =cut
 7438: 
 7439: sub lonnet_putfile {
 7440:     my ($contents,$filename)=@_;
 7441:     my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
 7442:     my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 7443:     $env{'form.sillywaytopassafilearound'}=$contents;
 7444:     &Apache::lonnet::finishuserfileupload($docuname,$docudom,'sillywaytopassafilearound',$filename);
 7445: 
 7446: }
 7447: 
 7448: =pod
 7449: 
 7450: =item scantron_putfile
 7451: 
 7452:     Stores the current version of the bubblesheet data files, and the
 7453:     scan_data hash. (Does not modify the original version only the
 7454:     corrected and skipped versions.
 7455: 
 7456:  Arguments:
 7457:     $scanlines - hash ref that looks like the first return value from
 7458:                  &scantron_getfile()
 7459:     $scan_data - hash ref that looks like the second return value from
 7460:                  &scantron_getfile()
 7461: 
 7462: =cut
 7463: 
 7464: sub scantron_putfile {
 7465:     my ($scanlines,$scan_data) = @_;
 7466:     #FIXME really would prefer a scantron directory
 7467:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 7468:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 7469:     if ($scanlines) {
 7470: 	my $prefix='scantron_';
 7471: # no need to update orig, shouldn't change
 7472: #   &lonnet_putfile(join("\n",@{$scanlines->{'orig'}}),$prefix.'orig_'.
 7473: #		    $env{'form.scantron_selectfile'});
 7474: 	&lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}),
 7475: 			$prefix.'corrected_'.
 7476: 			$env{'form.scantron_selectfile'});
 7477: 	&lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}),
 7478: 			$prefix.'skipped_'.
 7479: 			$env{'form.scantron_selectfile'});
 7480:     }
 7481:     &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname);
 7482: }
 7483: 
 7484: =pod
 7485: 
 7486: =item scantron_get_line
 7487: 
 7488:    Returns the correct version of the scanline
 7489: 
 7490:  Arguments:
 7491:     $scanlines - hash ref that looks like the first return value from
 7492:                  &scantron_getfile()
 7493:     $scan_data - hash ref that looks like the second return value from
 7494:                  &scantron_getfile()
 7495:     $i         - number of the requested line (starts at 0)
 7496: 
 7497:  Returns:
 7498:    A scanline, (either the original or the corrected one if it
 7499:    exists), or undef if the requested scanline should be
 7500:    skipped. (Either because it's an skipped scanline, or it's an
 7501:    unskipped scanline and we are not doing a 'do skipped scanlines'
 7502:    pass.
 7503: 
 7504: =cut
 7505: 
 7506: sub scantron_get_line {
 7507:     my ($scanlines,$scan_data,$i)=@_;
 7508:     if (&should_be_skipped($scanlines,$scan_data,$i)) { return undef; }
 7509:     #if ($scanlines->{'skipped'}[$i]) { return undef; }
 7510:     if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];}
 7511:     return $scanlines->{'orig'}[$i]; 
 7512: }
 7513: 
 7514: =pod
 7515: 
 7516: =item scantron_todo_count
 7517: 
 7518:     Counts the number of scanlines that need processing.
 7519: 
 7520:  Arguments:
 7521:     $scanlines - hash ref that looks like the first return value from
 7522:                  &scantron_getfile()
 7523:     $scan_data - hash ref that looks like the second return value from
 7524:                  &scantron_getfile()
 7525: 
 7526:  Returns:
 7527:     $count - number of scanlines to process
 7528: 
 7529: =cut
 7530: 
 7531: sub get_todo_count {
 7532:     my ($scanlines,$scan_data)=@_;
 7533:     my $count=0;
 7534:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 7535: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 7536: 	if ($line=~/^[\s\cz]*$/) { next; }
 7537: 	$count++;
 7538:     }
 7539:     return $count;
 7540: }
 7541: 
 7542: =pod
 7543: 
 7544: =item scantron_put_line
 7545: 
 7546:     Updates the 'corrected' or 'skipped' versions of the bubblesheet
 7547:     data file.
 7548: 
 7549:  Arguments:
 7550:     $scanlines - hash ref that looks like the first return value from
 7551:                  &scantron_getfile()
 7552:     $scan_data - hash ref that looks like the second return value from
 7553:                  &scantron_getfile()
 7554:     $i         - line number to update
 7555:     $newline   - contents of the updated scanline
 7556:     $skip      - if true make the line for skipping and update the
 7557:                  'skipped' file
 7558: 
 7559: =cut
 7560: 
 7561: sub scantron_put_line {
 7562:     my ($scanlines,$scan_data,$i,$newline,$skip)=@_;
 7563:     if ($skip) {
 7564: 	$scanlines->{'skipped'}[$i]=$newline;
 7565: 	&start_skipping($scan_data,$i);
 7566: 	return;
 7567:     }
 7568:     $scanlines->{'corrected'}[$i]=$newline;
 7569: }
 7570: 
 7571: =pod
 7572: 
 7573: =item scantron_clear_skip
 7574: 
 7575:    Remove a line from the 'skipped' file
 7576: 
 7577:  Arguments:
 7578:     $scanlines - hash ref that looks like the first return value from
 7579:                  &scantron_getfile()
 7580:     $scan_data - hash ref that looks like the second return value from
 7581:                  &scantron_getfile()
 7582:     $i         - line number to update
 7583: 
 7584: =cut
 7585: 
 7586: sub scantron_clear_skip {
 7587:     my ($scanlines,$scan_data,$i)=@_;
 7588:     if (exists($scanlines->{'skipped'}[$i])) {
 7589: 	undef($scanlines->{'skipped'}[$i]);
 7590: 	return 1;
 7591:     }
 7592:     return 0;
 7593: }
 7594: 
 7595: =pod
 7596: 
 7597: =item scantron_filter_not_exam
 7598: 
 7599:    Filter routine used by &Apache::lonnavmaps::retrieveResources(), to
 7600:    filter out resources that are not marked as 'exam' mode
 7601: 
 7602: =cut
 7603: 
 7604: sub scantron_filter_not_exam {
 7605:     my ($curres)=@_;
 7606:     
 7607:     if (ref($curres) && $curres->is_problem() && !$curres->is_exam()) {
 7608: 	# if the user has asked to not have either hidden
 7609: 	# or 'randomout' controlled resources to be graded
 7610: 	# don't include them
 7611: 	if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
 7612: 	    && $curres->randomout) {
 7613: 	    return 0;
 7614: 	}
 7615: 	return 1;
 7616:     }
 7617:     return 0;
 7618: }
 7619: 
 7620: =pod
 7621: 
 7622: =item scantron_validate_sequence
 7623: 
 7624:     Validates the selected sequence, checking for resource that are
 7625:     not set to exam mode.
 7626: 
 7627: =cut
 7628: 
 7629: sub scantron_validate_sequence {
 7630:     my ($r,$currentphase) = @_;
 7631: 
 7632:     my $navmap=Apache::lonnavmaps::navmap->new();
 7633:     unless (ref($navmap)) {
 7634:         $r->print(&navmap_errormsg());
 7635:         return (1,$currentphase);
 7636:     }
 7637:     my (undef,undef,$sequence)=
 7638: 	&Apache::lonnet::decode_symb($env{'form.selectpage'});
 7639: 
 7640:     my $map=$navmap->getResourceByUrl($sequence);
 7641: 
 7642:     $r->print('<input type="hidden" name="validate_sequence_exam"
 7643:                                     value="ignore" />');
 7644:     if ($env{'form.validate_sequence_exam'} ne 'ignore') {
 7645: 	my @resources=
 7646: 	    $navmap->retrieveResources($map,\&scantron_filter_not_exam,1,0);
 7647: 	if (@resources) {
 7648: 	    $r->print('<p class="LC_warning">'
 7649:                .&mt('Some resources in the sequence currently are not set to'
 7650:                    .' exam mode. Grading these resources currently may not'
 7651:                    .' work correctly.')
 7652:                .'</p>'
 7653:             );
 7654: 	    return (1,$currentphase);
 7655: 	}
 7656:     }
 7657: 
 7658:     return (0,$currentphase+1);
 7659: }
 7660: 
 7661: 
 7662: 
 7663: sub scantron_validate_ID {
 7664:     my ($r,$currentphase) = @_;
 7665:     
 7666:     #get student info
 7667:     my $classlist=&Apache::loncoursedata::get_classlist();
 7668:     my %idmap=&username_to_idmap($classlist);
 7669: 
 7670:     #get scantron line setup
 7671:     my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
 7672:     my ($scanlines,$scan_data)=&scantron_getfile();
 7673: 
 7674:     my $nav_error;
 7675:     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the bubble_lines.. array.
 7676:     if ($nav_error) {
 7677:         $r->print(&navmap_errormsg());
 7678:         return(1,$currentphase);
 7679:     }
 7680: 
 7681:     my %found=('ids'=>{},'usernames'=>{});
 7682:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 7683: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 7684: 	if ($line=~/^[\s\cz]*$/) { next; }
 7685: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
 7686: 						 $scan_data);
 7687: 	my $id=$$scan_record{'scantron.ID'};
 7688: 	my $found;
 7689: 	foreach my $checkid (keys(%idmap)) {
 7690: 	    if (lc($checkid) eq lc($id)) { $found=$checkid;last; }
 7691: 	}
 7692: 	if ($found) {
 7693: 	    my $username=$idmap{$found};
 7694: 	    if ($found{'ids'}{$found}) {
 7695: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
 7696: 					 $line,'duplicateID',$found);
 7697: 		return(1,$currentphase);
 7698: 	    } elsif ($found{'usernames'}{$username}) {
 7699: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
 7700: 					 $line,'duplicateID',$username);
 7701: 		return(1,$currentphase);
 7702: 	    }
 7703: 	    #FIXME store away line we previously saw the ID on to use above
 7704: 	    $found{'ids'}{$found}++;
 7705: 	    $found{'usernames'}{$username}++;
 7706: 	} else {
 7707: 	    if ($id =~ /^\s*$/) {
 7708: 		my $username=&scan_data($scan_data,"$i.user");
 7709: 		if (defined($username) && $found{'usernames'}{$username}) {
 7710: 		    &scantron_get_correction($r,$i,$scan_record,
 7711: 					     \%scantron_config,
 7712: 					     $line,'duplicateID',$username);
 7713: 		    return(1,$currentphase);
 7714: 		} elsif (!defined($username)) {
 7715: 		    &scantron_get_correction($r,$i,$scan_record,
 7716: 					     \%scantron_config,
 7717: 					     $line,'incorrectID');
 7718: 		    return(1,$currentphase);
 7719: 		}
 7720: 		$found{'usernames'}{$username}++;
 7721: 	    } else {
 7722: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
 7723: 					 $line,'incorrectID');
 7724: 		return(1,$currentphase);
 7725: 	    }
 7726: 	}
 7727:     }
 7728: 
 7729:     return (0,$currentphase+1);
 7730: }
 7731: 
 7732: 
 7733: sub scantron_get_correction {
 7734:     my ($r,$i,$scan_record,$scan_config,$line,$error,$arg,
 7735:         $randomorder,$randompick,$respnumlookup,$startline)=@_;
 7736: #FIXME in the case of a duplicated ID the previous line, probably need
 7737: #to show both the current line and the previous one and allow skipping
 7738: #the previous one or the current one
 7739: 
 7740:     if ( $$scan_record{'scantron.PaperID'} =~ /\S/) {
 7741:         $r->print(
 7742:             '<p class="LC_warning">'
 7743:            .&mt('An error was detected ([_1]) for PaperID [_2]',
 7744:                 "<b>$error</b>",
 7745:                 '<tt>'.$$scan_record{'scantron.PaperID'}.'</tt>')
 7746:            ."</p> \n");
 7747:     } else {
 7748:         $r->print(
 7749:             '<p class="LC_warning">'
 7750:            .&mt('An error was detected ([_1]) in scanline [_2] [_3]',
 7751:                 "<b>$error</b>", $i, "<pre>$line</pre>")
 7752:            ."</p> \n");
 7753:     }
 7754:     my $message =
 7755:         '<p>'
 7756:        .&mt('The ID on the form is [_1]',
 7757:             "<tt>$$scan_record{'scantron.ID'}</tt>")
 7758:        .'<br />'
 7759:        .&mt('The name on the paper is [_1], [_2]',
 7760:             $$scan_record{'scantron.LastName'},
 7761:             $$scan_record{'scantron.FirstName'})
 7762:        .'</p>';
 7763: 
 7764:     $r->print('<input type="hidden" name="scantron_corrections" value="'.$error.'" />'."\n");
 7765:     $r->print('<input type="hidden" name="scantron_line" value="'.$i.'" />'."\n");
 7766:                            # Array populated for doublebubble or
 7767:     my @lines_to_correct;  # missingbubble errors to build javascript
 7768:                            # to validate radio button checking   
 7769: 
 7770:     if ($error =~ /ID$/) {
 7771: 	if ($error eq 'incorrectID') {
 7772: 	    $r->print('<p class="LC_warning">'.&mt("The encoded ID is not in the classlist").
 7773: 		      "</p>\n");
 7774: 	} elsif ($error eq 'duplicateID') {
 7775: 	    $r->print('<p class="LC_warning">'.&mt("The encoded ID has also been used by a previous paper [_1]",$arg)."</p>\n");
 7776: 	}
 7777: 	$r->print($message);
 7778: 	$r->print("<p>".&mt("How should I handle this?")." <br /> \n");
 7779: 	$r->print("\n<ul><li> ");
 7780: 	#FIXME it would be nice if this sent back the user ID and
 7781: 	#could do partial userID matches
 7782: 	$r->print(&Apache::loncommon::selectstudent_link('scantronupload',
 7783: 				       'scantron_username','scantron_domain'));
 7784: 	$r->print(": <input type='text' name='scantron_username' value='' />");
 7785: 	$r->print("\n:\n".
 7786: 		 &Apache::loncommon::select_dom_form($env{'request.role.domain'},'scantron_domain'));
 7787: 
 7788: 	$r->print('</li>');
 7789:     } elsif ($error =~ /CODE$/) {
 7790: 	if ($error eq 'incorrectCODE') {
 7791: 	    $r->print('<p class="LC_warning">'.&mt("The encoded CODE is not in the list of possible CODEs.")."</p>\n");
 7792: 	} elsif ($error eq 'duplicateCODE') {
 7793: 	    $r->print('<p class="LC_warning">'.&mt("The encoded CODE has also been used by a previous paper [_1], and CODEs are supposed to be unique.",join(', ',@{$arg}))."</p>\n");
 7794: 	}
 7795:         $r->print("<p>".&mt('The CODE on the form is [_1]',
 7796:                             "<tt>'$$scan_record{'scantron.CODE'}'</tt>")
 7797:                  ."</p>\n");
 7798: 	$r->print($message);
 7799: 	$r->print("<p>".&mt("How should I handle this?")."</p>\n");
 7800: 	$r->print("\n<br /> ");
 7801: 	my $i=0;
 7802: 	if ($error eq 'incorrectCODE' 
 7803: 	    && $$scan_record{'scantron.CODE'}=~/\S/ ) {
 7804: 	    my ($max,$closest)=&scantron_get_closely_matching_CODEs($arg,$$scan_record{'scantron.CODE'});
 7805: 	    if ($closest > 0) {
 7806: 		foreach my $testcode (@{$closest}) {
 7807: 		    my $checked='';
 7808: 		    if (!$i) { $checked=' checked="checked"'; }
 7809: 		    $r->print("
 7810:    <label>
 7811:        <input type='radio' name='scantron_CODE_resolution' value='use_closest_$i'$checked />
 7812:        ".&mt("Use the similar CODE [_1] instead.",
 7813: 	    "<b><tt>".$testcode."</tt></b>")."
 7814:     </label>
 7815:     <input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");
 7816: 		    $r->print("\n<br />");
 7817: 		    $i++;
 7818: 		}
 7819: 	    }
 7820: 	}
 7821: 	if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
 7822: 	    my $checked; if (!$i) { $checked=' checked="checked"'; }
 7823: 	    $r->print("
 7824:     <label>
 7825:         <input type='radio' name='scantron_CODE_resolution' value='use_unfound'$checked />
 7826:        ".&mt("Use the CODE [_1] that was on the paper, ignoring the error.",
 7827: 	     "<b><tt>".$$scan_record{'scantron.CODE'}."</tt></b>")."
 7828:     </label>");
 7829: 	    $r->print("\n<br />");
 7830: 	}
 7831: 
 7832: 	$r->print(&Apache::lonhtmlcommon::scripttag(<<ENDSCRIPT));
 7833: function change_radio(field) {
 7834:     var slct=document.scantronupload.scantron_CODE_resolution;
 7835:     var i;
 7836:     for (i=0;i<slct.length;i++) {
 7837:         if (slct[i].value==field) { slct[i].checked=true; }
 7838:     }
 7839: }
 7840: ENDSCRIPT
 7841: 	my $href="/adm/pickcode?".
 7842: 	   "form=".&escape("scantronupload").
 7843: 	   "&scantron_format=".&escape($env{'form.scantron_format'}).
 7844: 	   "&scantron_CODElist=".&escape($env{'form.scantron_CODElist'}).
 7845: 	   "&curCODE=".&escape($$scan_record{'scantron.CODE'}).
 7846: 	   "&scantron_selectfile=".&escape($env{'form.scantron_selectfile'});
 7847: 	if ($env{'form.scantron_CODElist'} =~ /\S/) { 
 7848: 	    $r->print("
 7849:     <label>
 7850:        <input type='radio' name='scantron_CODE_resolution' value='use_found' />
 7851:        ".&mt("[_1]Select[_2] a CODE from the list of all CODEs and use it.",
 7852: 	     "<a target='_blank' href='$href'>","</a>")."
 7853:     </label> 
 7854:     ".&mt("Selected CODE is [_1]",'<input readonly="readonly" type="text" size="8" name="scantron_CODE_selectedvalue" onfocus="javascript:change_radio(\'use_found\')" onchange="javascript:change_radio(\'use_found\')" />'));
 7855: 	    $r->print("\n<br />");
 7856: 	}
 7857: 	$r->print("
 7858:     <label>
 7859:        <input type='radio' name='scantron_CODE_resolution' value='use_typed' />
 7860:        ".&mt("Use [_1] as the CODE.",
 7861: 	     "</label><input type='text' size='8' name='scantron_CODE_newvalue' onfocus=\"javascript:change_radio('use_typed')\" onkeypress=\"javascript:change_radio('use_typed')\" />"));
 7862: 	$r->print("\n<br /><br />");
 7863:     } elsif ($error eq 'doublebubble') {
 7864: 	$r->print('<p class="LC_warning">'.&mt("There have been multiple bubbles scanned for some question(s)")."</p>\n");
 7865: 
 7866: 	# The form field scantron_questions is acutally a list of line numbers.
 7867: 	# represented by this form so:
 7868: 
 7869: 	my $line_list = &questions_to_line_list($arg,$randomorder,$randompick,
 7870:                                                 $respnumlookup,$startline);
 7871: 
 7872: 	$r->print('<input type="hidden" name="scantron_questions" value="'.
 7873: 		  $line_list.'" />');
 7874: 	$r->print($message);
 7875: 	$r->print("<p>".&mt("Please indicate which bubble should be used for grading")."</p>");
 7876: 	foreach my $question (@{$arg}) {
 7877: 	    my @linenums = &prompt_for_corrections($r,$question,$scan_config,
 7878:                                                    $scan_record, $error,
 7879:                                                    $randomorder,$randompick,
 7880:                                                    $respnumlookup,$startline);
 7881:             push(@lines_to_correct,@linenums);
 7882: 	}
 7883:         $r->print(&verify_bubbles_checked(@lines_to_correct));
 7884:     } elsif ($error eq 'missingbubble') {
 7885: 	$r->print('<p class="LC_warning">'.&mt("There have been [_1]no[_2] bubbles scanned for some question(s)",'<b>','</b>')."</p>\n");
 7886: 	$r->print($message);
 7887: 	$r->print("<p>".&mt("Please indicate which bubble should be used for grading.")."</p>");
 7888: 	$r->print(&mt("Some questions have no scanned bubbles.")."\n");
 7889: 
 7890: 	# The form field scantron_questions is actually a list of line numbers not
 7891: 	# a list of question numbers. Therefore:
 7892: 	#
 7893: 	
 7894: 	my $line_list = &questions_to_line_list($arg,$randomorder,$randompick,
 7895:                                                 $respnumlookup,$startline);
 7896: 
 7897: 	$r->print('<input type="hidden" name="scantron_questions" value="'.
 7898: 		  $line_list.'" />');
 7899: 	foreach my $question (@{$arg}) {
 7900: 	    my @linenums = &prompt_for_corrections($r,$question,$scan_config,
 7901:                                                    $scan_record, $error,
 7902:                                                    $randomorder,$randompick,
 7903:                                                    $respnumlookup,$startline);
 7904:             push(@lines_to_correct,@linenums);
 7905: 	}
 7906:         $r->print(&verify_bubbles_checked(@lines_to_correct));
 7907:     } else {
 7908: 	$r->print("\n<ul>");
 7909:     }
 7910:     $r->print("\n</li></ul>");
 7911: }
 7912: 
 7913: sub verify_bubbles_checked {
 7914:     my (@ansnums) = @_;
 7915:     my $ansnumstr = join('","',@ansnums);
 7916:     my $warning = &mt("A bubble or 'No bubble' selection has not been made for one or more lines.");
 7917:     &js_escape(\$warning);
 7918:     my $output = &Apache::lonhtmlcommon::scripttag(<<ENDSCRIPT);
 7919: function verify_bubble_radio(form) {
 7920:     var ansnumArray = new Array ("$ansnumstr");
 7921:     var need_bubble_count = 0;
 7922:     for (var i=0; i<ansnumArray.length; i++) {
 7923:         if (form.elements["scantron_correct_Q_"+ansnumArray[i]].length > 1) {
 7924:             var bubble_picked = 0; 
 7925:             for (var j=0; j<form.elements["scantron_correct_Q_"+ansnumArray[i]].length; j++) {
 7926:                 if (form.elements["scantron_correct_Q_"+ansnumArray[i]][j].checked == true) {
 7927:                     bubble_picked = 1;
 7928:                 }
 7929:             }
 7930:             if (bubble_picked == 0) {
 7931:                 need_bubble_count ++;
 7932:             }
 7933:         }
 7934:     }
 7935:     if (need_bubble_count) {
 7936:         alert("$warning");
 7937:         return;
 7938:     }
 7939:     form.submit(); 
 7940: }
 7941: ENDSCRIPT
 7942:     return $output;
 7943: }
 7944: 
 7945: =pod
 7946: 
 7947: =item  questions_to_line_list
 7948: 
 7949: Converts a list of questions into a string of comma separated
 7950: line numbers in the answer sheet used by the questions.  This is
 7951: used to fill in the scantron_questions form field.
 7952: 
 7953:   Arguments:
 7954:      questions    - Reference to an array of questions.
 7955:      randomorder  - True if randomorder in use.
 7956:      randompick   - True if randompick in use.
 7957:      respnumlookup - Reference to HASH mapping question numbers in bubble lines
 7958:                      for current line to question number used for same question
 7959:                      in "Master Seqence" (as seen by Course Coordinator).
 7960:      startline    - Reference to hash where key is question number (0 is first)
 7961:                     and key is number of first bubble line for current student
 7962:                     or code-based randompick and/or randomorder.
 7963: 
 7964: =cut
 7965: 
 7966: 
 7967: sub questions_to_line_list {
 7968:     my ($questions,$randomorder,$randompick,$respnumlookup,$startline) = @_;
 7969:     my @lines;
 7970: 
 7971:     foreach my $item (@{$questions}) {
 7972:         my $question = $item;
 7973:         my ($first,$count,$last);
 7974:         if ($item =~ /^(\d+)\.(\d+)$/) {
 7975:             $question = $1;
 7976:             my $subquestion = $2;
 7977:             my $responsenum = $question-1;
 7978:             if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
 7979:                 $responsenum = $respnumlookup->{$question-1};
 7980:                 if (ref($startline) eq 'HASH') {
 7981:                     $first = $startline->{$question-1} + 1;
 7982:                 }
 7983:             } else {
 7984:                 $first = $first_bubble_line{$responsenum} + 1;
 7985:             }
 7986:             my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
 7987:             my $subcount = 1;
 7988:             while ($subcount<$subquestion) {
 7989:                 $first += $subans[$subcount-1];
 7990:                 $subcount ++;
 7991:             }
 7992:             $count = $subans[$subquestion-1];
 7993:         } else {
 7994:             my $responsenum = $question-1;
 7995:             if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
 7996:                 $responsenum = $respnumlookup->{$question-1};
 7997:                 if (ref($startline) eq 'HASH') {
 7998:                     $first = $startline->{$question-1} + 1;
 7999:                 }
 8000:             } else {
 8001:                 $first = $first_bubble_line{$responsenum} + 1;
 8002:             }
 8003:             $count   = $bubble_lines_per_response{$responsenum};
 8004:         }
 8005:         $last = $first+$count-1;
 8006:         push(@lines, ($first..$last));
 8007:     }
 8008:     return join(',', @lines);
 8009: }
 8010: 
 8011: =pod 
 8012: 
 8013: =item prompt_for_corrections
 8014: 
 8015: Prompts for a potentially multiline correction to the
 8016: user's bubbling (factors out common code from scantron_get_correction
 8017: for multi and missing bubble cases).
 8018: 
 8019:  Arguments:
 8020:    $r           - Apache request object.
 8021:    $question    - The question number to prompt for.
 8022:    $scan_config - The scantron file configuration hash.
 8023:    $scan_record - Reference to the hash that has the the parsed scanlines.
 8024:    $error       - Type of error
 8025:    $randomorder - True if randomorder in use.
 8026:    $randompick  - True if randompick in use.
 8027:    $respnumlookup - Reference to HASH mapping question numbers in bubble lines
 8028:                     for current line to question number used for same question
 8029:                     in "Master Seqence" (as seen by Course Coordinator).
 8030:    $startline   - Reference to hash where key is question number (0 is first)
 8031:                   and value is number of first bubble line for current student
 8032:                   or code-based randompick and/or randomorder.
 8033: 
 8034:  Implicit inputs:
 8035:    %bubble_lines_per_response   - Starting line numbers for each question.
 8036:                                   Numbered from 0 (but question numbers are from
 8037:                                   1.
 8038:    %first_bubble_line           - Starting bubble line for each question.
 8039:    %subdivided_bubble_lines     - optionresponse, matchresponse and rankresponse 
 8040:                                   type problems render as separate sub-questions, 
 8041:                                   in exam mode. This hash contains a 
 8042:                                   comma-separated list of the lines per 
 8043:                                   sub-question.
 8044:    %responsetype_per_response   - essayresponse, formularesponse,
 8045:                                   stringresponse, imageresponse, reactionresponse,
 8046:                                   and organicresponse type problem parts can have
 8047:                                   multiple lines per response if the weight
 8048:                                   assigned exceeds 10.  In this case, only
 8049:                                   one bubble per line is permitted, but more 
 8050:                                   than one line might contain bubbles, e.g.
 8051:                                   bubbling of: line 1 - J, line 2 - J, 
 8052:                                   line 3 - B would assign 22 points.  
 8053: 
 8054: =cut
 8055: 
 8056: sub prompt_for_corrections {
 8057:     my ($r, $question, $scan_config, $scan_record, $error, $randomorder,
 8058:         $randompick, $respnumlookup, $startline) = @_;
 8059:     my ($current_line,$lines);
 8060:     my @linenums;
 8061:     my $questionnum = $question;
 8062:     my ($first,$responsenum);
 8063:     if ($question =~ /^(\d+)\.(\d+)$/) {
 8064:         $question = $1;
 8065:         my $subquestion = $2;
 8066:         if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
 8067:             $responsenum = $respnumlookup->{$question-1};
 8068:             if (ref($startline) eq 'HASH') {
 8069:                 $first = $startline->{$question-1};
 8070:             }
 8071:         } else {
 8072:             $responsenum = $question-1;
 8073:             $first = $first_bubble_line{$responsenum};
 8074:         }
 8075:         $current_line = $first + 1 ;
 8076:         my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
 8077:         my $subcount = 1;
 8078:         while ($subcount<$subquestion) {
 8079:             $current_line += $subans[$subcount-1];
 8080:             $subcount ++;
 8081:         }
 8082:         $lines = $subans[$subquestion-1];
 8083:     } else {
 8084:         if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
 8085:             $responsenum = $respnumlookup->{$question-1};
 8086:             if (ref($startline) eq 'HASH') {
 8087:                 $first = $startline->{$question-1};
 8088:             }
 8089:         } else {
 8090:             $responsenum = $question-1;
 8091:             $first = $first_bubble_line{$responsenum};
 8092:         }
 8093:         $current_line = $first + 1;
 8094:         $lines        = $bubble_lines_per_response{$responsenum};
 8095:     }
 8096:     if ($lines > 1) {
 8097:         $r->print(&mt('The group of bubble lines below responds to a single question.').'<br />');
 8098:         if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
 8099:             ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
 8100:             ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
 8101:             ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
 8102:             ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
 8103:             ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
 8104:             $r->print(&mt("Although this particular question type requires handgrading, the instructions for this question in the bubblesheet exam directed students to leave [quant,_1,line] blank on their bubblesheets.",$lines).'<br /><br />'.&mt('A non-zero score can be assigned to the student during bubblesheet grading by selecting a bubble in at least one line.').'<br />'.&mt('The score for this question will be a sum of the numeric values for the selected bubbles from each line, where A=1 point, B=2 points etc.').'<br />'.&mt("To assign a score of zero for this question, mark all lines as 'No bubble'.").'<br /><br />');
 8105:         } else {
 8106:             $r->print(&mt("Select at most one bubble in a single line and select 'No Bubble' in all the other lines. ")."<br />");
 8107:         }
 8108:     }
 8109:     for (my $i =0; $i < $lines; $i++) {
 8110:         my $selected = $$scan_record{"scantron.$current_line.answer"};
 8111: 	&scantron_bubble_selector($r,$scan_config,$current_line,
 8112: 	        		  $questionnum,$error,split('', $selected));
 8113:         push(@linenums,$current_line);
 8114: 	$current_line++;
 8115:     }
 8116:     if ($lines > 1) {
 8117: 	$r->print("<hr /><br />");
 8118:     }
 8119:     return @linenums;
 8120: }
 8121: 
 8122: =pod
 8123: 
 8124: =item scantron_bubble_selector
 8125:   
 8126:    Generates the html radiobuttons to correct a single bubble line
 8127:    possibly showing the existing the selected bubbles if known
 8128: 
 8129:  Arguments:
 8130:     $r           - Apache request object
 8131:     $scan_config - hash from &Apache::lonnet::get_scantron_config()
 8132:     $line        - Number of the line being displayed.
 8133:     $questionnum - Question number (may include subquestion)
 8134:     $error       - Type of error.
 8135:     @selected    - Array of bubbles picked on this line.
 8136: 
 8137: =cut
 8138: 
 8139: sub scantron_bubble_selector {
 8140:     my ($r,$scan_config,$line,$questionnum,$error,@selected)=@_;
 8141:     my $max=$$scan_config{'Qlength'};
 8142: 
 8143:     my $scmode=$$scan_config{'Qon'};
 8144:     if ($scmode eq 'number' || $scmode eq 'letter') {
 8145:         if (($$scan_config{'BubblesPerRow'} =~ /^\d+$/) &&
 8146:             ($$scan_config{'BubblesPerRow'} > 0)) {
 8147:             $max=$$scan_config{'BubblesPerRow'};
 8148:             if (($scmode eq 'number') && ($max > 10)) {
 8149:                 $max = 10;
 8150:             } elsif (($scmode eq 'letter') && $max > 26) {
 8151:                 $max = 26;
 8152:             }
 8153:         } else {
 8154:             $max = 10;
 8155:         }
 8156:     }
 8157: 
 8158:     my @alphabet=('A'..'Z');
 8159:     $r->print(&Apache::loncommon::start_data_table().
 8160:               &Apache::loncommon::start_data_table_row());
 8161:     $r->print('<td rowspan="2" class="LC_leftcol_header">'.$line.'</td>');
 8162:     for (my $i=0;$i<$max+1;$i++) {
 8163: 	$r->print("\n".'<td align="center">');
 8164: 	if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }
 8165: 	else { $r->print('&nbsp;'); }
 8166: 	$r->print('</td>');
 8167:     }
 8168:     $r->print(&Apache::loncommon::end_data_table_row().
 8169:               &Apache::loncommon::start_data_table_row());
 8170:     for (my $i=0;$i<$max;$i++) {
 8171: 	$r->print("\n".
 8172: 		  '<td><label><input type="radio" name="scantron_correct_Q_'.
 8173: 		  $line.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");
 8174:     }
 8175:     my $nobub_checked = ' ';
 8176:     if ($error eq 'missingbubble') {
 8177:         $nobub_checked = ' checked = "checked" ';
 8178:     }
 8179:     $r->print("\n".'<td><label><input type="radio" name="scantron_correct_Q_'.
 8180: 	      $line.'" value="none"'.$nobub_checked.'/>'.&mt('No bubble').
 8181:               '</label>'."\n".'<input type="hidden" name="scantron_questionnum_Q_'.
 8182:               $line.'" value="'.$questionnum.'" /></td>');
 8183:     $r->print(&Apache::loncommon::end_data_table_row().
 8184:               &Apache::loncommon::end_data_table());
 8185: }
 8186: 
 8187: =pod
 8188: 
 8189: =item num_matches
 8190: 
 8191:    Counts the number of characters that are the same between the two arguments.
 8192: 
 8193:  Arguments:
 8194:    $orig - CODE from the scanline
 8195:    $code - CODE to match against
 8196: 
 8197:  Returns:
 8198:    $count - integer count of the number of same characters between the
 8199:             two arguments
 8200: 
 8201: =cut
 8202: 
 8203: sub num_matches {
 8204:     my ($orig,$code) = @_;
 8205:     my @code=split(//,$code);
 8206:     my @orig=split(//,$orig);
 8207:     my $same=0;
 8208:     for (my $i=0;$i<scalar(@code);$i++) {
 8209: 	if ($code[$i] eq $orig[$i]) { $same++; }
 8210:     }
 8211:     return $same;
 8212: }
 8213: 
 8214: =pod
 8215: 
 8216: =item scantron_get_closely_matching_CODEs
 8217: 
 8218:    Cycles through all CODEs and finds the set that has the greatest
 8219:    number of same characters as the provided CODE
 8220: 
 8221:  Arguments:
 8222:    $allcodes - hash ref returned by &get_codes()
 8223:    $CODE     - CODE from the current scanline
 8224: 
 8225:  Returns:
 8226:    2 element list
 8227:     - first elements is number of how closely matching the best fit is 
 8228:       (5 means best set has 5 matching characters)
 8229:     - second element is an arrary ref containing the set of valid CODEs
 8230:       that best fit the passed in CODE
 8231: 
 8232: =cut
 8233: 
 8234: sub scantron_get_closely_matching_CODEs {
 8235:     my ($allcodes,$CODE)=@_;
 8236:     my @CODEs;
 8237:     foreach my $testcode (sort(keys(%{$allcodes}))) {
 8238: 	push(@{$CODEs[&num_matches($CODE,$testcode)]},$testcode);
 8239:     }
 8240: 
 8241:     return ($#CODEs,$CODEs[-1]);
 8242: }
 8243: 
 8244: =pod
 8245: 
 8246: =item get_codes
 8247: 
 8248:    Builds a hash which has keys of all of the valid CODEs from the selected
 8249:    set of remembered CODEs.
 8250: 
 8251:  Arguments:
 8252:   $old_name - name of the set of remembered CODEs
 8253:   $cdom     - domain of the course
 8254:   $cnum     - internal course name
 8255: 
 8256:  Returns:
 8257:   %allcodes - keys are the valid CODEs, values are all 1
 8258: 
 8259: =cut
 8260: 
 8261: sub get_codes {
 8262:     my ($old_name, $cdom, $cnum) = @_;
 8263:     if (!$old_name) {
 8264: 	$old_name=$env{'form.scantron_CODElist'};
 8265:     }
 8266:     if (!$cdom) {
 8267: 	$cdom =$env{'course.'.$env{'request.course.id'}.'.domain'};
 8268:     }
 8269:     if (!$cnum) {
 8270: 	$cnum =$env{'course.'.$env{'request.course.id'}.'.num'};
 8271:     }
 8272:     my %result=&Apache::lonnet::get('CODEs',[$old_name,"type\0$old_name"],
 8273: 				    $cdom,$cnum);
 8274:     my %allcodes;
 8275:     if ($result{"type\0$old_name"} eq 'number') {
 8276: 	%allcodes=map {($_,1)} split(',',$result{$old_name});
 8277:     } else {
 8278: 	%allcodes=map {(&Apache::lonprintout::num_to_letters($_),1)} split(',',$result{$old_name});
 8279:     }
 8280:     return %allcodes;
 8281: }
 8282: 
 8283: =pod
 8284: 
 8285: =item scantron_validate_CODE
 8286: 
 8287:    Validates all scanlines in the selected file to not have any
 8288:    invalid or underspecified CODEs and that none of the codes are
 8289:    duplicated if this was requested.
 8290: 
 8291: =cut
 8292: 
 8293: sub scantron_validate_CODE {
 8294:     my ($r,$currentphase) = @_;
 8295:     my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
 8296:     if ($scantron_config{'CODElocation'} &&
 8297: 	$scantron_config{'CODEstart'} &&
 8298: 	$scantron_config{'CODElength'}) {
 8299: 	if (!defined($env{'form.scantron_CODElist'})) {
 8300: 	    &FIXME_blow_up()
 8301: 	}
 8302:     } else {
 8303: 	return (0,$currentphase+1);
 8304:     }
 8305:     
 8306:     my %usedCODEs;
 8307: 
 8308:     my %allcodes=&get_codes();
 8309: 
 8310:     my $nav_error;
 8311:     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the lines per response array.
 8312:     if ($nav_error) {
 8313:         $r->print(&navmap_errormsg());
 8314:         return(1,$currentphase);
 8315:     }
 8316: 
 8317:     my ($scanlines,$scan_data)=&scantron_getfile();
 8318:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 8319: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 8320: 	if ($line=~/^[\s\cz]*$/) { next; }
 8321: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
 8322: 						 $scan_data);
 8323: 	my $CODE=$$scan_record{'scantron.CODE'};
 8324: 	my $error=0;
 8325: 	if (!&Apache::lonnet::validCODE($CODE)) {
 8326: 	    &scantron_get_correction($r,$i,$scan_record,
 8327: 				     \%scantron_config,
 8328: 				     $line,'incorrectCODE',\%allcodes);
 8329: 	    return(1,$currentphase);
 8330: 	}
 8331: 	if (%allcodes && !exists($allcodes{$CODE}) 
 8332: 	    && !$$scan_record{'scantron.useCODE'}) {
 8333: 	    &scantron_get_correction($r,$i,$scan_record,
 8334: 				     \%scantron_config,
 8335: 				     $line,'incorrectCODE',\%allcodes);
 8336: 	    return(1,$currentphase);
 8337: 	}
 8338: 	if (exists($usedCODEs{$CODE}) 
 8339: 	    && $env{'form.scantron_CODEunique'} eq 'yes'
 8340: 	    && !$$scan_record{'scantron.CODE_ignore_dup'}) {
 8341: 	    &scantron_get_correction($r,$i,$scan_record,
 8342: 				     \%scantron_config,
 8343: 				     $line,'duplicateCODE',$usedCODEs{$CODE});
 8344: 	    return(1,$currentphase);
 8345: 	}
 8346: 	push(@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
 8347:     }
 8348:     return (0,$currentphase+1);
 8349: }
 8350: 
 8351: =pod
 8352: 
 8353: =item scantron_validate_doublebubble
 8354: 
 8355:    Validates all scanlines in the selected file to not have any
 8356:    bubble lines with multiple bubbles marked.
 8357: 
 8358: =cut
 8359: 
 8360: sub scantron_validate_doublebubble {
 8361:     my ($r,$currentphase) = @_;
 8362:     #get student info
 8363:     my $classlist=&Apache::loncoursedata::get_classlist();
 8364:     my %idmap=&username_to_idmap($classlist);
 8365:     my (undef,undef,$sequence)=
 8366:         &Apache::lonnet::decode_symb($env{'form.selectpage'});
 8367: 
 8368:     #get scantron line setup
 8369:     my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
 8370:     my ($scanlines,$scan_data)=&scantron_getfile();
 8371: 
 8372:     my $navmap = Apache::lonnavmaps::navmap->new();
 8373:     unless (ref($navmap)) {
 8374:         $r->print(&navmap_errormsg());
 8375:         return(1,$currentphase);
 8376:     }
 8377:     my $map=$navmap->getResourceByUrl($sequence);
 8378:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
 8379:     my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
 8380:         %grader_randomlists_by_symb,%orderedforcode,%respnumlookup,%startline);
 8381:     my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
 8382: 
 8383:     my $nav_error;
 8384:     if (ref($map)) {
 8385:         $randomorder = $map->randomorder();
 8386:         $randompick = $map->randompick();
 8387:         if ($randomorder || $randompick) {
 8388:             $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
 8389:             if ($nav_error) {
 8390:                 $r->print(&navmap_errormsg());
 8391:                 return(1,$currentphase);
 8392:             }
 8393:             &graders_resources_pass(\@resources,\%grader_partids_by_symb,
 8394:                                     \%grader_randomlists_by_symb,$bubbles_per_row);
 8395:         }
 8396:     } else {
 8397:         $r->print(&navmap_errormsg());
 8398:         return(1,$currentphase);
 8399:     }
 8400: 
 8401:     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the bubble line array.
 8402:     if ($nav_error) {
 8403:         $r->print(&navmap_errormsg());
 8404:         return(1,$currentphase);
 8405:     }
 8406: 
 8407:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 8408: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 8409: 	if ($line=~/^[\s\cz]*$/) { next; }
 8410: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
 8411: 						 $scan_data,undef,\%idmap,$randomorder,
 8412:                                                  $randompick,$sequence,\@master_seq,
 8413:                                                  \%symb_to_resource,\%grader_partids_by_symb,
 8414:                                                  \%orderedforcode,\%respnumlookup,\%startline);
 8415: 	if (!defined($$scan_record{'scantron.doubleerror'})) { next; }
 8416: 	&scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line,
 8417: 				 'doublebubble',
 8418: 				 $$scan_record{'scantron.doubleerror'},
 8419:                                  $randomorder,$randompick,\%respnumlookup,\%startline);
 8420:     	return (1,$currentphase);
 8421:     }
 8422:     return (0,$currentphase+1);
 8423: }
 8424: 
 8425: 
 8426: sub scantron_get_maxbubble {
 8427:     my ($nav_error,$scantron_config) = @_;
 8428:     if (defined($env{'form.scantron_maxbubble'}) &&
 8429: 	$env{'form.scantron_maxbubble'}) {
 8430: 	&restore_bubble_lines();
 8431: 	return $env{'form.scantron_maxbubble'};
 8432:     }
 8433: 
 8434:     my (undef, undef, $sequence) =
 8435: 	&Apache::lonnet::decode_symb($env{'form.selectpage'});
 8436: 
 8437:     my $navmap=Apache::lonnavmaps::navmap->new();
 8438:     unless (ref($navmap)) {
 8439:         if (ref($nav_error)) {
 8440:             $$nav_error = 1;
 8441:         }
 8442:         return;
 8443:     }
 8444:     my $map=$navmap->getResourceByUrl($sequence);
 8445:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
 8446:     my $bubbles_per_row = &bubblesheet_bubbles_per_row($scantron_config);
 8447: 
 8448:     &Apache::lonxml::clear_problem_counter();
 8449: 
 8450:     my $uname       = $env{'user.name'};
 8451:     my $udom        = $env{'user.domain'};
 8452:     my $cid         = $env{'request.course.id'};
 8453:     my $total_lines = 0;
 8454:     %bubble_lines_per_response = ();
 8455:     %first_bubble_line         = ();
 8456:     %subdivided_bubble_lines   = ();
 8457:     %responsetype_per_response = ();
 8458:     %masterseq_id_responsenum  = ();
 8459: 
 8460:     my $response_number = 0;
 8461:     my $bubble_line     = 0;
 8462:     foreach my $resource (@resources) {
 8463:         my $resid = $resource->id();
 8464:         my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,
 8465:                                                           $udom,undef,$bubbles_per_row);
 8466:         if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) {
 8467: 	    foreach my $part_id (@{$parts}) {
 8468:                 my $lines;
 8469: 
 8470: 	        # TODO - make this a persistent hash not an array.
 8471: 
 8472:                 # optionresponse, matchresponse and rankresponse type items 
 8473:                 # render as separate sub-questions in exam mode.
 8474:                 if (($analysis->{$part_id.'.type'} eq 'optionresponse') ||
 8475:                     ($analysis->{$part_id.'.type'} eq 'matchresponse') ||
 8476:                     ($analysis->{$part_id.'.type'} eq 'rankresponse')) {
 8477:                     my ($numbub,$numshown);
 8478:                     if ($analysis->{$part_id.'.type'} eq 'optionresponse') {
 8479:                         if (ref($analysis->{$part_id.'.options'}) eq 'ARRAY') {
 8480:                             $numbub = scalar(@{$analysis->{$part_id.'.options'}});
 8481:                         }
 8482:                     } elsif ($analysis->{$part_id.'.type'} eq 'matchresponse') {
 8483:                         if (ref($analysis->{$part_id.'.items'}) eq 'ARRAY') {
 8484:                             $numbub = scalar(@{$analysis->{$part_id.'.items'}});
 8485:                         }
 8486:                     } elsif ($analysis->{$part_id.'.type'} eq 'rankresponse') {
 8487:                         if (ref($analysis->{$part_id.'.foils'}) eq 'ARRAY') {
 8488:                             $numbub = scalar(@{$analysis->{$part_id.'.foils'}});
 8489:                         }
 8490:                     }
 8491:                     if (ref($analysis->{$part_id.'.shown'}) eq 'ARRAY') {
 8492:                         $numshown = scalar(@{$analysis->{$part_id.'.shown'}});
 8493:                     }
 8494:                     my $bubbles_per_row =
 8495:                         &bubblesheet_bubbles_per_row($scantron_config);
 8496:                     my $inner_bubble_lines = int($numbub/$bubbles_per_row);
 8497:                     if (($numbub % $bubbles_per_row) != 0) {
 8498:                         $inner_bubble_lines++;
 8499:                     }
 8500:                     for (my $i=0; $i<$numshown; $i++) {
 8501:                         $subdivided_bubble_lines{$response_number} .= 
 8502:                             $inner_bubble_lines.',';
 8503:                     }
 8504:                     $subdivided_bubble_lines{$response_number} =~ s/,$//;
 8505:                     $lines = $numshown * $inner_bubble_lines;
 8506:                 } else {
 8507:                     $lines = $analysis->{"$part_id.bubble_lines"};
 8508:                 }
 8509: 
 8510:                 $first_bubble_line{$response_number} = $bubble_line;
 8511: 	        $bubble_lines_per_response{$response_number} = $lines;
 8512:                 $responsetype_per_response{$response_number} = 
 8513:                     $analysis->{$part_id.'.type'};
 8514:                 $masterseq_id_responsenum{$resid.'_'.$part_id} = $response_number;
 8515: 	        $response_number++;
 8516: 
 8517: 	        $bubble_line +=  $lines;
 8518: 	        $total_lines +=  $lines;
 8519: 	    }
 8520:         }
 8521:     }
 8522:     &Apache::lonnet::delenv('scantron.');
 8523: 
 8524:     &save_bubble_lines();
 8525:     $env{'form.scantron_maxbubble'} =
 8526: 	$total_lines;
 8527:     return $env{'form.scantron_maxbubble'};
 8528: }
 8529: 
 8530: sub bubblesheet_bubbles_per_row {
 8531:     my ($scantron_config) = @_;
 8532:     my $bubbles_per_row;
 8533:     if (ref($scantron_config) eq 'HASH') {
 8534:         $bubbles_per_row = $scantron_config->{'BubblesPerRow'};
 8535:     }
 8536:     if ((!$bubbles_per_row) || ($bubbles_per_row < 1)) {
 8537:         $bubbles_per_row = 10;
 8538:     }
 8539:     return $bubbles_per_row;
 8540: }
 8541: 
 8542: sub scantron_validate_missingbubbles {
 8543:     my ($r,$currentphase) = @_;
 8544:     #get student info
 8545:     my $classlist=&Apache::loncoursedata::get_classlist();
 8546:     my %idmap=&username_to_idmap($classlist);
 8547:     my (undef,undef,$sequence)=
 8548:         &Apache::lonnet::decode_symb($env{'form.selectpage'});
 8549: 
 8550:     #get scantron line setup
 8551:     my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
 8552:     my ($scanlines,$scan_data)=&scantron_getfile();
 8553: 
 8554:     my $navmap = Apache::lonnavmaps::navmap->new();
 8555:     unless (ref($navmap)) {
 8556:         $r->print(&navmap_errormsg());
 8557:         return(1,$currentphase);
 8558:     }
 8559: 
 8560:     my $map=$navmap->getResourceByUrl($sequence);
 8561:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
 8562:     my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
 8563:         %grader_randomlists_by_symb,%orderedforcode,%respnumlookup,%startline);
 8564:     my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
 8565: 
 8566:     my $nav_error;
 8567:     if (ref($map)) {
 8568:         $randomorder = $map->randomorder();
 8569:         $randompick = $map->randompick();
 8570:         if ($randomorder || $randompick) {
 8571:             $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
 8572:             if ($nav_error) {
 8573:                 $r->print(&navmap_errormsg());
 8574:                 return(1,$currentphase);
 8575:             }
 8576:             &graders_resources_pass(\@resources,\%grader_partids_by_symb,
 8577:                                     \%grader_randomlists_by_symb,$bubbles_per_row);
 8578:         }
 8579:     } else {
 8580:         $r->print(&navmap_errormsg());
 8581:         return(1,$currentphase);
 8582:     }
 8583: 
 8584: 
 8585:     my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
 8586:     if ($nav_error) {
 8587:         $r->print(&navmap_errormsg());
 8588:         return(1,$currentphase);
 8589:     }
 8590: 
 8591:     if (!$max_bubble) { $max_bubble=2**31; }
 8592:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
 8593: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 8594: 	if ($line=~/^[\s\cz]*$/) { next; }
 8595:         my $scan_record =
 8596:             &scantron_parse_scanline($line,$i,\%scantron_config,$scan_data,undef,\%idmap,
 8597:                                      $randomorder,$randompick,$sequence,\@master_seq,
 8598:                                      \%symb_to_resource,\%grader_partids_by_symb,
 8599:                                      \%orderedforcode,\%respnumlookup,\%startline);
 8600: 	if (!defined($$scan_record{'scantron.missingerror'})) { next; }
 8601: 	my @to_correct;
 8602: 	
 8603: 	# Probably here's where the error is...
 8604: 
 8605: 	foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
 8606:             my $lastbubble;
 8607:             if ($missing =~ /^(\d+)\.(\d+)$/) {
 8608:                 my $question = $1;
 8609:                 my $subquestion = $2;
 8610:                 my ($first,$responsenum);
 8611:                 if ($randomorder || $randompick) {
 8612:                     $responsenum = $respnumlookup{$question-1};
 8613:                     $first = $startline{$question-1};
 8614:                 } else {
 8615:                     $responsenum = $question-1;
 8616:                     $first = $first_bubble_line{$responsenum};
 8617:                 }
 8618:                 if (!defined($first)) { next; }
 8619:                 my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
 8620:                 my $subcount = 1;
 8621:                 while ($subcount<$subquestion) {
 8622:                     $first += $subans[$subcount-1];
 8623:                     $subcount ++;
 8624:                 }
 8625:                 my $count = $subans[$subquestion-1];
 8626:                 $lastbubble = $first + $count;
 8627:             } else {
 8628:                 my ($first,$responsenum);
 8629:                 if ($randomorder || $randompick) {
 8630:                     $responsenum = $respnumlookup{$missing-1};
 8631:                     $first = $startline{$missing-1};
 8632:                 } else {
 8633:                     $responsenum = $missing-1;
 8634:                     $first = $first_bubble_line{$responsenum};
 8635:                 }
 8636:                 if (!defined($first)) { next; }
 8637:                 $lastbubble = $first + $bubble_lines_per_response{$responsenum};
 8638:             }
 8639:             if ($lastbubble > $max_bubble) { next; }
 8640: 	    push(@to_correct,$missing);
 8641: 	}
 8642: 	if (@to_correct) {
 8643: 	    &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
 8644: 				     $line,'missingbubble',\@to_correct,
 8645:                                      $randomorder,$randompick,\%respnumlookup,
 8646:                                      \%startline);
 8647: 	    return (1,$currentphase);
 8648: 	}
 8649: 
 8650:     }
 8651:     return (0,$currentphase+1);
 8652: }
 8653: 
 8654: sub hand_bubble_option {
 8655:     my (undef, undef, $sequence) =
 8656:         &Apache::lonnet::decode_symb($env{'form.selectpage'});
 8657:     return if ($sequence eq '');
 8658:     my $navmap = Apache::lonnavmaps::navmap->new();
 8659:     unless (ref($navmap)) {
 8660:         return;
 8661:     }
 8662:     my $needs_hand_bubbles;
 8663:     my $map=$navmap->getResourceByUrl($sequence);
 8664:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
 8665:     foreach my $res (@resources) {
 8666:         if (ref($res)) {
 8667:             if ($res->is_problem()) {
 8668:                 my $partlist = $res->parts();
 8669:                 foreach my $part (@{ $partlist }) {
 8670:                     my @types = $res->responseType($part);
 8671:                     if (grep(/^(chem|essay|image|formula|math|string|functionplot)$/,@types)) {
 8672:                         $needs_hand_bubbles = 1;
 8673:                         last;
 8674:                     }
 8675:                 }
 8676:             }
 8677:         }
 8678:     }
 8679:     if ($needs_hand_bubbles) {
 8680:         my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
 8681:         my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
 8682:         return &mt('The sequence to be graded contains response types which are handgraded.').'<p>'.
 8683:                &mt('If you have already graded these by bubbling sheets to indicate points awarded, [_1]what point value is assigned to a filled last bubble in each row?','<br />').
 8684:                '<label><input type="radio" name="scantron_lastbubblepoints" value="'.$bubbles_per_row.'" checked="checked" />'.&mt('[quant,_1,point]',$bubbles_per_row).'</label>&nbsp;'.&mt('or').'&nbsp;'.
 8685:                '<label><input type="radio" name="scantron_lastbubblepoints" value="0" />'.&mt('0 points').'</label></p>';
 8686:     }
 8687:     return;
 8688: }
 8689: 
 8690: sub scantron_process_students {
 8691:     my ($r,$symb) = @_;
 8692: 
 8693:     my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
 8694:     if (!$symb) {
 8695: 	return '';
 8696:     }
 8697:     my $default_form_data=&defaultFormData($symb);
 8698: 
 8699:     my %scantron_config=&Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
 8700:     my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
 8701:     my ($scanlines,$scan_data)=&scantron_getfile();
 8702:     my $classlist=&Apache::loncoursedata::get_classlist();
 8703:     my %idmap=&username_to_idmap($classlist);
 8704:     my $navmap=Apache::lonnavmaps::navmap->new();
 8705:     unless (ref($navmap)) {
 8706:         $r->print(&navmap_errormsg());
 8707:         return '';
 8708:     }
 8709:     my $map=$navmap->getResourceByUrl($sequence);
 8710:     my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
 8711:         %grader_randomlists_by_symb);
 8712:     if (ref($map)) {
 8713:         $randomorder = $map->randomorder();
 8714:         $randompick = $map->randompick();
 8715:     } else {
 8716:         $r->print(&navmap_errormsg());
 8717:         return '';
 8718:     }
 8719:     my $nav_error;
 8720:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
 8721:     if ($randomorder || $randompick) {
 8722:         $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
 8723:         if ($nav_error) {
 8724:             $r->print(&navmap_errormsg());
 8725:             return '';
 8726:         }
 8727:     }
 8728:     &graders_resources_pass(\@resources,\%grader_partids_by_symb,
 8729:                             \%grader_randomlists_by_symb,$bubbles_per_row);
 8730: 
 8731:     my ($uname,$udom);
 8732:     my $result= <<SCANTRONFORM;
 8733: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
 8734:   <input type="hidden" name="command" value="scantron_configphase" />
 8735:   $default_form_data
 8736: SCANTRONFORM
 8737:     $r->print($result);
 8738: 
 8739:     my @delayqueue;
 8740:     my (%completedstudents,%scandata);
 8741:     
 8742:     my $lock=&Apache::lonnet::set_lock(&mt('Grading bubblesheet exam'));
 8743:     my $count=&get_todo_count($scanlines,$scan_data);
 8744:     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count);
 8745:     &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,'Processing first student');
 8746:     $r->print('<br />');
 8747:     my $start=&Time::HiRes::time();
 8748:     my $i=-1;
 8749:     my $started;
 8750: 
 8751:     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse.
 8752:     if ($nav_error) {
 8753:         $r->print(&navmap_errormsg());
 8754:         return '';
 8755:     }
 8756: 
 8757:     # If an ssi failed in scantron_get_maxbubble, put an error message out to
 8758:     # the user and return.
 8759: 
 8760:     if ($ssi_error) {
 8761: 	$r->print("</form>");
 8762: 	&ssi_print_error($r);
 8763:         &Apache::lonnet::remove_lock($lock);
 8764: 	return '';		# Dunno why the other returns return '' rather than just returning.
 8765:     }
 8766: 
 8767:     my %lettdig = &Apache::lonnet::letter_to_digits();
 8768:     my $numletts = scalar(keys(%lettdig));
 8769:     my %orderedforcode;
 8770: 
 8771:     while ($i<$scanlines->{'count'}) {
 8772:  	($uname,$udom)=('','');
 8773:  	$i++;
 8774:  	my $line=&scantron_get_line($scanlines,$scan_data,$i);
 8775:  	if ($line=~/^[\s\cz]*$/) { next; }
 8776: 	if ($started) {
 8777: 	    &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
 8778: 	}
 8779: 	$started=1;
 8780:         my %respnumlookup = ();
 8781:         my %startline = ();
 8782:         my $total;
 8783:  	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
 8784:  						 $scan_data,undef,\%idmap,$randomorder,
 8785:                                                  $randompick,$sequence,\@master_seq,
 8786:                                                  \%symb_to_resource,\%grader_partids_by_symb,
 8787:                                                  \%orderedforcode,\%respnumlookup,\%startline,
 8788:                                                  \$total);
 8789:  	unless ($uname=&scantron_find_student($scan_record,$scan_data,
 8790:  					      \%idmap,$i)) {
 8791:   	    &scantron_add_delay(\@delayqueue,$line,
 8792:  				'Unable to find a student that matches',1);
 8793:  	    next;
 8794:   	}
 8795:  	if (exists $completedstudents{$uname}) {
 8796:  	    &scantron_add_delay(\@delayqueue,$line,
 8797:  				'Student '.$uname.' has multiple sheets',2);
 8798:  	    next;
 8799:  	}
 8800:         my $usec = $classlist->{$uname}->[&Apache::loncoursedata::CL_SECTION];
 8801:         my $user = $uname.':'.$usec;
 8802:   	($uname,$udom)=split(/:/,$uname);
 8803: 
 8804:         my $scancode;
 8805:         if ((exists($scan_record->{'scantron.CODE'})) &&
 8806:             (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
 8807:             $scancode = $scan_record->{'scantron.CODE'};
 8808:         } else {
 8809:             $scancode = '';
 8810:         }
 8811: 
 8812:         my @mapresources = @resources;
 8813:         if ($randomorder || $randompick) {
 8814:             @mapresources =
 8815:                 &users_order($user,$scancode,$sequence,\@master_seq,\%symb_to_resource,
 8816:                              \%orderedforcode);
 8817:         }
 8818:         my (%partids_by_symb,$res_error);
 8819:         foreach my $resource (@mapresources) {
 8820:             my $ressymb;
 8821:             if (ref($resource)) {
 8822:                 $ressymb = $resource->symb();
 8823:             } else {
 8824:                 $res_error = 1;
 8825:                 last;
 8826:             }
 8827:             if ((exists($grader_randomlists_by_symb{$ressymb})) ||
 8828:                 (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
 8829:                 my $currcode;
 8830:                 if (exists($grader_randomlists_by_symb{$ressymb})) {
 8831:                     $currcode = $scancode;
 8832:                 }
 8833:                 my ($analysis,$parts) =
 8834:                     &scantron_partids_tograde($resource,$env{'request.course.id'},
 8835:                                               $uname,$udom,undef,$bubbles_per_row,
 8836:                                               $currcode);
 8837:                 $partids_by_symb{$ressymb} = $parts;
 8838:             } else {
 8839:                 $partids_by_symb{$ressymb} = $grader_partids_by_symb{$ressymb};
 8840:             }
 8841:         }
 8842: 
 8843:         if ($res_error) {
 8844:             &scantron_add_delay(\@delayqueue,$line,
 8845:                                 'An error occurred while grading student '.$uname,2);
 8846:             next;
 8847:         }
 8848: 
 8849: 	&Apache::lonxml::clear_problem_counter();
 8850:   	&Apache::lonnet::appenv($scan_record);
 8851: 
 8852: 	if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
 8853: 	    &scantron_putfile($scanlines,$scan_data);
 8854: 	}
 8855: 	
 8856:         if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
 8857:                                    \@mapresources,\%partids_by_symb,
 8858:                                    $bubbles_per_row,$randomorder,$randompick,
 8859:                                    \%respnumlookup,\%startline) 
 8860:             eq 'ssi_error') {
 8861:             $ssi_error = 0; # So end of handler error message does not trigger.
 8862:             $r->print("</form>");
 8863:             &ssi_print_error($r);
 8864:             &Apache::lonnet::remove_lock($lock);
 8865:             return '';      # Why return ''?  Beats me.
 8866:         }
 8867: 
 8868:         if (($scancode) && ($randomorder || $randompick)) {
 8869:             my $parmresult =
 8870:                 &Apache::lonparmset::storeparm_by_symb($symb,
 8871:                                                        '0_examcode',2,$scancode,
 8872:                                                        'string_examcode',$uname,
 8873:                                                        $udom);
 8874:         }
 8875: 	$completedstudents{$uname}={'line'=>$line};
 8876:         if ($env{'form.verifyrecord'}) {
 8877:             my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
 8878:             if ($randompick) {
 8879:                 if ($total) {
 8880:                     $lastpos = $total*$scantron_config{'Qlength'};
 8881:                 }
 8882:             }
 8883: 
 8884:             my $studentdata = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
 8885:             chomp($studentdata);
 8886:             $studentdata =~ s/\r$//;
 8887:             my $studentrecord = '';
 8888:             my $counter = -1;
 8889:             foreach my $resource (@mapresources) {
 8890:                 my $ressymb = $resource->symb();
 8891:                 ($counter,my $recording) =
 8892:                     &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
 8893:                                              $counter,$studentdata,$partids_by_symb{$ressymb},
 8894:                                              \%scantron_config,\%lettdig,$numletts,$randomorder,
 8895:                                              $randompick,\%respnumlookup,\%startline);
 8896:                 $studentrecord .= $recording;
 8897:             }
 8898:             if ($studentrecord ne $studentdata) {
 8899:                 &Apache::lonxml::clear_problem_counter();
 8900:                 if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
 8901:                                            \@mapresources,\%partids_by_symb,
 8902:                                            $bubbles_per_row,$randomorder,$randompick,
 8903:                                            \%respnumlookup,\%startline)
 8904:                     eq 'ssi_error') {
 8905:                     $ssi_error = 0; # So end of handler error message does not trigger.
 8906:                     $r->print("</form>");
 8907:                     &ssi_print_error($r);
 8908:                     &Apache::lonnet::remove_lock($lock);
 8909:                     delete($completedstudents{$uname});
 8910:                     return '';
 8911:                 }
 8912:                 $counter = -1;
 8913:                 $studentrecord = '';
 8914:                 foreach my $resource (@mapresources) {
 8915:                     my $ressymb = $resource->symb();
 8916:                     ($counter,my $recording) =
 8917:                         &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
 8918:                                                  $counter,$studentdata,$partids_by_symb{$ressymb},
 8919:                                                  \%scantron_config,\%lettdig,$numletts,
 8920:                                                  $randomorder,$randompick,\%respnumlookup,
 8921:                                                  \%startline);
 8922:                     $studentrecord .= $recording;
 8923:                 }
 8924:                 if ($studentrecord ne $studentdata) {
 8925:                     $r->print('<p><span class="LC_warning">');
 8926:                     if ($scancode eq '') {
 8927:                         $r->print(&mt('Mismatch grading bubblesheet for user: [_1] with ID: [_2].',
 8928:                                   $uname.':'.$udom,$scan_record->{'scantron.ID'}));
 8929:                     } else {
 8930:                         $r->print(&mt('Mismatch grading bubblesheet for user: [_1] with ID: [_2] and CODE: [_3].',
 8931:                                   $uname.':'.$udom,$scan_record->{'scantron.ID'},$scancode));
 8932:                     }
 8933:                     $r->print('</span><br />'.&Apache::loncommon::start_data_table()."\n".
 8934:                               &Apache::loncommon::start_data_table_header_row()."\n".
 8935:                               '<th>'.&mt('Source').'</th><th>'.&mt('Bubbled responses').'</th>'.
 8936:                               &Apache::loncommon::end_data_table_header_row()."\n".
 8937:                               &Apache::loncommon::start_data_table_row().
 8938:                               '<td>'.&mt('Bubblesheet').'</td>'.
 8939:                               '<td><span class="LC_nobreak" style="white-space: pre;"><tt>'.$studentdata.'</tt></span></td>'.
 8940:                               &Apache::loncommon::end_data_table_row().
 8941:                               &Apache::loncommon::start_data_table_row().
 8942:                               '<td>'.&mt('Stored submissions').'</td>'.
 8943:                               '<td><span class="LC_nobreak" style="white-space: pre;"><tt>'.$studentrecord.'</tt></span></td>'."\n".
 8944:                               &Apache::loncommon::end_data_table_row().
 8945:                               &Apache::loncommon::end_data_table().'</p>');
 8946:                 } else {
 8947:                     $r->print('<br /><span class="LC_warning">'.
 8948:                              &mt('A second grading pass was needed for user: [_1] with ID: [_2], because a mismatch was seen on the first pass.',$uname.':'.$udom,$scan_record->{'scantron.ID'}).'<br />'.
 8949:                              &mt("As a consequence, this user's submission history records two tries.").
 8950:                                  '</span><br />');
 8951:                 }
 8952:             }
 8953:         }
 8954:         if (&Apache::loncommon::connection_aborted($r)) { last; }
 8955:     } continue {
 8956: 	&Apache::lonxml::clear_problem_counter();
 8957: 	&Apache::lonnet::delenv('scantron.');
 8958:     }
 8959:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
 8960:     &Apache::lonnet::remove_lock($lock);
 8961: #    my $lasttime = &Time::HiRes::time()-$start;
 8962: #    $r->print("<p>took $lasttime</p>");
 8963: 
 8964:     $r->print("</form>");
 8965:     return '';
 8966: }
 8967: 
 8968: sub graders_resources_pass {
 8969:     my ($resources,$grader_partids_by_symb,$grader_randomlists_by_symb,
 8970:         $bubbles_per_row) = @_;
 8971:     if ((ref($resources) eq 'ARRAY') && (ref($grader_partids_by_symb)) && 
 8972:         (ref($grader_randomlists_by_symb) eq 'HASH')) {
 8973:         foreach my $resource (@{$resources}) {
 8974:             my $ressymb = $resource->symb();
 8975:             my ($analysis,$parts) =
 8976:                 &scantron_partids_tograde($resource,$env{'request.course.id'},
 8977:                                           $env{'user.name'},$env{'user.domain'},
 8978:                                           1,$bubbles_per_row);
 8979:             $grader_partids_by_symb->{$ressymb} = $parts;
 8980:             if (ref($analysis) eq 'HASH') {
 8981:                 if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') {
 8982:                     $grader_randomlists_by_symb->{$ressymb} =
 8983:                         $analysis->{'parts_withrandomlist'};
 8984:                 }
 8985:             }
 8986:         }
 8987:     }
 8988:     return;
 8989: }
 8990: 
 8991: =pod
 8992: 
 8993: =item users_order
 8994: 
 8995:   Returns array of resources in current map, ordered based on either CODE,
 8996:   if this is a CODEd exam, or based on student's identity if this is a
 8997:   "NAMEd" exam.
 8998: 
 8999:   Should be used when randomorder and/or randompick applied when the 
 9000:   corresponding exam was printed, prior to students completing bubblesheets 
 9001:   for the version of the exam the student received.
 9002: 
 9003: =cut
 9004: 
 9005: sub users_order  {
 9006:     my ($user,$scancode,$mapurl,$master_seq,$symb_to_resource,$orderedforcode) = @_;
 9007:     my @mapresources;
 9008:     unless ((ref($master_seq) eq 'ARRAY') && (ref($symb_to_resource) eq 'HASH')) {
 9009:         return @mapresources;
 9010:     }
 9011:     if ($scancode) {
 9012:         if ((ref($orderedforcode) eq 'HASH') && (ref($orderedforcode->{$scancode}) eq 'ARRAY')) {
 9013:             @mapresources = @{$orderedforcode->{$scancode}};
 9014:         } else {
 9015:             $env{'form.CODE'} = $scancode;
 9016:             my $actual_seq =
 9017:                 &Apache::lonprintout::master_seq_to_person_seq($mapurl,
 9018:                                                                $master_seq,
 9019:                                                                $user,$scancode,1);
 9020:             if (ref($actual_seq) eq 'ARRAY') {
 9021:                 @mapresources = map { $symb_to_resource->{$_}; } @{$actual_seq};
 9022:                 if (ref($orderedforcode) eq 'HASH') {
 9023:                     if (@mapresources > 0) {
 9024:                         $orderedforcode->{$scancode} = \@mapresources;
 9025:                     }
 9026:                 }
 9027:             }
 9028:             delete($env{'form.CODE'});
 9029:         }
 9030:     } else {
 9031:         my $actual_seq =
 9032:             &Apache::lonprintout::master_seq_to_person_seq($mapurl,
 9033:                                                            $master_seq,
 9034:                                                            $user,undef,1);
 9035:         if (ref($actual_seq) eq 'ARRAY') {
 9036:             @mapresources =
 9037:                 map { $symb_to_resource->{$_}; } @{$actual_seq};
 9038:         }
 9039:     }
 9040:     return @mapresources;
 9041: }
 9042: 
 9043: sub grade_student_bubbles {
 9044:     my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts,$bubbles_per_row,
 9045:         $randomorder,$randompick,$respnumlookup,$startline) = @_;
 9046:     my $uselookup = 0;
 9047:     if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH') &&
 9048:         (ref($startline) eq 'HASH')) {
 9049:         $uselookup = 1;
 9050:     }
 9051: 
 9052:     if (ref($resources) eq 'ARRAY') {
 9053:         my $count = 0;
 9054:         foreach my $resource (@{$resources}) {
 9055:             my $ressymb = $resource->symb();
 9056:             my %form = ('submitted'      => 'scantron',
 9057:                         'grade_target'   => 'grade',
 9058:                         'grade_username' => $uname,
 9059:                         'grade_domain'   => $udom,
 9060:                         'grade_courseid' => $env{'request.course.id'},
 9061:                         'grade_symb'     => $ressymb,
 9062:                         'CODE'           => $scancode
 9063:                        );
 9064:             if ($bubbles_per_row ne '') {
 9065:                 $form{'bubbles_per_row'} = $bubbles_per_row;
 9066:             }
 9067:             if ($env{'form.scantron_lastbubblepoints'} ne '') {
 9068:                 $form{'scantron_lastbubblepoints'} = $env{'form.scantron_lastbubblepoints'};
 9069:             }
 9070:             if (ref($parts) eq 'HASH') {
 9071:                 if (ref($parts->{$ressymb}) eq 'ARRAY') {
 9072:                     foreach my $part (@{$parts->{$ressymb}}) {
 9073:                         if ($uselookup) {
 9074:                             $form{'scantron_questnum_start.'.$part} = $startline->{$count} + 1;
 9075:                         } else {
 9076:                             $form{'scantron_questnum_start.'.$part} =
 9077:                                 1+$env{'form.scantron.first_bubble_line.'.$count};
 9078:                         }
 9079:                         $count++;
 9080:                     }
 9081:                 }
 9082:             }
 9083:             my $result=&ssi_with_retries($resource->src(),$ssi_retries,%form);
 9084:             return 'ssi_error' if ($ssi_error);
 9085:             last if (&Apache::loncommon::connection_aborted($r));
 9086:         }
 9087:     }
 9088:     return;
 9089: }
 9090: 
 9091: sub scantron_upload_scantron_data {
 9092:     my ($r,$symb) = @_;
 9093:     my $dom = $env{'request.role.domain'};
 9094:     my ($formatoptions,$formattitle,$formatjs) = &scantron_upload_dataformat($dom);
 9095:     my $domdesc = &Apache::lonnet::domain($dom,'description');
 9096:     $r->print(&Apache::loncommon::coursebrowser_javascript($dom));
 9097:     my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
 9098: 							  'domainid',
 9099: 							  'coursename',$dom);
 9100:     my $syllabuslink = '<a href="javascript:ToSyllabus();">'.&mt('Syllabus').'</a>'.
 9101:                        ('&nbsp'x2).&mt('(shows course personnel)');
 9102:     my $default_form_data=&defaultFormData($symb);
 9103:     my $nofile_alert = &mt('Please use the browse button to select a file from your local directory.');
 9104:     &js_escape(\$nofile_alert);
 9105:     my $nocourseid_alert = &mt("Please use the 'Select Course' link to open a separate window where you can search for a course to which a file can be uploaded.");
 9106:     &js_escape(\$nocourseid_alert);
 9107:     $r->print(&Apache::lonhtmlcommon::scripttag('
 9108:     function checkUpload(formname) {
 9109: 	if (formname.upfile.value == "") {
 9110: 	    alert("'.$nofile_alert.'");
 9111: 	    return false;
 9112: 	}
 9113:         if (formname.courseid.value == "") {
 9114:             alert("'.$nocourseid_alert.'");
 9115:             return false;
 9116:         }
 9117: 	formname.submit();
 9118:     }
 9119: 
 9120:     function ToSyllabus() {
 9121:         var cdom = '."'$dom'".';
 9122:         var cnum = document.rules.courseid.value;
 9123:         if (cdom == "" || cdom == null) {
 9124:             return;
 9125:         }
 9126:         if (cnum == "" || cnum == null) {
 9127:            return;
 9128:         }
 9129:         syllwin=window.open("/public/"+cdom+"/"+cnum+"/syllabus","LONCAPASyllabus",
 9130:                             "height=350,width=350,scrollbars=yes,menubar=no");
 9131:         return;
 9132:     }
 9133: 
 9134:     '.$formatjs.'
 9135: '));
 9136:     $r->print('
 9137: <h3>'.&mt('Send bubblesheet data to a course').'</h3>
 9138: 
 9139: <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
 9140: '.$default_form_data.
 9141:   &Apache::lonhtmlcommon::start_pick_box().
 9142:   &Apache::lonhtmlcommon::row_title(&mt('Course ID')).
 9143:   '<input name="courseid" type="text" size="30" />'.$select_link.
 9144:   &Apache::lonhtmlcommon::row_closure().
 9145:   &Apache::lonhtmlcommon::row_title(&mt('Course Name')).
 9146:   '<input name="coursename" type="text" size="30" />'.$syllabuslink.
 9147:   &Apache::lonhtmlcommon::row_closure().
 9148:   &Apache::lonhtmlcommon::row_title(&mt('Domain')).
 9149:   '<input name="domainid" type="hidden" />'.$domdesc.
 9150:   &Apache::lonhtmlcommon::row_closure());
 9151:     if ($formatoptions) {
 9152:         $r->print(&Apache::lonhtmlcommon::row_title($formattitle).$formatoptions.
 9153:                   &Apache::lonhtmlcommon::row_closure());
 9154:     }
 9155:     $r->print(
 9156:   &Apache::lonhtmlcommon::row_title(&mt('File to upload')).
 9157:   '<input type="file" name="upfile" size="50" />'.
 9158:   &Apache::lonhtmlcommon::row_closure(1).
 9159:   &Apache::lonhtmlcommon::end_pick_box().'<br />
 9160: 
 9161: <input name="command" value="scantronupload_save" type="hidden" />
 9162: <input type="button" onclick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
 9163: </form>
 9164: ');
 9165:     return '';
 9166: }
 9167: 
 9168: sub scantron_upload_dataformat {
 9169:     my ($dom) = @_;
 9170:     my ($formatoptions,$formattitle,$formatjs);
 9171:     $formatjs = <<'END';
 9172: function toggleScantab(form) {
 9173:    return;
 9174: }
 9175: END
 9176:     my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$dom);
 9177:     if (ref($domconfig{'scantron'}) eq 'HASH') {
 9178:         if (ref($domconfig{'scantron'}{'config'}) eq 'HASH') {
 9179:             if (keys(%{$domconfig{'scantron'}{'config'}}) > 1) {
 9180:                 if (($domconfig{'scantron'}{'config'}{'dat'}) &&
 9181:                     (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH')) {
 9182:                     if (ref($domconfig{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') {
 9183:                         if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}{'fields'}})) {
 9184:                             my ($onclick,$formatextra,$singleline);
 9185:                             my @lines = &Apache::lonnet::get_scantronformat_file();
 9186:                             my $count = 0;
 9187:                             foreach my $line (@lines) {
 9188:                                 next if ($line =~ /^#/);
 9189:                                 $singleline = $line;
 9190:                                 $count ++;
 9191:                             }
 9192:                             if ($count > 1) {
 9193:                                 $formatextra = '<div style="display:none" id="bubbletype">'.
 9194:                                                '<span class="LC_nobreak">'.
 9195:                                                &mt('Bubblesheet type:').'&nbsp;'.
 9196:                                                &scantron_scantab().'</span></div>';
 9197:                                 $onclick = ' onclick="toggleScantab(this.form);"';
 9198:                                 $formatjs = <<"END";
 9199: function toggleScantab(form) {
 9200:     var divid = 'bubbletype';
 9201:     if (document.getElementById(divid)) {
 9202:         var radioname = 'fileformat';
 9203:         var num = form.elements[radioname].length;
 9204:         if (num) {
 9205:             for (var i=0; i<num; i++) {
 9206:                 if (form.elements[radioname][i].checked) {
 9207:                     var chosen = form.elements[radioname][i].value;
 9208:                     if (chosen == 'dat') {
 9209:                         document.getElementById(divid).style.display = 'none';
 9210:                     } else if (chosen == 'csv') {
 9211:                         document.getElementById(divid).style.display = 'block';
 9212:                     }
 9213:                 }
 9214:             }
 9215:         }
 9216:     }
 9217:     return;
 9218: }
 9219: 
 9220: END
 9221:                             } elsif ($count == 1) {
 9222:                                 my $formatname = (split(/:/,$singleline,2))[0];
 9223:                                 $formatextra = '<input type="hidden" name="scantron_format" value="'.$formatname.'" />';
 9224:                             }
 9225:                             $formattitle = &mt('File format');
 9226:                             $formatoptions = '<label><input name="fileformat" type="radio" value="dat" checked="checked"'.$onclick.' />'.
 9227:                                              &mt('Plain Text (no delimiters)').
 9228:                                              '</label>'.('&nbsp;'x2).
 9229:                                              '<label><input name="fileformat" type="radio" value="csv"'.$onclick.' />'.
 9230:                                              &mt('Comma separated values').'</label>'.$formatextra;
 9231:                         }
 9232:                     }
 9233:                 }
 9234:             } elsif (keys(%{$domconfig{'scantron'}{'config'}}) == 1) {
 9235:                 if (ref($domconfig{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') {
 9236:                     if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}{'fields'}})) {
 9237:                         $formattitle = &mt('Bubblesheet type');
 9238:                         $formatoptions = &scantron_scantab();
 9239:                     }
 9240:                 }
 9241:             }
 9242:         }
 9243:     }
 9244:     return ($formatoptions,$formattitle,$formatjs);
 9245: }
 9246: 
 9247: sub scantron_upload_scantron_data_save {
 9248:     my ($r,$symb) = @_;
 9249:     my $doanotherupload=
 9250: 	'<br /><form action="/adm/grades" method="post">'."\n".
 9251: 	'<input type="hidden" name="command" value="scantronupload" />'."\n".
 9252: 	'<input type="submit" name="submit" value="'.&mt('Do Another Upload').'" />'."\n".
 9253: 	'</form>'."\n";
 9254:     if (!&Apache::lonnet::allowed('usc',$env{'form.domainid'}) &&
 9255: 	!&Apache::lonnet::allowed('usc',
 9256: 			    $env{'form.domainid'}.'_'.$env{'form.courseid'})) {
 9257: 	$r->print(&mt("You are not allowed to upload bubblesheet data to the requested course.")."<br />");
 9258:         unless ($symb) {
 9259: 	    $r->print($doanotherupload);
 9260: 	}
 9261: 	return '';
 9262:     }
 9263:     my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
 9264:     my $uploadedfile;
 9265:     $r->print('<p>'.&mt("Uploading file to [_1]",$coursedata{'description'}).'</p>');
 9266:     if (length($env{'form.upfile'}) < 2) {
 9267:         $r->print(
 9268:             &Apache::lonhtmlcommon::confirm_success(
 9269:                 &mt('The file: [_1] you attempted to upload contained no information. Please check that you entered the correct filename.',
 9270:                         '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'),1));
 9271:     } else {
 9272:         my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$env{'form.domainid'});
 9273:         my $parser;
 9274:         if (ref($domconfig{'scantron'}) eq 'HASH') {
 9275:             if (ref($domconfig{'scantron'}{'config'}) eq 'HASH') {
 9276:                 my $is_csv;
 9277:                 my @possibles = keys(%{$domconfig{'scantron'}{'config'}});
 9278:                 if (@possibles > 1) {
 9279:                     if ($env{'form.fileformat'} eq 'csv') {
 9280:                         if (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH') {
 9281:                             if (ref($domconfig{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') {
 9282:                                 if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}{'fields'}}) > 1) {
 9283:                                     $is_csv = 1;
 9284:                                 }
 9285:                             }
 9286:                         }
 9287:                     }
 9288:                 } elsif (@possibles == 1) {
 9289:                     if (ref($domconfig{'scantron'}{'config'}{'csv'}) eq 'HASH') {
 9290:                         if (ref($domconfig{'scantron'}{'config'}{'csv'}{'fields'}) eq 'HASH') {
 9291:                             if (keys(%{$domconfig{'scantron'}{'config'}{'csv'}{'fields'}}) > 1) {
 9292:                                 $is_csv = 1;
 9293:                             }
 9294:                         }
 9295:                     }
 9296:                 }
 9297:                 if ($is_csv) {
 9298:                    $parser = $domconfig{'scantron'}{'config'}{'csv'};
 9299:                 }
 9300:             }
 9301:         }
 9302:         my $result =
 9303:             &Apache::lonnet::userfileupload('upfile','scantron','scantron',$parser,'','',
 9304:                                             $env{'form.courseid'},$env{'form.domainid'});
 9305: 	if ($result =~ m{^/uploaded/}) {
 9306:             $r->print(
 9307:                 &Apache::lonhtmlcommon::confirm_success(&mt('Upload successful')).'<br />'.
 9308:                 &mt('Uploaded [_1] bytes of data into location: [_2]',
 9309:                         (length($env{'form.upfile'})-1),
 9310:                         '<span class="LC_filename">'.$result.'</span>'));
 9311:             ($uploadedfile) = ($result =~ m{/([^/]+)$});
 9312:             $r->print(&validate_uploaded_scantron_file($env{'form.domainid'},
 9313:                                                        $env{'form.courseid'},$uploadedfile));
 9314: 	} else {
 9315:             $r->print(
 9316:                 &Apache::lonhtmlcommon::confirm_success(&mt('Upload failed'),1).'<br />'.
 9317:                     &mt('An error ([_1]) occurred when attempting to upload the file: [_2]',
 9318:                           $result,
 9319: 			  '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'));
 9320: 	}
 9321:     }
 9322:     if ($symb) {
 9323: 	$r->print(&scantron_selectphase($r,$uploadedfile,$symb));
 9324:     } else {
 9325: 	$r->print($doanotherupload);
 9326:     }
 9327:     return '';
 9328: }
 9329: 
 9330: sub validate_uploaded_scantron_file {
 9331:     my ($cdom,$cname,$fname) = @_;
 9332:     my $scanlines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.$fname);
 9333:     my @lines;
 9334:     if ($scanlines ne '-1') {
 9335:         @lines=split("\n",$scanlines,-1);
 9336:     }
 9337:     my $output;
 9338:     if (@lines) {
 9339:         my (%counts,$max_match_format);
 9340:         my ($found_match_count,$max_match_count,$max_match_pct) = (0,0,0);
 9341:         my $classlist = &Apache::loncoursedata::get_classlist($cdom,$cname);
 9342:         my %idmap = &username_to_idmap($classlist);
 9343:         foreach my $key (keys(%idmap)) {
 9344:             my $lckey = lc($key);
 9345:             $idmap{$lckey} = $idmap{$key};
 9346:         }
 9347:         my %unique_formats;
 9348:         my @formatlines = &Apache::lonnet::get_scantronformat_file();
 9349:         foreach my $line (@formatlines) {
 9350:             chomp($line);
 9351:             my @config = split(/:/,$line);
 9352:             my $idstart = $config[5];
 9353:             my $idlength = $config[6];
 9354:             if (($idstart ne '') && ($idlength > 0)) {
 9355:                 if (ref($unique_formats{$idstart.':'.$idlength}) eq 'ARRAY') {
 9356:                     push(@{$unique_formats{$idstart.':'.$idlength}},$config[0].':'.$config[1]); 
 9357:                 } else {
 9358:                     $unique_formats{$idstart.':'.$idlength} = [$config[0].':'.$config[1]];
 9359:                 }
 9360:             }
 9361:         }
 9362:         foreach my $key (keys(%unique_formats)) {
 9363:             my ($idstart,$idlength) = split(':',$key);
 9364:             %{$counts{$key}} = (
 9365:                                'found'   => 0,
 9366:                                'total'   => 0,
 9367:                               );
 9368:             foreach my $line (@lines) {
 9369:                 next if ($line =~ /^#/);
 9370:                 next if ($line =~ /^[\s\cz]*$/);
 9371:                 my $id = substr($line,$idstart-1,$idlength);
 9372:                 $id = lc($id);
 9373:                 if (exists($idmap{$id})) {
 9374:                     $counts{$key}{'found'} ++;
 9375:                 }
 9376:                 $counts{$key}{'total'} ++;
 9377:             }
 9378:             if ($counts{$key}{'total'}) {
 9379:                 my $percent_match = (100*$counts{$key}{'found'})/($counts{$key}{'total'});
 9380:                 if (($max_match_format eq '') || ($percent_match > $max_match_pct)) {
 9381:                     $max_match_pct = $percent_match;
 9382:                     $max_match_format = $key;
 9383:                     $found_match_count = $counts{$key}{'found'};
 9384:                     $max_match_count = $counts{$key}{'total'};
 9385:                 }
 9386:             }
 9387:         }
 9388:         if (ref($unique_formats{$max_match_format}) eq 'ARRAY') {
 9389:             my $format_descs;
 9390:             my $numwithformat = @{$unique_formats{$max_match_format}};
 9391:             for (my $i=0; $i<$numwithformat; $i++) {
 9392:                 my ($name,$desc) = split(':',$unique_formats{$max_match_format}[$i]);
 9393:                 if ($i<$numwithformat-2) {
 9394:                     $format_descs .= '"<i>'.$desc.'</i>", ';
 9395:                 } elsif ($i==$numwithformat-2) {
 9396:                     $format_descs .= '"<i>'.$desc.'</i>" '.&mt('and').' ';
 9397:                 } elsif ($i==$numwithformat-1) {
 9398:                     $format_descs .= '"<i>'.$desc.'</i>"';
 9399:                 }
 9400:             }
 9401:             my $showpct = sprintf("%.0f",$max_match_pct).'%';
 9402:             $output .= '<br />';
 9403:             if ($found_match_count == $max_match_count) {
 9404:                 # 100% matching entries
 9405:                 $output .= &Apache::lonhtmlcommon::confirm_success(
 9406:                      &mt('Comparison of student IDs: [_1] matching ([quant,_2,entry,entries])',
 9407:                             '<b>'.$showpct.'</b>',$found_match_count)).'<br />'.
 9408:                 &mt('Comparison of student IDs in the uploaded file with'.
 9409:                     ' the course roster found matches for [_1] of the [_2] entries'.
 9410:                     ' in the file (for the format defined for [_3]).',
 9411:                         '<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs);
 9412:             } else {
 9413:                 # Not all entries matching? -> Show warning and additional info
 9414:                 $output .=
 9415:                     &Apache::lonhtmlcommon::confirm_success(
 9416:                         &mt('Comparison of student IDs: [_1] matching ([_2]/[quant,_3,entry,entries])',
 9417:                                 '<b>'.$showpct.'</b>',$found_match_count,$max_match_count).'<br />'.
 9418:                         &mt('Not all entries could be matched!'),1).'<br />'.
 9419:                     &mt('Comparison of student IDs in the uploaded file with'.
 9420:                         ' the course roster found matches for [_1] of the [_2] entries'.
 9421:                         ' in the file (for the format defined for [_3]).',
 9422:                             '<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs).
 9423:                     '<p class="LC_info">'.
 9424:                     &mt('A low percentage of matches results from one of the following:').
 9425:                     '</p><ul>'.
 9426:                     '<li>'.&mt('The file was uploaded to the wrong course.').'</li>'.
 9427:                     '<li>'.&mt('The data is not in the format expected for the domain: [_1]',
 9428:                                '<i>'.$cdom.'</i>').'</li>'.
 9429:                     '<li>'.&mt('Students did not bubble their IDs, or mis-bubbled them').'</li>'.
 9430:                     '<li>'.&mt('The course roster is not up to date.').'</li>'.
 9431:                     '</ul>';
 9432:             }
 9433:         }
 9434:     } else {
 9435:         $output = '<p class="LC_warning">'.&mt('Uploaded file contained no data').'</p>';
 9436:     }
 9437:     return $output;
 9438: }
 9439: 
 9440: sub valid_file {
 9441:     my ($requested_file)=@_;
 9442:     foreach my $filename (sort(&scantron_filenames())) {
 9443: 	if ($requested_file eq $filename) { return 1; }
 9444:     }
 9445:     return 0;
 9446: }
 9447: 
 9448: sub scantron_download_scantron_data {
 9449:     my ($r,$symb) = @_;
 9450:     my $default_form_data=&defaultFormData($symb);
 9451:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
 9452:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
 9453:     my $file=$env{'form.scantron_selectfile'};
 9454:     if (! &valid_file($file)) {
 9455: 	$r->print('
 9456: 	<p>
 9457: 	    '.&mt('The requested filename was invalid.').'
 9458:         </p>
 9459: ');
 9460: 	return;
 9461:     }
 9462:     my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;
 9463:     my $corrected='/uploaded/'.$cdom.'/'.$cname.'/scantron_corrected_'.$file;
 9464:     my $skipped='/uploaded/'.$cdom.'/'.$cname.'/scantron_skipped_'.$file;
 9465:     &Apache::lonnet::allowuploaded('/adm/grades',$orig);
 9466:     &Apache::lonnet::allowuploaded('/adm/grades',$corrected);
 9467:     &Apache::lonnet::allowuploaded('/adm/grades',$skipped);
 9468:     $r->print('
 9469:     <p>
 9470: 	'.&mt('[_1]Original[_2] file as uploaded by the bubblesheet scanning office.',
 9471: 	      '<a href="'.$orig.'">','</a>').'
 9472:     </p>
 9473:     <p>
 9474: 	'.&mt('[_1]Corrections[_2], a file of corrected records that were used in grading.',
 9475: 	      '<a href="'.$corrected.'">','</a>').'
 9476:     </p>
 9477:     <p>
 9478: 	'.&mt('[_1]Skipped[_2], a file of records that were skipped.',
 9479: 	      '<a href="'.$skipped.'">','</a>').'
 9480:     </p>
 9481: ');
 9482:     return '';
 9483: }
 9484: 
 9485: sub checkscantron_results {
 9486:     my ($r,$symb) = @_;
 9487:     if (!$symb) {return '';}
 9488:     my $cid = $env{'request.course.id'};
 9489:     my %lettdig = &Apache::lonnet::letter_to_digits();
 9490:     my $numletts = scalar(keys(%lettdig));
 9491:     my $cnum = $env{'course.'.$cid.'.num'};
 9492:     my $cdom = $env{'course.'.$cid.'.domain'};
 9493:     my (undef, undef, $sequence) = &Apache::lonnet::decode_symb($env{'form.selectpage'});
 9494:     my %record;
 9495:     my %scantron_config =
 9496:         &Apache::lonnet::get_scantron_config($env{'form.scantron_format'});
 9497:     my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
 9498:     my ($scanlines,$scan_data)=&Apache::grades::scantron_getfile();
 9499:     my $classlist=&Apache::loncoursedata::get_classlist();
 9500:     my %idmap=&Apache::grades::username_to_idmap($classlist);
 9501:     my $navmap=Apache::lonnavmaps::navmap->new();
 9502:     unless (ref($navmap)) {
 9503:         $r->print(&navmap_errormsg());
 9504:         return '';
 9505:     }
 9506:     my $map=$navmap->getResourceByUrl($sequence);
 9507:     my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
 9508:         %grader_randomlists_by_symb,%orderedforcode);
 9509:     if (ref($map)) {
 9510:         $randomorder=$map->randomorder();
 9511:         $randompick=$map->randompick();
 9512:     }
 9513:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
 9514:     my $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
 9515:     if ($nav_error) {
 9516:         $r->print(&navmap_errormsg());
 9517:         return '';
 9518:     }
 9519:     &graders_resources_pass(\@resources,\%grader_partids_by_symb,
 9520:                             \%grader_randomlists_by_symb,$bubbles_per_row);
 9521:     my ($uname,$udom);
 9522:     my (%scandata,%lastname,%bylast);
 9523:     $r->print('
 9524: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="checkscantron">'."\n");
 9525: 
 9526:     my @delayqueue;
 9527:     my %completedstudents;
 9528: 
 9529:     my $count=&get_todo_count($scanlines,$scan_data);
 9530:     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count);
 9531:     my ($username,$domain,$started);
 9532:     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse.
 9533:     if ($nav_error) {
 9534:         $r->print(&navmap_errormsg());
 9535:         return '';
 9536:     }
 9537: 
 9538:     &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
 9539:                                           'Processing first student');
 9540:     my $start=&Time::HiRes::time();
 9541:     my $i=-1;
 9542: 
 9543:     while ($i<$scanlines->{'count'}) {
 9544:         ($username,$domain,$uname)=('','','');
 9545:         $i++;
 9546:         my $line=&Apache::grades::scantron_get_line($scanlines,$scan_data,$i);
 9547:         if ($line=~/^[\s\cz]*$/) { next; }
 9548:         if ($started) {
 9549:             &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
 9550:                                                      'last student');
 9551:         }
 9552:         $started=1;
 9553:         my $scan_record=
 9554:             &Apache::grades::scantron_parse_scanline($line,$i,\%scantron_config,
 9555:                                                      $scan_data);
 9556:         unless ($uname=&scantron_find_student($scan_record,$scan_data,
 9557:                                               \%idmap,$i)) {
 9558:             &Apache::grades::scantron_add_delay(\@delayqueue,$line,
 9559:                                 'Unable to find a student that matches',1);
 9560:             next;
 9561:         }
 9562:         if (exists $completedstudents{$uname}) {
 9563:             &Apache::grades::scantron_add_delay(\@delayqueue,$line,
 9564:                                 'Student '.$uname.' has multiple sheets',2);
 9565:             next;
 9566:         }
 9567:         my $pid = $scan_record->{'scantron.ID'};
 9568:         $lastname{$pid} = $scan_record->{'scantron.LastName'};
 9569:         push(@{$bylast{$lastname{$pid}}},$pid);
 9570:         my $usec = $classlist->{$uname}->[&Apache::loncoursedata::CL_SECTION];
 9571:         my $user = $uname.':'.$usec;
 9572:         ($username,$domain)=split(/:/,$uname);
 9573: 
 9574:         my $scancode;
 9575:         if ((exists($scan_record->{'scantron.CODE'})) &&
 9576:             (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
 9577:             $scancode = $scan_record->{'scantron.CODE'};
 9578:         } else {
 9579:             $scancode = '';
 9580:         }
 9581: 
 9582:         my @mapresources = @resources;
 9583:         my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
 9584:         my %respnumlookup=();
 9585:         my %startline=();
 9586:         if ($randomorder || $randompick) {
 9587:             @mapresources =
 9588:                 &users_order($user,$scancode,$sequence,\@master_seq,\%symb_to_resource,
 9589:                              \%orderedforcode);
 9590:             my $total = &get_respnum_lookups($sequence,$scan_data,\%idmap,$line,
 9591:                                              $scan_record,\@master_seq,\%symb_to_resource,
 9592:                                              \%grader_partids_by_symb,\%orderedforcode,
 9593:                                              \%respnumlookup,\%startline);
 9594:             if ($randompick && $total) {
 9595:                 $lastpos = $total*$scantron_config{'Qlength'};
 9596:             }
 9597:         }
 9598:         $scandata{$pid} = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
 9599:         chomp($scandata{$pid});
 9600:         $scandata{$pid} =~ s/\r$//;
 9601: 
 9602:         my $counter = -1;
 9603:         foreach my $resource (@mapresources) {
 9604:             my $parts;
 9605:             my $ressymb = $resource->symb();
 9606:             if ((exists($grader_randomlists_by_symb{$ressymb})) ||
 9607:                 (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
 9608:                 my $currcode;
 9609:                 if (exists($grader_randomlists_by_symb{$ressymb})) {
 9610:                     $currcode = $scancode;
 9611:                 }
 9612:                 (my $analysis,$parts) =
 9613:                     &scantron_partids_tograde($resource,$env{'request.course.id'},
 9614:                                               $username,$domain,undef,
 9615:                                               $bubbles_per_row,$currcode);
 9616:             } else {
 9617:                 $parts = $grader_partids_by_symb{$ressymb};
 9618:             }
 9619:             ($counter,my $recording) =
 9620:                 &verify_scantron_grading($resource,$domain,$username,$cid,$counter,
 9621:                                          $scandata{$pid},$parts,
 9622:                                          \%scantron_config,\%lettdig,$numletts,
 9623:                                          $randomorder,$randompick,
 9624:                                          \%respnumlookup,\%startline);
 9625:             $record{$pid} .= $recording;
 9626:         }
 9627:     }
 9628:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
 9629:     $r->print('<br />');
 9630:     my ($okstudents,$badstudents,$numstudents,$passed,$failed);
 9631:     $passed = 0;
 9632:     $failed = 0;
 9633:     $numstudents = 0;
 9634:     foreach my $last (sort(keys(%bylast))) {
 9635:         if (ref($bylast{$last}) eq 'ARRAY') {
 9636:             foreach my $pid (sort(@{$bylast{$last}})) {
 9637:                 my $showscandata = $scandata{$pid};
 9638:                 my $showrecord = $record{$pid};
 9639:                 $showscandata =~ s/\s/&nbsp;/g;
 9640:                 $showrecord =~ s/\s/&nbsp;/g;
 9641:                 if ($scandata{$pid} eq $record{$pid}) {
 9642:                     my $css_class = ($passed % 2)?'LC_odd_row':'LC_even_row';
 9643:                     $okstudents .= '<tr class="'.$css_class.'">'.
 9644: '<td>'.&mt('Bubblesheet').'</td><td>'.$showscandata.'</td><td rowspan="2">'.$last.'</td><td rowspan="2">'.$pid.'</td>'."\n".
 9645: '</tr>'."\n".
 9646: '<tr class="'.$css_class.'">'."\n".
 9647: '<td>'.&mt('Submissions').'</td><td>'.$showrecord.'</td></tr>'."\n";
 9648:                     $passed ++;
 9649:                 } else {
 9650:                     my $css_class = ($failed % 2)?'LC_odd_row':'LC_even_row';
 9651:                     $badstudents .= '<tr class="'.$css_class.'"><td>'.&mt('Bubblesheet').'</td><td><span class="LC_nobreak">'.$scandata{$pid}.'</span></td><td rowspan="2">'.$last.'</td><td rowspan="2">'.$pid.'</td>'."\n".
 9652: '</tr>'."\n".
 9653: '<tr class="'.$css_class.'">'."\n".
 9654: '<td>'.&mt('Submissions').'</td><td><span class="LC_nobreak">'.$record{$pid}.'</span></td>'."\n".
 9655: '</tr>'."\n";
 9656:                     $failed ++;
 9657:                 }
 9658:                 $numstudents ++;
 9659:             }
 9660:         }
 9661:     }
 9662:     $r->print('<p>'.
 9663:               &mt('Comparison of bubblesheet data (including corrections) with corresponding submission records (most recent submission) for [_1][quant,_2,student][_3] ([quant,_4,bubblesheet line] per student).',
 9664:                   '<b>',
 9665:                   $numstudents,
 9666:                   '</b>',
 9667:                   $env{'form.scantron_maxbubble'}).
 9668:               '</p>'
 9669:     );
 9670:     $r->print('<p>'
 9671:              .&mt('Exact matches for [_1][quant,_2,student][_3].','<b>',$passed,'</b>')
 9672:              .'<br />'
 9673:              .&mt('Discrepancies detected for [_1][quant,_2,student][_3].','<b>',$failed,'</b>')
 9674:              .'</p>');
 9675:     if ($passed) {
 9676:         $r->print(&mt('Students with exact correspondence between bubblesheet data and submissions are as follows:').'<br /><br />');
 9677:         $r->print(&Apache::loncommon::start_data_table()."\n".
 9678:                  &Apache::loncommon::start_data_table_header_row()."\n".
 9679:                  '<th>'.&mt('Source').'</th><th>'.&mt('Bubble records').'</th><th>'.&mt('Name').'</th><th>'.&mt('ID').'</th>'.
 9680:                  &Apache::loncommon::end_data_table_header_row()."\n".
 9681:                  $okstudents."\n".
 9682:                  &Apache::loncommon::end_data_table().'<br />');
 9683:     }
 9684:     if ($failed) {
 9685:         $r->print(&mt('Students with differences between bubblesheet data and submissions are as follows:').'<br /><br />');
 9686:         $r->print(&Apache::loncommon::start_data_table()."\n".
 9687:                  &Apache::loncommon::start_data_table_header_row()."\n".
 9688:                  '<th>'.&mt('Source').'</th><th>'.&mt('Bubble records').'</th><th>'.&mt('Name').'</th><th>'.&mt('ID').'</th>'.
 9689:                  &Apache::loncommon::end_data_table_header_row()."\n".
 9690:                  $badstudents."\n".
 9691:                  &Apache::loncommon::end_data_table()).'<br />'.
 9692:                  &mt('Differences can occur if submissions were modified using manual grading after a bubblesheet grading pass.').'<br />'.&mt('If unexpected discrepancies were detected, it is recommended that you inspect the original bubblesheets.');  
 9693:     }
 9694:     $r->print('</form><br />');
 9695:     return;
 9696: }
 9697: 
 9698: sub verify_scantron_grading {
 9699:     my ($resource,$domain,$username,$cid,$counter,$scandata,$partids,
 9700:         $scantron_config,$lettdig,$numletts,$randomorder,$randompick,
 9701:         $respnumlookup,$startline) = @_;
 9702:     my ($record,%expected,%startpos);
 9703:     return ($counter,$record) if (!ref($resource));
 9704:     return ($counter,$record) if (!$resource->is_problem());
 9705:     my $symb = $resource->symb();
 9706:     return ($counter,$record) if (ref($partids) ne 'ARRAY');
 9707:     foreach my $part_id (@{$partids}) {
 9708:         $counter ++;
 9709:         $expected{$part_id} = 0;
 9710:         my $respnum = $counter;
 9711:         if ($randomorder || $randompick) {
 9712:             $respnum = $respnumlookup->{$counter};
 9713:             $startpos{$part_id} = $startline->{$counter} + 1;
 9714:         } else {
 9715:             $startpos{$part_id} = $env{"form.scantron.first_bubble_line.$counter"};
 9716:         }
 9717:         if ($env{"form.scantron.sub_bubblelines.$respnum"}) {
 9718:             my @sub_lines = split(/,/,$env{"form.scantron.sub_bubblelines.$respnum"});
 9719:             foreach my $item (@sub_lines) {
 9720:                 $expected{$part_id} += $item;
 9721:             }
 9722:         } else {
 9723:             $expected{$part_id} = $env{"form.scantron.bubblelines.$respnum"};
 9724:         }
 9725:     }
 9726:     if ($symb) {
 9727:         my %recorded;
 9728:         my (%returnhash) = &Apache::lonnet::restore($symb,$cid,$domain,$username);
 9729:         if ($returnhash{'version'}) {
 9730:             my %lasthash=();
 9731:             my $version;
 9732:             for ($version=1;$version<=$returnhash{'version'};$version++) {
 9733:                 foreach my $key (sort(split(/\:/,$returnhash{$version.':keys'}))) {
 9734:                     $lasthash{$key}=$returnhash{$version.':'.$key};
 9735:                 }
 9736:             }
 9737:             foreach my $key (keys(%lasthash)) {
 9738:                 if ($key =~ /\.scantron$/) {
 9739:                     my $value = &unescape($lasthash{$key});
 9740:                     my ($part_id) = ($key =~ /^resource\.(.+)\.scantron$/);
 9741:                     if ($value eq '') {
 9742:                         for (my $i=0; $i<$expected{$part_id}; $i++) {
 9743:                             for (my $j=0; $j<$scantron_config->{'length'}; $j++) {
 9744:                                 $recorded{$part_id} .= $scantron_config->{'Qoff'};
 9745:                             }
 9746:                         }
 9747:                     } else {
 9748:                         my @tocheck;
 9749:                         my @items = split(//,$value);
 9750:                         if (($scantron_config->{'Qon'} eq 'letter') ||
 9751:                             ($scantron_config->{'Qon'} eq 'number')) {
 9752:                             if (@items < $expected{$part_id}) {
 9753:                                 my $fragment = substr($scandata,$startpos{$part_id},$expected{$part_id});
 9754:                                 my @singles = split(//,$fragment);
 9755:                                 foreach my $pos (@singles) {
 9756:                                     if ($pos eq ' ') {
 9757:                                         push(@tocheck,$pos);
 9758:                                     } else {
 9759:                                         my $next = shift(@items);
 9760:                                         push(@tocheck,$next);
 9761:                                     }
 9762:                                 }
 9763:                             } else {
 9764:                                 @tocheck = @items;
 9765:                             }
 9766:                             foreach my $letter (@tocheck) {
 9767:                                 if ($scantron_config->{'Qon'} eq 'letter') {
 9768:                                     if ($letter !~ /^[A-J]$/) {
 9769:                                         $letter = $scantron_config->{'Qoff'};
 9770:                                     }
 9771:                                     $recorded{$part_id} .= $letter;
 9772:                                 } elsif ($scantron_config->{'Qon'} eq 'number') {
 9773:                                     my $digit;
 9774:                                     if ($letter !~ /^[A-J]$/) {
 9775:                                         $digit = $scantron_config->{'Qoff'};
 9776:                                     } else {
 9777:                                         $digit = $lettdig->{$letter};
 9778:                                     }
 9779:                                     $recorded{$part_id} .= $digit;
 9780:                                 }
 9781:                             }
 9782:                         } else {
 9783:                             @tocheck = @items;
 9784:                             for (my $i=0; $i<$expected{$part_id}; $i++) {
 9785:                                 my $curr_sub = shift(@tocheck);
 9786:                                 my $digit;
 9787:                                 if ($curr_sub =~ /^[A-J]$/) {
 9788:                                     $digit = $lettdig->{$curr_sub}-1;
 9789:                                 }
 9790:                                 if ($curr_sub eq 'J') {
 9791:                                     $digit += scalar($numletts);
 9792:                                 }
 9793:                                 for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) {
 9794:                                     if ($j == $digit) {
 9795:                                         $recorded{$part_id} .= $scantron_config->{'Qon'};
 9796:                                     } else {
 9797:                                         $recorded{$part_id} .= $scantron_config->{'Qoff'};
 9798:                                     }
 9799:                                 }
 9800:                             }
 9801:                         }
 9802:                     }
 9803:                 }
 9804:             }
 9805:         }
 9806:         foreach my $part_id (@{$partids}) {
 9807:             if ($recorded{$part_id} eq '') {
 9808:                 for (my $i=0; $i<$expected{$part_id}; $i++) {
 9809:                     for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) {
 9810:                         $recorded{$part_id} .= $scantron_config->{'Qoff'};
 9811:                     }
 9812:                 }
 9813:             }
 9814:             $record .= $recorded{$part_id};
 9815:         }
 9816:     }
 9817:     return ($counter,$record);
 9818: }
 9819: 
 9820: #-------- end of section for handling grading scantron forms -------
 9821: #
 9822: #-------------------------------------------------------------------
 9823: 
 9824: #-------------------------- Menu interface -------------------------
 9825: #
 9826: #--- Href with symb and command ---
 9827: 
 9828: sub href_symb_cmd {
 9829:     my ($symb,$cmd)=@_;
 9830:     return '/adm/grades?symb='.&HTML::Entities::encode(&Apache::lonenc::check_encrypt($symb),'<>&"').'&amp;command='.$cmd;
 9831: }
 9832: 
 9833: sub grading_menu {
 9834:     my ($request,$symb) = @_;
 9835:     if (!$symb) {return '';}
 9836: 
 9837:     my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),
 9838:                   'command'=>'individual');
 9839: 
 9840:     my $url1a = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
 9841: 
 9842:     $fields{'command'}='ungraded';
 9843:     my $url1b=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
 9844: 
 9845:     $fields{'command'}='table';
 9846:     my $url1c=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
 9847: 
 9848:     $fields{'command'}='all_for_one';
 9849:     my $url1d=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
 9850: 
 9851:     $fields{'command'}='downloadfilesselect';
 9852:     my $url1e=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
 9853:     
 9854:     $fields{'command'} = 'csvform';
 9855:     my $url2 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
 9856:     
 9857:     $fields{'command'} = 'processclicker';
 9858:     my $url3 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
 9859:     
 9860:     $fields{'command'} = 'scantron_selectphase';
 9861:     my $url4 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
 9862: 
 9863:     $fields{'command'} = 'initialverifyreceipt';
 9864:     my $url5 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
 9865:     
 9866:     my @menu = ({	categorytitle=>'Hand Grading',
 9867:             items =>[
 9868:                         {       linktext => 'Select individual students to grade',
 9869:                                 url => $url1a,
 9870:                                 permission => 'F',
 9871:                                 icon => 'grade_students.png',
 9872:                                 linktitle => 'Grade current resource for a selection of students.'
 9873:                         },
 9874:                         {       linktext => 'Grade ungraded submissions',
 9875:                                 url => $url1b,
 9876:                                 permission => 'F',
 9877:                                 icon => 'ungrade_sub.png',
 9878:                                 linktitle => 'Grade all submissions that have not been graded yet.'
 9879:                         },
 9880: 
 9881:                         {       linktext => 'Grading table',
 9882:                                 url => $url1c,
 9883:                                 permission => 'F',
 9884:                                 icon => 'grading_table.png',
 9885:                                 linktitle => 'Grade current resource for all students.'
 9886:                         },
 9887:                         {       linktext => 'Grade page/folder for one student',
 9888:                                 url => $url1d,
 9889:                                 permission => 'F',
 9890:                                 icon => 'grade_PageFolder.png',
 9891:                                 linktitle => 'Grade all resources in current page/sequence/folder for one student.'
 9892:                         },
 9893:                         {       linktext => 'Download submissions',
 9894:                                 url => $url1e,
 9895:                                 permission => 'F',
 9896:                                 icon => 'download_sub.png',
 9897:                                 linktitle => 'Download all students submissions.'
 9898:                         }]},
 9899:                          { categorytitle=>'Automated Grading',
 9900:                items =>[
 9901: 
 9902:                 	    {	linktext => 'Upload Scores',
 9903:                     		url => $url2,
 9904:                     		permission => 'F',
 9905:                     		icon => 'uploadscores.png',
 9906:                     		linktitle => 'Specify a file containing the class scores for current resource.'
 9907:                 	    },
 9908:                 	    {	linktext => 'Process Clicker',
 9909:                     		url => $url3,
 9910:                     		permission => 'F',
 9911:                     		icon => 'addClickerInfoFile.png',
 9912:                     		linktitle => 'Specify a file containing the clicker information for this resource.'
 9913:                 	    },
 9914:                 	    {	linktext => 'Grade/Manage/Review Bubblesheets',
 9915:                     		url => $url4,
 9916:                     		permission => 'F',
 9917:                     		icon => 'bubblesheet.png',
 9918:                     		linktitle => 'Grade bubblesheet exams, upload/download bubblesheet data files, and review previously graded bubblesheet exams.'
 9919:                 	    },
 9920:                             {   linktext => 'Verify Receipt Number',
 9921:                                 url => $url5,
 9922:                                 permission => 'F',
 9923:                                 icon => 'receipt_number.png',
 9924:                                 linktitle => 'Verify a system-generated receipt number for correct problem solution.'
 9925:                             }
 9926: 
 9927:                     ]
 9928:             });
 9929: 
 9930:     # Create the menu
 9931:     my $Str;
 9932:     $Str .= '<form method="post" action="" name="gradingMenu">';
 9933:     $Str .= '<input type="hidden" name="command" value="" />'.
 9934:     	'<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
 9935: 
 9936:     $Str .= &Apache::lonhtmlcommon::generate_menu(@menu);
 9937:     return $Str;    
 9938: }
 9939: 
 9940: sub ungraded {
 9941:     my ($request)=@_;
 9942:     &submit_options($request);
 9943: }
 9944: 
 9945: sub submit_options_sequence {
 9946:     my ($request,$symb) = @_;
 9947:     if (!$symb) {return '';}
 9948:     &commonJSfunctions($request);
 9949:     my $result;
 9950: 
 9951:     $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
 9952:         '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
 9953:     $result.=&selectfield(0).
 9954:             '<input type="hidden" name="command" value="pickStudentPage" />
 9955:             <div>
 9956:               <input type="submit" value="'.&mt('Next').' &rarr;" />
 9957:             </div>
 9958:         </div>
 9959:   </form>';
 9960:     return $result;
 9961: }
 9962: 
 9963: sub submit_options_table {
 9964:     my ($request,$symb) = @_;
 9965:     if (!$symb) {return '';}
 9966:     &commonJSfunctions($request);
 9967:     my $result;
 9968: 
 9969:     $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
 9970:         '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
 9971: 
 9972:     $result.=&selectfield(1).
 9973:             '<input type="hidden" name="command" value="viewgrades" />
 9974:             <div>
 9975:               <input type="submit" value="'.&mt('Next').' &rarr;" />
 9976:             </div>
 9977:         </div>
 9978:   </form>';
 9979:     return $result;
 9980: }
 9981: 
 9982: sub submit_options_download {
 9983:     my ($request,$symb) = @_;
 9984:     if (!$symb) {return '';}
 9985: 
 9986:     my $res_error;
 9987:     my ($partlist,$handgrade,$responseType,$numresp,$numessay,$numdropbox) =
 9988:         &response_type($symb,\$res_error);
 9989:     if ($res_error) {
 9990:         $request->print(&mt('An error occurred retrieving response types'));
 9991:         return;
 9992:     }
 9993:     unless ($numessay) {
 9994:         $request->print(&mt('No essayresponse items found'));
 9995:         return;
 9996:     }
 9997:     my $table;
 9998:     if (ref($partlist) eq 'ARRAY') {
 9999:         if (scalar(@$partlist) > 1 ) {
10000:             $table = &showResourceInfo($symb,$partlist,$responseType,'gradingMenu',1,1);
10001:         }
10002:     }
10003: 
10004:     &commonJSfunctions($request);
10005: 
10006:     my $result='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
10007:         $table."\n".
10008:         '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
10009:     $result.='
10010: <h2>
10011:   '.&mt('Select Students for whom to Download Submissions').'
10012: </h2>'.&selectfield(1).'
10013:                 <input type="hidden" name="command" value="downloadfileslink" />
10014:               <input type="submit" value="'.&mt('Next').' &rarr;" />
10015:             </div>
10016:           </div>
10017: 
10018: 
10019:   </form>';
10020:     return $result;
10021: }
10022: 
10023: #--- Displays the submissions first page -------
10024: sub submit_options {
10025:     my ($request,$symb) = @_;
10026:     if (!$symb) {return '';}
10027: 
10028:     &commonJSfunctions($request);
10029:     my $result;
10030: 
10031:     $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
10032: 	'<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
10033:     $result.=&selectfield(1).'
10034:                 <input type="hidden" name="command" value="submission" />
10035:               <input type="submit" value="'.&mt('Next').' &rarr;" />
10036:             </div>
10037:           </div>
10038:   </form>';
10039:     return $result;
10040: }
10041: 
10042: sub selectfield {
10043:    my ($full)=@_;
10044:    my %options =
10045:        (&substatus_options,
10046:         'select_form_order' => ['yes','queued','graded','incorrect','all']);
10047:    my $result='<div class="LC_columnSection">
10048: 
10049:     <fieldset>
10050:       <legend>
10051:        '.&mt('Sections').'
10052:       </legend>
10053:       '.&Apache::lonstatistics::SectionSelect('section','multiple',5).'
10054:     </fieldset>
10055: 
10056:     <fieldset>
10057:       <legend>
10058:         '.&mt('Groups').'
10059:       </legend>
10060:       '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'
10061:     </fieldset>
10062:  
10063:     <fieldset>
10064:       <legend>
10065:         '.&mt('Access Status').'
10066:       </legend>
10067:       '.&Apache::lonhtmlcommon::StatusOptions(undef,undef,5,undef,'mult').'
10068:     </fieldset>';
10069:     if ($full) {
10070:         $result.='
10071:     <fieldset>
10072:       <legend>
10073:         '.&mt('Submission Status').'
10074:       </legend>'.
10075:        &Apache::loncommon::select_form('all','submitonly',\%options).
10076:    '</fieldset>';
10077:     }
10078:     $result.='</div><br />';
10079:     return $result;
10080: }
10081: 
10082: sub substatus_options {
10083:     return &Apache::lonlocal::texthash(
10084:                                       'yes'       => 'with submissions',
10085:                                       'queued'    => 'in grading queue',
10086:                                       'graded'    => 'with ungraded submissions',
10087:                                       'incorrect' => 'with incorrect submissions',
10088:                                       'all'       => 'with any status',
10089:                                       );
10090: }
10091: 
10092: sub transtatus_options {
10093:     return &Apache::lonlocal::texthash(
10094:                                        'yes'       => 'with score transactions',
10095:                                        'incorrect' => 'with less than full credit',
10096:                                        'all'       => 'with any status',
10097:                                       );
10098: }
10099: 
10100: sub reset_perm {
10101:     undef(%perm);
10102: }
10103: 
10104: sub init_perm {
10105:     &reset_perm();
10106:     foreach my $test_perm ('vgr','mgr','opa') {
10107: 
10108: 	my $scope = $env{'request.course.id'};
10109: 	if (!($perm{$test_perm}=&Apache::lonnet::allowed($test_perm,$scope))) {
10110: 
10111: 	    $scope .= '/'.$env{'request.course.sec'};
10112: 	    if ( $perm{$test_perm}=
10113: 		 &Apache::lonnet::allowed($test_perm,$scope)) {
10114: 		$perm{$test_perm.'_section'}=$env{'request.course.sec'};
10115: 	    } else {
10116: 		delete($perm{$test_perm});
10117: 	    }
10118: 	}
10119:     }
10120: }
10121: 
10122: sub init_old_essays {
10123:     my ($symb,$apath,$adom,$aname) = @_;
10124:     if ($symb ne '') {
10125:         my %essays = &Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
10126:         if (keys(%essays) > 0) {
10127:             $old_essays{$symb} = \%essays;
10128:         }
10129:     }
10130:     return;
10131: }
10132: 
10133: sub reset_old_essays {
10134:     undef(%old_essays);
10135: }
10136: 
10137: sub gather_clicker_ids {
10138:     my %clicker_ids;
10139: 
10140:     my $classlist = &Apache::loncoursedata::get_classlist();
10141: 
10142:     # Set up a couple variables.
10143:     my $username_idx = &Apache::loncoursedata::CL_SNAME();
10144:     my $domain_idx   = &Apache::loncoursedata::CL_SDOM();
10145:     my $status_idx   = &Apache::loncoursedata::CL_STATUS();
10146: 
10147:     foreach my $student (keys(%$classlist)) {
10148:         if ($classlist->{$student}->[$status_idx] ne 'Active') { next; }
10149:         my $username = $classlist->{$student}->[$username_idx];
10150:         my $domain   = $classlist->{$student}->[$domain_idx];
10151:         my $clickers =
10152: 	    (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];
10153:         foreach my $id (split(/\,/,$clickers)) {
10154:             $id=~s/^[\#0]+//;
10155:             $id=~s/[\-\:]//g;
10156:             if (exists($clicker_ids{$id})) {
10157: 		$clicker_ids{$id}.=','.$username.':'.$domain;
10158:             } else {
10159: 		$clicker_ids{$id}=$username.':'.$domain;
10160:             }
10161:         }
10162:     }
10163:     return %clicker_ids;
10164: }
10165: 
10166: sub gather_adv_clicker_ids {
10167:     my %clicker_ids;
10168:     my $cnum=$env{'course.'.$env{'request.course.id'}.'.num'};
10169:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
10170:     my %coursepersonnel=&Apache::lonnet::get_course_adv_roles($cdom.'/'.$cnum);
10171:     foreach my $element (sort(keys(%coursepersonnel))) {
10172:         foreach my $person (split(/\,/,$coursepersonnel{$element})) {
10173:             my ($puname,$pudom)=split(/\:/,$person);
10174:             my $clickers =
10175: 		(&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];
10176:             foreach my $id (split(/\,/,$clickers)) {
10177: 		$id=~s/^[\#0]+//;
10178:                 $id=~s/[\-\:]//g;
10179: 		if (exists($clicker_ids{$id})) {
10180: 		    $clicker_ids{$id}.=','.$puname.':'.$pudom;
10181: 		} else {
10182: 		    $clicker_ids{$id}=$puname.':'.$pudom;
10183: 		}
10184:             }
10185:         }
10186:     }
10187:     return %clicker_ids;
10188: }
10189: 
10190: sub clicker_grading_parameters {
10191:     return ('gradingmechanism' => 'scalar',
10192:             'upfiletype' => 'scalar',
10193:             'specificid' => 'scalar',
10194:             'pcorrect' => 'scalar',
10195:             'pincorrect' => 'scalar');
10196: }
10197: 
10198: sub process_clicker {
10199:     my ($r,$symb)=@_;
10200:     if (!$symb) {return '';}
10201:     my $result=&checkforfile_js();
10202:     $result.=&Apache::loncommon::start_data_table().
10203:              &Apache::loncommon::start_data_table_header_row().
10204:              '<th>'.&mt('Specify a file containing clicker information and set grading options.').'</th>'.
10205:              &Apache::loncommon::end_data_table_header_row().
10206:              &Apache::loncommon::start_data_table_row()."<td>\n";
10207: # Attempt to restore parameters from last session, set defaults if not present
10208:     my %Saveable_Parameters=&clicker_grading_parameters();
10209:     &Apache::loncommon::restore_course_settings('grades_clicker',
10210:                                                  \%Saveable_Parameters);
10211:     if (!$env{'form.pcorrect'}) { $env{'form.pcorrect'}=100; }
10212:     if (!$env{'form.pincorrect'}) { $env{'form.pincorrect'}=100; }
10213:     if (!$env{'form.gradingmechanism'}) { $env{'form.gradingmechanism'}='attendance'; }
10214:     if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }
10215: 
10216:     my %checked;
10217:     foreach my $gradingmechanism ('attendance','personnel','specific','given') {
10218:        if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
10219:           $checked{$gradingmechanism}=' checked="checked"';
10220:        }
10221:     }
10222: 
10223:     my $upload=&mt("Evaluate File");
10224:     my $type=&mt("Type");
10225:     my $attendance=&mt("Award points just for participation");
10226:     my $personnel=&mt("Correctness determined from response by course personnel");
10227:     my $specific=&mt("Correctness determined from response with clicker ID(s)"); 
10228:     my $given=&mt("Correctness determined from given list of answers").' '.
10229:               '<font size="-2"><tt>('.&mt("Provide comma-separated list. Use '*' for any answer correct, '-' for skip").')</tt></font>';
10230:     my $pcorrect=&mt("Percentage points for correct solution");
10231:     my $pincorrect=&mt("Percentage points for incorrect solution");
10232:     my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
10233:                                                    {'iclicker' => 'i>clicker',
10234:                                                     'interwrite' => 'interwrite PRS',
10235:                                                     'turning' => 'Turning Technologies'});
10236:     $symb = &Apache::lonenc::check_encrypt($symb);
10237:     $result.= &Apache::lonhtmlcommon::scripttag(<<ENDUPFORM);
10238: function sanitycheck() {
10239: // Accept only integer percentages
10240:    document.forms.gradesupload.pcorrect.value=Math.round(document.forms.gradesupload.pcorrect.value);
10241:    document.forms.gradesupload.pincorrect.value=Math.round(document.forms.gradesupload.pincorrect.value);
10242: // Find out grading choice
10243:    for (i=0; i<document.forms.gradesupload.gradingmechanism.length; i++) {
10244:       if (document.forms.gradesupload.gradingmechanism[i].checked) {
10245:          gradingchoice=document.forms.gradesupload.gradingmechanism[i].value;
10246:       }
10247:    }
10248: // By default, new choice equals user selection
10249:    newgradingchoice=gradingchoice;
10250: // Not good to give more points for false answers than correct ones
10251:    if (Math.round(document.forms.gradesupload.pcorrect.value)<Math.round(document.forms.gradesupload.pincorrect.value)) {
10252:       document.forms.gradesupload.pcorrect.value=document.forms.gradesupload.pincorrect.value;
10253:    }
10254: // If new choice is attendance only, and old choice was correctness-based, restore defaults
10255:    if ((gradingchoice=='attendance') && (document.forms.gradesupload.waschecked.value!='attendance')) {
10256:       document.forms.gradesupload.pcorrect.value=100;
10257:       document.forms.gradesupload.pincorrect.value=100;
10258:    }
10259: // If the values are different, cannot be attendance only
10260:    if ((Math.round(document.forms.gradesupload.pcorrect.value)!=Math.round(document.forms.gradesupload.pincorrect.value)) &&
10261:        (gradingchoice=='attendance')) {
10262:        newgradingchoice='personnel';
10263:    }
10264: // Change grading choice to new one
10265:    for (i=0; i<document.forms.gradesupload.gradingmechanism.length; i++) {
10266:       if (document.forms.gradesupload.gradingmechanism[i].value==newgradingchoice) {
10267:          document.forms.gradesupload.gradingmechanism[i].checked=true;
10268:       } else {
10269:          document.forms.gradesupload.gradingmechanism[i].checked=false;
10270:       }
10271:    }
10272: // Remember the old state
10273:    document.forms.gradesupload.waschecked.value=newgradingchoice;
10274: }
10275: ENDUPFORM
10276:     $result.= <<ENDUPFORM;
10277: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
10278: <input type="hidden" name="symb" value="$symb" />
10279: <input type="hidden" name="command" value="processclickerfile" />
10280: <input type="file" name="upfile" size="50" />
10281: <br /><label>$type: $selectform</label>
10282: ENDUPFORM
10283:     $result.='</td>'.&Apache::loncommon::end_data_table_row().
10284:                      &Apache::loncommon::start_data_table_row().'<td>'.(<<ENDGRADINGFORM);
10285:       <label><input type="radio" name="gradingmechanism" value="attendance"$checked{'attendance'} onclick="sanitycheck()" />$attendance </label>
10286: <br /><label><input type="radio" name="gradingmechanism" value="personnel"$checked{'personnel'} onclick="sanitycheck()" />$personnel</label>
10287: <br /><label><input type="radio" name="gradingmechanism" value="specific"$checked{'specific'} onclick="sanitycheck()" />$specific </label>
10288: <input type="text" name="specificid" value="$env{'form.specificid'}" size="20" />
10289: <br /><label><input type="radio" name="gradingmechanism" value="given"$checked{'given'} onclick="sanitycheck()" />$given </label>
10290: <br />&nbsp;&nbsp;&nbsp;
10291: <input type="text" name="givenanswer" size="50" />
10292: <input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" />
10293: ENDGRADINGFORM
10294:     $result.='</td>'.&Apache::loncommon::end_data_table_row().
10295:                      &Apache::loncommon::start_data_table_row().'<td>'.(<<ENDPERCFORM);
10296:       <label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onchange="sanitycheck()" /></label>
10297: <br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onchange="sanitycheck()" /></label>
10298: <br /><input type="button" onclick="javascript:checkUpload(this.form);" value="$upload" />
10299: </form>
10300: ENDPERCFORM
10301:     $result.='</td>'.
10302:              &Apache::loncommon::end_data_table_row().
10303:              &Apache::loncommon::end_data_table();
10304:     return $result;
10305: }
10306: 
10307: sub process_clicker_file {
10308:     my ($r,$symb) = @_;
10309:     if (!$symb) {return '';}
10310: 
10311:     my %Saveable_Parameters=&clicker_grading_parameters();
10312:     &Apache::loncommon::store_course_settings('grades_clicker',
10313:                                               \%Saveable_Parameters);
10314:     my $result='';
10315:     if (($env{'form.gradingmechanism'} eq 'specific') && ($env{'form.specificid'}!~/\w/)) {
10316: 	$result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';
10317: 	return $result;
10318:     }
10319:     if (($env{'form.gradingmechanism'} eq 'given') && ($env{'form.givenanswer'}!~/\S/)) {
10320:         $result.='<span class="LC_error">'.&mt('You need to specify the correct answer').'</span>';
10321:         return $result;
10322:     }
10323:     my $foundgiven=0;
10324:     if ($env{'form.gradingmechanism'} eq 'given') {
10325:         $env{'form.givenanswer'}=~s/^\s*//gs;
10326:         $env{'form.givenanswer'}=~s/\s*$//gs;
10327:         $env{'form.givenanswer'}=~s/[^a-zA-Z0-9\.\*\-\+]+/\,/g;
10328:         $env{'form.givenanswer'}=uc($env{'form.givenanswer'});
10329:         my @answers=split(/\,/,$env{'form.givenanswer'});
10330:         $foundgiven=$#answers+1;
10331:     }
10332:     my %clicker_ids=&gather_clicker_ids();
10333:     my %correct_ids;
10334:     if ($env{'form.gradingmechanism'} eq 'personnel') {
10335: 	%correct_ids=&gather_adv_clicker_ids();
10336:     }
10337:     if ($env{'form.gradingmechanism'} eq 'specific') {
10338: 	foreach my $correct_id (split(/[\s\,]/,$env{'form.specificid'})) {;
10339: 	   $correct_id=~tr/a-z/A-Z/;
10340: 	   $correct_id=~s/\s//gs;
10341: 	   $correct_id=~s/^[\#0]+//;
10342:            $correct_id=~s/[\-\:]//g;
10343:            if ($correct_id) {
10344: 	      $correct_ids{$correct_id}='specified';
10345:            }
10346:         }
10347:     }
10348:     if ($env{'form.gradingmechanism'} eq 'attendance') {
10349: 	$result.=&mt('Score based on attendance only');
10350:     } elsif ($env{'form.gradingmechanism'} eq 'given') {
10351:         $result.=&mt('Score based on [_1] ([_2] answers)','<tt>'.$env{'form.givenanswer'}.'</tt>',$foundgiven);
10352:     } else {
10353: 	my $number=0;
10354: 	$result.='<p><b>'.&mt('Correctness determined by the following IDs').'</b>';
10355: 	foreach my $id (sort(keys(%correct_ids))) {
10356: 	    $result.='<br /><tt>'.$id.'</tt> - ';
10357: 	    if ($correct_ids{$id} eq 'specified') {
10358: 		$result.=&mt('specified');
10359: 	    } else {
10360: 		my ($uname,$udom)=split(/\:/,$correct_ids{$id});
10361: 		$result.=&Apache::loncommon::plainname($uname,$udom);
10362: 	    }
10363: 	    $number++;
10364: 	}
10365:         $result.="</p>\n";
10366:         if ($number==0) {
10367:             $result .=
10368:                  &Apache::lonhtmlcommon::confirm_success(
10369:                      &mt('No IDs found to determine correct answer'),1);
10370:             return $result;
10371:         }
10372:     }
10373:     if (length($env{'form.upfile'}) < 2) {
10374:         $result .=
10375:             &Apache::lonhtmlcommon::confirm_success(
10376:                 &mt('The file: [_1] you attempted to upload contained no information. Please check that you entered the correct filename.',
10377:                         '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'),1);
10378:         return $result;
10379:     }
10380:     my $mimetype;
10381:     if ($env{'form.upfiletype'} eq 'iclicker') {
10382:         my $mm = new File::MMagic;
10383:         $mimetype = $mm->checktype_contents($env{'form.upfile'});
10384:         unless (($mimetype eq 'text/plain') || ($mimetype eq 'text/html')) {
10385:             $result.= '<p>'.
10386:                 &Apache::lonhtmlcommon::confirm_success(
10387:                     &mt('File format is neither csv (iclicker 6) nor xml (iclicker 7)'),1).'</p>';
10388:             return $result;
10389:         }
10390:     } elsif (($env{'form.upfiletype'} ne 'interwrite') && ($env{'form.upfiletype'} ne 'turning')) {
10391:         $result .= '<p>'.
10392:             &Apache::lonhtmlcommon::confirm_success(
10393:                 &mt('Invalid clicker type: choose one of: i>clicker, Interwrite PRS, or Turning Technologies.'),1).'</p>';
10394:         return $result;
10395:     }
10396: 
10397: # Were able to get all the info needed, now analyze the file
10398: 
10399:     $result.=&Apache::loncommon::studentbrowser_javascript();
10400:     $symb = &Apache::lonenc::check_encrypt($symb);
10401:     $result.=&Apache::loncommon::start_data_table().
10402:              &Apache::loncommon::start_data_table_header_row().
10403:              '<th>'.&mt('Evaluate clicker file').'</th>'.
10404:              &Apache::loncommon::end_data_table_header_row().
10405:              &Apache::loncommon::start_data_table_row().(<<ENDHEADER);
10406: <td>
10407: <form method="post" action="/adm/grades" name="clickeranalysis">
10408: <input type="hidden" name="symb" value="$symb" />
10409: <input type="hidden" name="command" value="assignclickergrades" />
10410: <input type="hidden" name="gradingmechanism" value="$env{'form.gradingmechanism'}" />
10411: <input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />
10412: <input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />
10413: ENDHEADER
10414:     if ($env{'form.gradingmechanism'} eq 'given') {
10415:        $result.='<input type="hidden" name="correct:given" value="'.$env{'form.givenanswer'}.'" />';
10416:     } 
10417:     my %responses;
10418:     my @questiontitles;
10419:     my $errormsg='';
10420:     my $number=0;
10421:     if ($env{'form.upfiletype'} eq 'iclicker') {
10422:         if ($mimetype eq 'text/plain') {
10423:             ($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses);
10424:         } elsif ($mimetype eq 'text/html') {
10425:             ($errormsg,$number)=&iclickerxml_eval(\@questiontitles,\%responses);
10426:         }
10427:     } elsif ($env{'form.upfiletype'} eq 'interwrite') {
10428:         ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses);
10429:     } elsif ($env{'form.upfiletype'} eq 'turning') {
10430:         ($errormsg,$number)=&turning_eval(\@questiontitles,\%responses);
10431:     }
10432:     $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'.
10433:              '<input type="hidden" name="number" value="'.$number.'" />'.
10434:              &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',
10435:                  $env{'form.pcorrect'},$env{'form.pincorrect'}).
10436:              '<br />';
10437:     if (($env{'form.gradingmechanism'} eq 'given') && ($number!=$foundgiven)) {
10438:        $result.='<span class="LC_error">'.&mt('Number of given answers does not agree with number of questions in file.').'</span>';
10439:        return $result;
10440:     } 
10441: # Remember Question Titles
10442: # FIXME: Possibly need delimiter other than ":"
10443:     for (my $i=0;$i<$number;$i++) {
10444:         $result.='<input type="hidden" name="question:'.$i.'" value="'.
10445:                  &HTML::Entities::encode($questiontitles[$i],'"&<>').'" />';
10446:     }
10447:     my $correct_count=0;
10448:     my $student_count=0;
10449:     my $unknown_count=0;
10450: # Match answers with usernames
10451: # FIXME: Possibly need delimiter other than ":"
10452:     foreach my $id (keys(%responses)) {
10453:        if ($correct_ids{$id}) {
10454:           $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />';
10455:           $correct_count++;
10456:        } elsif ($clicker_ids{$id}) {
10457:           if ($clicker_ids{$id}=~/\,/) {
10458: # More than one user with the same clicker!
10459:              $result.="</td>".&Apache::loncommon::end_data_table_row().
10460:                            &Apache::loncommon::start_data_table_row()."<td>".
10461:                        &mt('Clicker registered more than once').": <tt>".$id."</tt><br />";
10462:              $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
10463:                            "<select name='multi".$id."'>";
10464:              foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) {
10465:                  $result.="<option value='".$reguser."'>".&Apache::loncommon::plainname(split(/\:/,$reguser)).' ('.$reguser.')</option>';
10466:              }
10467:              $result.='</select>';
10468:              $unknown_count++;
10469:           } else {
10470: # Good: found one and only one user with the right clicker
10471:              $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />';
10472:              $student_count++;
10473:           }
10474:        } else {
10475:           $result.="</td>".&Apache::loncommon::end_data_table_row().
10476:                            &Apache::loncommon::start_data_table_row()."<td>".
10477:                     &mt('Unregistered Clicker')." <tt>".$id."</tt><br />";
10478:           $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
10479:                    "\n".&mt("Username").": <input type='text' name='uname".$id."' />&nbsp;".
10480:                    "\n".&mt("Domain").": ".
10481:                    &Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).'&nbsp;'.
10482:                    &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id,'',$id);
10483:           $unknown_count++;
10484:        }
10485:     }
10486:     $result.='<hr />'.
10487:              &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);
10488:     if (($env{'form.gradingmechanism'} ne 'attendance') && ($env{'form.gradingmechanism'} ne 'given')) {
10489:        if ($correct_count==0) {
10490:           $errormsg.="Found no correct answers for grading!";
10491:        } elsif ($correct_count>1) {
10492:           $result.='<br /><span class="LC_warning">'.&mt("Found [_1] entries for grading!",$correct_count).'</span>';
10493:        }
10494:     }
10495:     if ($number<1) {
10496:        $errormsg.="Found no questions.";
10497:     }
10498:     if ($errormsg) {
10499:        $result.='<br /><span class="LC_error">'.&mt($errormsg).'</span>';
10500:     } else {
10501:        $result.='<br /><input type="submit" name="finalize" value="'.&mt('Finalize Grading').'" />';
10502:     }
10503:     $result.='</form></td>'.
10504:              &Apache::loncommon::end_data_table_row().
10505:              &Apache::loncommon::end_data_table();
10506:     return $result;
10507: }
10508: 
10509: sub iclicker_eval {
10510:     my ($questiontitles,$responses)=@_;
10511:     my $number=0;
10512:     my $errormsg='';
10513:     foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
10514:         my %components=&Apache::loncommon::record_sep($line);
10515:         my @entries=map {$components{$_}} (sort(keys(%components)));
10516: 	if ($entries[0] eq 'Question') {
10517: 	    for (my $i=3;$i<$#entries;$i+=6) {
10518: 		$$questiontitles[$number]=$entries[$i];
10519: 		$number++;
10520: 	    }
10521: 	}
10522: 	if ($entries[0]=~/^\#/) {
10523: 	    my $id=$entries[0];
10524: 	    my @idresponses;
10525: 	    $id=~s/^[\#0]+//;
10526: 	    for (my $i=0;$i<$number;$i++) {
10527: 		my $idx=3+$i*6;
10528:                 $entries[$idx]=~s/[^a-zA-Z0-9\.\*\-\+]+//g;
10529: 		push(@idresponses,$entries[$idx]);
10530: 	    }
10531: 	    $$responses{$id}=join(',',@idresponses);
10532: 	}
10533:     }
10534:     return ($errormsg,$number);
10535: }
10536: 
10537: sub iclickerxml_eval {
10538:     my ($questiontitles,$responses)=@_;
10539:     my $number=0;
10540:     my $errormsg='';
10541:     my @state;
10542:     my %respbyid;
10543:     my $p = HTML::Parser->new
10544:     (
10545:         xml_mode => 1,
10546:         start_h =>
10547:             [sub {
10548:                  my ($tagname,$attr) = @_;
10549:                  push(@state,$tagname);
10550:                  if ("@state" eq "ssn p") {
10551:                      my $title = $attr->{qn};
10552:                      $title =~ s/(^\s+|\s+$)//g;
10553:                      $questiontitles->[$number]=$title;
10554:                  } elsif ("@state" eq "ssn p v") {
10555:                      my $id = $attr->{id};
10556:                      my $entry = $attr->{ans};
10557:                      $id=~s/^[\#0]+//;
10558:                      $entry =~s/[^a-zA-Z0-9\.\*\-\+]+//g;
10559:                      $respbyid{$id}[$number] = $entry;
10560:                  }
10561:             }, "tagname, attr"],
10562:          end_h =>
10563:                [sub {
10564:                    my ($tagname) = @_;
10565:                    if ("@state" eq "ssn p") {
10566:                        $number++;
10567:                    }
10568:                    pop(@state);
10569:                 }, "tagname"],
10570:     );
10571: 
10572:     $p->parse($env{'form.upfile'});
10573:     $p->eof;
10574:     foreach my $id (keys(%respbyid)) {
10575:         $responses->{$id}=join(',',@{$respbyid{$id}});
10576:     }
10577:     return ($errormsg,$number);
10578: }
10579: 
10580: sub interwrite_eval {
10581:     my ($questiontitles,$responses)=@_;
10582:     my $number=0;
10583:     my $errormsg='';
10584:     my $skipline=1;
10585:     my $questionnumber=0;
10586:     my %idresponses=();
10587:     foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
10588:         my %components=&Apache::loncommon::record_sep($line);
10589:         my @entries=map {$components{$_}} (sort(keys(%components)));
10590:         if ($entries[1] eq 'Time') { $skipline=0; next; }
10591:         if ($entries[1] eq 'Response') { $skipline=1; }
10592:         next if $skipline;
10593:         if ($entries[0]!=$questionnumber) {
10594:            $questionnumber=$entries[0];
10595:            $$questiontitles[$number]=&mt('Question [_1]',$questionnumber);
10596:            $number++;
10597:         }
10598:         my $id=$entries[4];
10599:         $id=~s/^[\#0]+//;
10600:         $id=~s/^v\d*\://i;
10601:         $id=~s/[\-\:]//g;
10602:         $idresponses{$id}[$number]=$entries[6];
10603:     }
10604:     foreach my $id (keys(%idresponses)) {
10605:        $$responses{$id}=join(',',@{$idresponses{$id}});
10606:        $$responses{$id}=~s/^\s*\,//;
10607:     }
10608:     return ($errormsg,$number);
10609: }
10610: 
10611: sub turning_eval {
10612:     my ($questiontitles,$responses)=@_;
10613:     my $number=0;
10614:     my $errormsg='';
10615:     foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
10616:         my %components=&Apache::loncommon::record_sep($line);
10617:         my @entries=map {$components{$_}} (sort(keys(%components)));
10618:         if ($#entries>$number) { $number=$#entries; }
10619:         my $id=$entries[0];
10620:         my @idresponses;
10621:         $id=~s/^[\#0]+//;
10622:         unless ($id) { next; }
10623:         for (my $idx=1;$idx<=$#entries;$idx++) {
10624:             $entries[$idx]=~s/\,/\;/g;
10625:             $entries[$idx]=~s/[^a-zA-Z0-9\.\*\-\+\;]+//g;
10626:             push(@idresponses,$entries[$idx]);
10627:         }
10628:         $$responses{$id}=join(',',@idresponses);
10629:     }
10630:     for (my $i=1; $i<=$number; $i++) {
10631:         $$questiontitles[$i]=&mt('Question [_1]',$i);
10632:     }
10633:     return ($errormsg,$number);
10634: }
10635: 
10636: sub assign_clicker_grades {
10637:     my ($r,$symb) = @_;
10638:     if (!$symb) {return '';}
10639: # See which part we are saving to
10640:     my $res_error;
10641:     my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
10642:     if ($res_error) {
10643:         return &navmap_errormsg();
10644:     }
10645: # FIXME: This should probably look for the first handgradeable part
10646:     my $part=$$partlist[0];
10647: # Start screen output
10648:     my $result = &Apache::loncommon::start_data_table(). 
10649:                  &Apache::loncommon::start_data_table_header_row().
10650:                  '<th>'.&mt('Assigning grades based on clicker file').'</th>'.
10651:                  &Apache::loncommon::end_data_table_header_row().
10652:                  &Apache::loncommon::start_data_table_row().'<td>';
10653: # Get correct result
10654: # FIXME: Possibly need delimiter other than ":"
10655:     my @correct=();
10656:     my $gradingmechanism=$env{'form.gradingmechanism'};
10657:     my $number=$env{'form.number'};
10658:     if ($gradingmechanism ne 'attendance') {
10659:        foreach my $key (keys(%env)) {
10660:           if ($key=~/^form\.correct\:/) {
10661:              my @input=split(/\,/,$env{$key});
10662:              for (my $i=0;$i<=$#input;$i++) {
10663:                  if (($correct[$i]) && ($input[$i]) &&
10664:                      ($correct[$i] ne $input[$i])) {
10665:                     $result.='<br /><span class="LC_warning">'.
10666:                              &mt('More than one correct result given for question "[_1]": [_2] versus [_3].',
10667:                                  $env{'form.question:'.$i},$correct[$i],$input[$i]).'</span>';
10668:                  } elsif (($input[$i]) || ($input[$i] eq '0')) {
10669:                     $correct[$i]=$input[$i];
10670:                  }
10671:              }
10672:           }
10673:        }
10674:        for (my $i=0;$i<$number;$i++) {
10675:           if ((!$correct[$i]) && ($correct[$i] ne '0')) {
10676:              $result.='<br /><span class="LC_error">'.
10677:                       &mt('No correct result given for question "[_1]"!',
10678:                           $env{'form.question:'.$i}).'</span>';
10679:           }
10680:        }
10681:        $result.='<br />'.&mt("Correct answer: [_1]",join(', ',map { ((($_) || ($_ eq '0'))?$_:'-') } @correct));
10682:     }
10683: # Start grading
10684:     my $pcorrect=$env{'form.pcorrect'};
10685:     my $pincorrect=$env{'form.pincorrect'};
10686:     my $storecount=0;
10687:     my %users=();
10688:     foreach my $key (keys(%env)) {
10689:        my $user='';
10690:        if ($key=~/^form\.student\:(.*)$/) {
10691:           $user=$1;
10692:        }
10693:        if ($key=~/^form\.unknown\:(.*)$/) {
10694:           my $id=$1;
10695:           if (($env{'form.uname'.$id}) && ($env{'form.udom'.$id})) {
10696:              $user=$env{'form.uname'.$id}.':'.$env{'form.udom'.$id};
10697:           } elsif ($env{'form.multi'.$id}) {
10698:              $user=$env{'form.multi'.$id};
10699:           }
10700:        }
10701:        if ($user) {
10702:           if ($users{$user}) {
10703:              $result.='<br /><span class="LC_warning">'.
10704:                       &mt('More than one entry found for [_1]!','<tt>'.$user.'</tt>').
10705:                       '</span><br />';
10706:           }
10707:           $users{$user}=1;
10708:           my @answer=split(/\,/,$env{$key});
10709:           my $sum=0;
10710:           my $realnumber=$number;
10711:           for (my $i=0;$i<$number;$i++) {
10712:              if  ($correct[$i] eq '-') {
10713:                 $realnumber--;
10714:              } elsif (($answer[$i]) || ($answer[$i]=~/^[0\.]+$/)) {
10715:                 if ($gradingmechanism eq 'attendance') {
10716:                    $sum+=$pcorrect;
10717:                 } elsif ($correct[$i] eq '*') {
10718:                    $sum+=$pcorrect;
10719:                 } else {
10720: # We actually grade if correct or not
10721:                    my $increment=$pincorrect;
10722: # Special case: numerical answer "0"
10723:                    if ($correct[$i] eq '0') {
10724:                       if ($answer[$i]=~/^[0\.]+$/) {
10725:                          $increment=$pcorrect;
10726:                       }
10727: # General numerical answer, both evaluate to something non-zero
10728:                    } elsif ((1.0*$correct[$i]!=0) && (1.0*$answer[$i]!=0)) {
10729:                       if (1.0*$correct[$i]==1.0*$answer[$i]) {
10730:                          $increment=$pcorrect;
10731:                       }
10732: # Must be just alphanumeric
10733:                    } elsif ($answer[$i] eq $correct[$i]) {
10734:                       $increment=$pcorrect;
10735:                    }
10736:                    $sum+=$increment;
10737:                 }
10738:              }
10739:           }
10740:           my $ave=$sum/(100*$realnumber);
10741: # Store
10742:           my ($username,$domain)=split(/\:/,$user);
10743:           my %grades=();
10744:           $grades{"resource.$part.solved"}='correct_by_override';
10745:           $grades{"resource.$part.awarded"}=$ave;
10746:           $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
10747:           my $returncode=&Apache::lonnet::cstore(\%grades,$symb,
10748:                                                  $env{'request.course.id'},
10749:                                                  $domain,$username);
10750:           if ($returncode ne 'ok') {
10751:              $result.="<br /><span class=\"LC_error\">Failed to save student $username:$domain. Message when trying to save was ($returncode)</span>";
10752:           } else {
10753:              $storecount++;
10754:           }
10755:        }
10756:     }
10757: # We are done
10758:     $result.='<br />'.&mt('Successfully stored grades for [quant,_1,student].',$storecount).
10759:              '</td>'.
10760:              &Apache::loncommon::end_data_table_row().
10761:              &Apache::loncommon::end_data_table();
10762:     return $result;
10763: }
10764: 
10765: sub navmap_errormsg {
10766:     return '<div class="LC_error">'.
10767:            &mt('An error occurred retrieving information about resources in the course.').'<br />'.
10768:            &mt('It is recommended that you [_1]re-initialize the course[_2] and then return to this grading page.','<a href="/adm/roles?selectrole=1&newrole='.$env{'request.role'}.'">','</a>').
10769:            '</div>';
10770: }
10771: 
10772: sub startpage {
10773:     my ($r,$symb,$crumbs,$onlyfolderflag,$nodisplayflag,$stuvcurrent,$stuvdisp,$nomenu,$js,$onload,$divforres) = @_;
10774:     my %args;
10775:     if ($onload) {
10776:          my %loaditems = (
10777:                         'onload' => $onload,
10778:                       );
10779:          $args{'add_entries'} = \%loaditems;
10780:     }
10781:     if ($nomenu) {
10782:         $args{'only_body'} = 1;
10783:         $r->print(&Apache::loncommon::start_page("Student's Version",$js,\%args));
10784:     } else {
10785:         unshift(@$crumbs,{href=>&href_symb_cmd($symb,'gradingmenu'),text=>"Grading"});
10786:         $args{'bread_crumbs'} = $crumbs;
10787:         $r->print(&Apache::loncommon::start_page('Grading',$js,\%args));
10788:     }
10789:     unless ($nodisplayflag) {
10790:        $r->print(&Apache::lonhtmlcommon::resource_info_box($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp,$divforres));
10791:     }
10792: }
10793: 
10794: sub select_problem {
10795:     my ($r)=@_;
10796:     $r->print('<h3>'.&mt('Select the problem or one of the problems you want to grade').'</h3><form action="/adm/grades">');
10797:     $r->print(&Apache::lonstathelpers::problem_selector('.',undef,1,undef,undef,1));
10798:     $r->print('<input type="hidden" name="command" value="gradingmenu" />');
10799:     $r->print('<input type="submit" value="'.&mt('Next').' &rarr;" /></form>');
10800: }
10801: 
10802: sub handler {
10803:     my $request=$_[0];
10804:     &reset_caches();
10805:     if ($request->header_only) {
10806:         &Apache::loncommon::content_type($request,'text/html');
10807:         $request->send_http_header;
10808:         return OK;
10809:     }
10810:     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});
10811: 
10812: # see what command we need to execute
10813:  
10814:     my @commands=&Apache::loncommon::get_env_multiple('form.command');
10815:     my $command=$commands[0];
10816: 
10817:     &init_perm();
10818:     if (!$env{'request.course.id'}) {
10819:         unless ((&Apache::lonnet::allowed('usc',$env{'request.role.domain'})) &&
10820:                 ($command =~ /^scantronupload/)) {
10821:             # Not in a course.
10822:             $env{'user.error.msg'}="/adm/grades::vgr:0:0:Cannot display grades page outside course context";
10823:             return HTTP_NOT_ACCEPTABLE;
10824:         }
10825:     } elsif (!%perm) {
10826:         $request->internal_redirect('/adm/quickgrades');
10827:         return OK;
10828:     }
10829:     &Apache::loncommon::content_type($request,'text/html');
10830:     $request->send_http_header;
10831: 
10832:     if ($#commands > 0) {
10833: 	&Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
10834:     }
10835: 
10836: # see what the symb is
10837: 
10838:     my $symb=$env{'form.symb'};
10839:     unless ($symb) {
10840:        (my $url=$env{'form.url'}) =~ s-^https*://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
10841:        $symb=&Apache::lonnet::symbread($url);
10842:     }
10843:     &Apache::lonenc::check_decrypt(\$symb);
10844: 
10845:     $ssi_error = 0;
10846:     if (($symb eq '' || $command eq '') && ($env{'request.course.id'})) {
10847: #
10848: # Not called from a resource, but inside a course
10849: #
10850:         &startpage($request,undef,[],1,1);
10851:         &select_problem($request);
10852:     } else {
10853:         if ($command eq 'submission' && $perm{'vgr'}) {
10854:             my ($stuvcurrent,$stuvdisp,$versionform,$js,$onload);
10855:             if (($env{'form.student'} ne '') && ($env{'form.userdom'} ne '')) {
10856:                 ($stuvcurrent,$stuvdisp,$versionform,$js) =
10857:                     &choose_task_version_form($symb,$env{'form.student'},
10858:                                               $env{'form.userdom'});
10859:             }
10860:             my $divforres;
10861:             if ($env{'form.student'} eq '') {
10862:                 $js .= &part_selector_js();
10863:                 $onload = "toggleParts('gradesub');";
10864:             } else {
10865:                 $divforres = 1;
10866:             }
10867:             &startpage($request,$symb,[{href=>"", text=>"Student Submissions"}],undef,undef,$stuvcurrent,$stuvdisp,undef,$js,$onload,$divforres);
10868:             if ($versionform) {
10869:                 $request->print($versionform);
10870:             }
10871:             ($env{'form.student'} eq '' ? &listStudents($request,$symb,'',$divforres) : &submission($request,0,0,$symb,$divforres,$command));
10872:         } elsif ($command eq 'versionsub' && $perm{'vgr'}) {
10873:             my ($stuvcurrent,$stuvdisp,$versionform,$js) =
10874:                 &choose_task_version_form($symb,$env{'form.student'},
10875:                                           $env{'form.userdom'},
10876:                                           $env{'form.inhibitmenu'});
10877:             &startpage($request,$symb,[{href=>"", text=>"Previous Student Version"}],undef,undef,$stuvcurrent,$stuvdisp,$env{'form.inhibitmenu'},$js);
10878:             if ($versionform) {
10879:                 $request->print($versionform);
10880:             }
10881:             $request->print('<br clear="all" />');
10882:             $request->print(&show_previous_task_version($request,$symb));
10883:         } elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) {
10884:             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
10885:                                        {href=>'',text=>'Select student'}],1,1);
10886:             &pickStudentPage($request,$symb);
10887:         } elsif ($command eq 'displayPage' && $perm{'vgr'}) {
10888:             &startpage($request,$symb,
10889:                                       [{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
10890:                                        {href=>'',text=>'Select student'},
10891:                                        {href=>'',text=>'Grade student'}],1,1);
10892:             &displayPage($request,$symb);
10893:         } elsif ($command eq 'gradeByPage' && $perm{'mgr'}) {
10894:             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
10895:                                        {href=>'',text=>'Select student'},
10896:                                        {href=>'',text=>'Grade student'},
10897:                                        {href=>'',text=>'Store grades'}],1,1);
10898:             &updateGradeByPage($request,$symb);
10899:         } elsif ($command eq 'processGroup' && $perm{'vgr'}) {
10900:             &startpage($request,$symb,[{href=>'',text=>'...'},
10901:                                        {href=>'',text=>'Modify grades'}],undef,undef,undef,undef,undef,undef,undef,1);
10902:             &processGroup($request,$symb);
10903:         } elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {
10904:             &startpage($request,$symb);
10905:             $request->print(&grading_menu($request,$symb));
10906:         } elsif ($command eq 'individual' && $perm{'vgr'}) {
10907:             &startpage($request,$symb,[{href=>'',text=>'Select individual students to grade'}]);
10908:             $request->print(&submit_options($request,$symb));
10909:         } elsif ($command eq 'ungraded' && $perm{'vgr'}) {
10910:             my $js = &part_selector_js();
10911:             my $onload = "toggleParts('gradesub');";
10912:             &startpage($request,$symb,[{href=>'',text=>'Grade ungraded submissions'}],
10913:                        undef,undef,undef,undef,undef,$js,$onload);
10914:             $request->print(&listStudents($request,$symb,'graded'));
10915:         } elsif ($command eq 'table' && $perm{'vgr'}) {
10916:             &startpage($request,$symb,[{href=>"", text=>"Grading table"}]);
10917:             $request->print(&submit_options_table($request,$symb));
10918:         } elsif ($command eq 'all_for_one' && $perm{'vgr'}) {
10919:             &startpage($request,$symb,[{href=>'',text=>'Grade page/folder for one student'}],1,1);
10920:             $request->print(&submit_options_sequence($request,$symb));
10921:         } elsif ($command eq 'viewgrades' && $perm{'vgr'}) {
10922:             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"table"), text=>"Grading table"},{href=>'', text=>"Modify grades"}]);
10923:             $request->print(&viewgrades($request,$symb));
10924:         } elsif ($command eq 'handgrade' && $perm{'mgr'}) {
10925:             &startpage($request,$symb,[{href=>'',text=>'...'},
10926:                                        {href=>'',text=>'Store grades'}]);
10927:             $request->print(&processHandGrade($request,$symb));
10928:         } elsif ($command eq 'editgrades' && $perm{'mgr'}) {
10929:             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"table"), text=>"Grading table"},
10930:                                        {href=>&href_symb_cmd($symb,'viewgrades').'&group=all&section=all&Status=Active',
10931:                                                                              text=>"Modify grades"},
10932:                                        {href=>'', text=>"Store grades"}]);
10933:             $request->print(&editgrades($request,$symb));
10934:         } elsif ($command eq 'initialverifyreceipt' && $perm{'vgr'}) {
10935:             &startpage($request,$symb,[{href=>'',text=>'Verify Receipt Number'}]);
10936:             $request->print(&initialverifyreceipt($request,$symb));
10937:         } elsif ($command eq 'verify' && $perm{'vgr'}) {
10938:             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"initialverifyreceipt"),text=>'Verify Receipt Number'},
10939:                                        {href=>'',text=>'Verification Result'}]);
10940:             $request->print(&verifyreceipt($request,$symb));
10941:         } elsif ($command eq 'processclicker' && $perm{'mgr'}) {
10942:             &startpage($request,$symb,[{href=>'', text=>'Process clicker'}]);
10943:             $request->print(&process_clicker($request,$symb));
10944:         } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {
10945:             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'processclicker'), text=>'Process clicker'},
10946:                                        {href=>'', text=>'Process clicker file'}]);
10947:             $request->print(&process_clicker_file($request,$symb));
10948:         } elsif ($command eq 'assignclickergrades' && $perm{'mgr'}) {
10949:             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'processclicker'), text=>'Process clicker'},
10950:                                        {href=>'', text=>'Process clicker file'},
10951:                                        {href=>'', text=>'Store grades'}]);
10952:             $request->print(&assign_clicker_grades($request,$symb));
10953:         } elsif ($command eq 'csvform' && $perm{'mgr'}) {
10954:             &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
10955:             $request->print(&upcsvScores_form($request,$symb));
10956:         } elsif ($command eq 'csvupload' && $perm{'mgr'}) {
10957:             &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
10958:             $request->print(&csvupload($request,$symb));
10959:         } elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) {
10960:             &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
10961:             $request->print(&csvuploadmap($request,$symb));
10962:         } elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {
10963:             if ($env{'form.associate'} ne 'Reverse Association') {
10964:                 &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
10965:                 $request->print(&csvuploadoptions($request,$symb));
10966:             } else {
10967:                 if ( $env{'form.upfile_associate'} ne 'reverse' ) {
10968:                     $env{'form.upfile_associate'} = 'reverse';
10969:                 } else {
10970:                     $env{'form.upfile_associate'} = 'forward';
10971:                 }
10972:                 &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
10973:                 $request->print(&csvuploadmap($request,$symb));
10974:             }
10975:         } elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
10976:             &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
10977:             $request->print(&csvuploadassign($request,$symb));
10978:         } elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
10979:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1,
10980:                        undef,undef,undef,undef,'toggleScantab(document.rules);');
10981:             $request->print(&scantron_selectphase($request,undef,$symb));
10982:         } elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
10983:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
10984:             $request->print(&scantron_do_warning($request,$symb));
10985:         } elsif ($command eq 'scantron_validate' && $perm{'mgr'}) {
10986:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
10987:             $request->print(&scantron_validate_file($request,$symb));
10988:         } elsif ($command eq 'scantron_process' && $perm{'mgr'}) {
10989:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
10990:             $request->print(&scantron_process_students($request,$symb));
10991:         } elsif ($command eq 'scantronupload' &&
10992:                  (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
10993:                   &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
10994:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1,
10995:                        undef,undef,undef,undef,'toggleScantab(document.rules);');
10996:             $request->print(&scantron_upload_scantron_data($request,$symb));
10997:         } elsif ($command eq 'scantronupload_save' &&
10998:                  (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
10999:                   &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
11000:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
11001:             $request->print(&scantron_upload_scantron_data_save($request,$symb));
11002:         } elsif ($command eq 'scantron_download' &&
11003:                  &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
11004:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
11005:             $request->print(&scantron_download_scantron_data($request,$symb));
11006:         } elsif ($command eq 'checksubmissions' && $perm{'vgr'}) {
11007:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
11008:             $request->print(&checkscantron_results($request,$symb));
11009:         } elsif ($command eq 'downloadfilesselect' && $perm{'vgr'}) {
11010:             my $js = &part_selector_js();
11011:             my $onload = "toggleParts('gradingMenu');";
11012:             &startpage($request,$symb,[{href=>'', text=>'Select which submissions to download'}],
11013:                        undef,undef,undef,undef,undef,$js,$onload);
11014:             $request->print(&submit_options_download($request,$symb));
11015:          } elsif ($command eq 'downloadfileslink' && $perm{'vgr'}) {
11016:             &startpage($request,$symb,
11017:    [{href=>&href_symb_cmd($symb,'downloadfilesselect'), text=>'Select which submissions to download'},
11018:     {href=>'', text=>'Download submitted files'}],
11019:                undef,undef,undef,undef,undef,undef,undef,1);
11020:             &submit_download_link($request,$symb);
11021:         } elsif ($command) {
11022:             &startpage($request,$symb,[{href=>'', text=>'Access denied'}]);
11023:             $request->print('<p class="LC_error">'.&mt('Access Denied ([_1])',$command).'</p>');
11024:         }
11025:     }
11026:     if ($ssi_error) {
11027: 	&ssi_print_error($request);
11028:     }
11029:     $request->print(&Apache::loncommon::end_page());
11030:     &reset_caches();
11031:     return OK;
11032: }
11033: 
11034: 1;
11035: 
11036: __END__;
11037: 
11038: 
11039: =head1 NAME
11040: 
11041: Apache::grades
11042: 
11043: =head1 SYNOPSIS
11044: 
11045: Handles the viewing of grades.
11046: 
11047: This is part of the LearningOnline Network with CAPA project
11048: described at http://www.lon-capa.org.
11049: 
11050: =head1 OVERVIEW
11051: 
11052: Do an ssi with retries:
11053: While I'd love to factor out this with the vesrion in lonprintout,
11054: that would either require a data coupling between modules, which I refuse to perpetuate (there's quite enough of that already), or would require the invention of another infrastructure
11055: I'm not quite ready to invent (e.g. an ssi_with_retry object).
11056: 
11057: At least the logic that drives this has been pulled out into loncommon.
11058: 
11059: 
11060: 
11061: ssi_with_retries - Does the server side include of a resource.
11062:                      if the ssi call returns an error we'll retry it up to
11063:                      the number of times requested by the caller.
11064:                      If we still have a problem, no text is appended to the
11065:                      output and we set some global variables.
11066:                      to indicate to the caller an SSI error occurred.  
11067:                      All of this is supposed to deal with the issues described
11068:                      in LON-CAPA BZ 5631 see:
11069:                      http://bugs.lon-capa.org/show_bug.cgi?id=5631
11070:                      by informing the user that this happened.
11071: 
11072: Parameters:
11073:   resource   - The resource to include.  This is passed directly, without
11074:                interpretation to lonnet::ssi.
11075:   form       - The form hash parameters that guide the interpretation of the resource
11076:                
11077:   retries    - Number of retries allowed before giving up completely.
11078: Returns:
11079:   On success, returns the rendered resource identified by the resource parameter.
11080: Side Effects:
11081:   The following global variables can be set:
11082:    ssi_error                - If an unrecoverable error occurred this becomes true.
11083:                               It is up to the caller to initialize this to false
11084:                               if desired.
11085:    ssi_error_resource  - If an unrecoverable error occurred, this is the value
11086:                               of the resource that could not be rendered by the ssi
11087:                               call.
11088:    ssi_error_message   - The error string fetched from the ssi response
11089:                               in the event of an error.
11090: 
11091: 
11092: =head1 HANDLER SUBROUTINE
11093: 
11094: ssi_with_retries()
11095: 
11096: =head1 SUBROUTINES
11097: 
11098: =over
11099: 
11100: =item scantron_get_correction() : 
11101: 
11102:    Builds the interface screen to interact with the operator to fix a
11103:    specific error condition in a specific scanline
11104: 
11105:  Arguments:
11106:     $r           - Apache request object
11107:     $i           - number of the current scanline
11108:     $scan_record - hash ref as returned from &scantron_parse_scanline()
11109:     $scan_config - hash ref as returned from &Apache::lonnet::get_scantron_config()
11110:     $line        - full contents of the current scanline
11111:     $error       - error condition, valid values are
11112:                    'incorrectCODE', 'duplicateCODE',
11113:                    'doublebubble', 'missingbubble',
11114:                    'duplicateID', 'incorrectID'
11115:     $arg         - extra information needed
11116:        For errors:
11117:          - duplicateID   - paper number that this studentID was seen before on
11118:          - duplicateCODE - array ref of the paper numbers this CODE was
11119:                            seen on before
11120:          - incorrectCODE - current incorrect CODE 
11121:          - doublebubble  - array ref of the bubble lines that have double
11122:                            bubble errors
11123:          - missingbubble - array ref of the bubble lines that have missing
11124:                            bubble errors
11125: 
11126:    $randomorder - True if exam folder has randomorder set
11127:    $randompick  - True if exam folder has randompick set
11128:    $respnumlookup - Reference to HASH mapping question numbers in bubble lines
11129:                      for current line to question number used for same question
11130:                      in "Master Seqence" (as seen by Course Coordinator).
11131:    $startline   - Reference to hash where key is question number (0 is first)
11132:                   and value is number of first bubble line for current student
11133:                   or code-based randompick and/or randomorder.
11134: 
11135: 
11136: =item  scantron_get_maxbubble() : 
11137: 
11138:    Arguments:
11139:        $nav_error  - Reference to scalar which is a flag to indicate a
11140:                       failure to retrieve a navmap object.
11141:        if $nav_error is set to 1 by scantron_get_maxbubble(), the 
11142:        calling routine should trap the error condition and display the warning
11143:        found in &navmap_errormsg().
11144: 
11145:        $scantron_config - Reference to bubblesheet format configuration hash.
11146: 
11147:    Returns the maximum number of bubble lines that are expected to
11148:    occur. Does this by walking the selected sequence rendering the
11149:    resource and then checking &Apache::lonxml::get_problem_counter()
11150:    for what the current value of the problem counter is.
11151: 
11152:    Caches the results to $env{'form.scantron_maxbubble'},
11153:    $env{'form.scantron.bubble_lines.n'}, 
11154:    $env{'form.scantron.first_bubble_line.n'} and
11155:    $env{"form.scantron.sub_bubblelines.n"}
11156:    which are the total number of bubble lines, the number of bubble
11157:    lines for response n and number of the first bubble line for response n,
11158:    and a comma separated list of numbers of bubble lines for sub-questions
11159:    (for optionresponse, matchresponse, and rankresponse items), for response n.  
11160: 
11161: 
11162: =item  scantron_validate_missingbubbles() : 
11163: 
11164:    Validates all scanlines in the selected file to not have any
11165:     answers that don't have bubbles that have not been verified
11166:     to be bubble free.
11167: 
11168: =item  scantron_process_students() : 
11169: 
11170:    Routine that does the actual grading of the bubblesheet information.
11171: 
11172:    The parsed scanline hash is added to %env 
11173: 
11174:    Then foreach unskipped scanline it does an &Apache::lonnet::ssi()
11175:    foreach resource , with the form data of
11176: 
11177: 	'submitted'     =>'scantron' 
11178: 	'grade_target'  =>'grade',
11179: 	'grade_username'=> username of student
11180: 	'grade_domain'  => domain of student
11181: 	'grade_courseid'=> of course
11182: 	'grade_symb'    => symb of resource to grade
11183: 
11184:     This triggers a grading pass. The problem grading code takes care
11185:     of converting the bubbled letter information (now in %env) into a
11186:     valid submission.
11187: 
11188: =item  scantron_upload_scantron_data() :
11189: 
11190:     Creates the screen for adding a new bubblesheet data file to a course.
11191: 
11192: =item  scantron_upload_scantron_data_save() : 
11193: 
11194:    Adds a provided bubble information data file to the course if user
11195:    has the correct privileges to do so. 
11196: 
11197: =item  valid_file() :
11198: 
11199:    Validates that the requested bubble data file exists in the course.
11200: 
11201: =item  scantron_download_scantron_data() : 
11202: 
11203:    Shows a list of the three internal files (original, corrected,
11204:    skipped) for a specific bubblesheet data file that exists in the
11205:    course.
11206: 
11207: =item  scantron_validate_ID() : 
11208: 
11209:    Validates all scanlines in the selected file to not have any
11210:    invalid or underspecified student/employee IDs
11211: 
11212: =item navmap_errormsg() :
11213: 
11214:    Returns HTML mark-up inside a <div></div> with a link to re-initialize the course.
11215:    Should be called whenever the request to instantiate a navmap object fails.  
11216: 
11217: =back
11218: 
11219: =cut

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>