Annotation of loncom/homework/grades.pm, revision 1.596.2.12.2.38

1.17      albertel    1: # The LearningOnline Network with CAPA
1.13      albertel    2: # The LON-CAPA Grading handler
1.17      albertel    3: #
1.596.2.12.2.  8(raebur    4:6): # $Id: grades.pm,v 1.596.2.12.2.37 2016/10/14 17:33:34 raeburn Exp $
1.17      albertel    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: #
1.1       albertel   28: 
1.529     jms        29: 
                     30: 
1.1       albertel   31: package Apache::grades;
                     32: use strict;
                     33: use Apache::style;
                     34: use Apache::lonxml;
                     35: use Apache::lonnet;
1.3       albertel   36: use Apache::loncommon;
1.112     ng         37: use Apache::lonhtmlcommon;
1.68      ng         38: use Apache::lonnavmaps;
1.1       albertel   39: use Apache::lonhomework;
1.456     banghart   40: use Apache::lonpickcode;
1.55      matthew    41: use Apache::loncoursedata;
1.362     albertel   42: use Apache::lonmsg();
1.596.2.4  raeburn    43: use Apache::Constants qw(:common :http);
1.167     sakharuk   44: use Apache::lonlocal;
1.386     raeburn    45: use Apache::lonenc;
1.596.2.4  raeburn    46: use Apache::bridgetask();
1.170     albertel   47: use String::Similarity;
1.359     www        48: use LONCAPA;
                     49: 
1.315     bowersj2   50: use POSIX qw(floor);
1.87      www        51: 
1.435     foxr       52: 
1.513     foxr       53: 
1.435     foxr       54: my %perm=();
1.596.2.12.2.  (raeburn   55:): my %old_essays=();
1.447     foxr       56: 
1.513     foxr       57: #  These variables are used to recover from ssi errors
                     58: 
                     59: my $ssi_retries = 5;
                     60: my $ssi_error;
                     61: my $ssi_error_resource;
                     62: my $ssi_error_message;
                     63: 
                     64: 
                     65: sub ssi_with_retries {
                     66:     my ($resource, $retries, %form) = @_;
                     67:     my ($content, $response) = &Apache::loncommon::ssi_with_retries($resource, $retries, %form);
                     68:     if ($response->is_error) {
                     69: 	$ssi_error          = 1;
                     70: 	$ssi_error_resource = $resource;
                     71: 	$ssi_error_message  = $response->code . " " . $response->message;
                     72:     }
                     73: 
                     74:     return $content;
                     75: 
                     76: }
                     77: #
                     78: #  Prodcuces an ssi retry failure error message to the user:
                     79: #
                     80: 
                     81: sub ssi_print_error {
                     82:     my ($r) = @_;
1.516     raeburn    83:     my $helpurl = &Apache::loncommon::top_nav_help('Helpdesk');
                     84:     $r->print('
                     85: <br />
                     86: <h2>'.&mt('An unrecoverable network error occurred:').'</h2>
                     87: <p>
                     88: '.&mt('Unable to retrieve a resource from a server:').'<br />
                     89: '.&mt('Resource:').' '.$ssi_error_resource.'<br />
                     90: '.&mt('Error:').' '.$ssi_error_message.'
                     91: </p>
                     92: <p>'.
                     93: &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 />'.
                     94: &mt('If the error persists, please contact the [_1] for assistance.',$helpurl).
                     95: '</p>');
                     96:     return;
1.513     foxr       97: }
                     98: 
1.44      ng         99: #
1.146     albertel  100: # --- Retrieve the parts from the metadata file.---
1.44      ng        101: sub getpartlist {
1.582     raeburn   102:     my ($symb,$errorref) = @_;
1.439     albertel  103: 
                    104:     my $navmap   = Apache::lonnavmaps::navmap->new();
1.582     raeburn   105:     unless (ref($navmap)) {
                    106:         if (ref($errorref)) { 
                    107:             $$errorref = 'navmap';
                    108:             return;
                    109:         }
                    110:     }
1.439     albertel  111:     my $res      = $navmap->getBySymb($symb);
                    112:     my $partlist = $res->parts();
                    113:     my $url      = $res->src();
                    114:     my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys'));
                    115: 
1.146     albertel  116:     my @stores;
1.439     albertel  117:     foreach my $part (@{ $partlist }) {
1.146     albertel  118: 	foreach my $key (@metakeys) {
                    119: 	    if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }
                    120: 	}
                    121:     }
                    122:     return @stores;
1.2       albertel  123: }
                    124: 
1.44      ng        125: # --- Get the symbolic name of a problem and the url
1.324     albertel  126: sub get_symb {
1.173     albertel  127:     my ($request,$silent) = @_;
1.596.2.12.2.  (raeburn  128:):     my $symb=$env{'form.symb'};
                    129:):     unless ($symb) {
                    130:):         (my $url=$env{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
                    131:):         $symb = &Apache::lonnet::symbread($url);
                    132:):         if ($symb eq '') { 
                    133:): 	    if (!$silent) {
                    134:):                 $request->print(&mt("Unable to handle ambiguous references: [_1].",$url));
                    135:): 	        return ();
                    136:): 	    }
                    137:):         }
1.173     albertel  138:     }
1.418     albertel  139:     &Apache::lonenc::check_decrypt(\$symb);
1.324     albertel  140:     return ($symb);
1.32      ng        141: }
                    142: 
1.129     ng        143: #--- Format fullname, username:domain if different for display
                    144: #--- Use anywhere where the student names are listed
                    145: sub nameUserString {
                    146:     my ($type,$fullname,$uname,$udom) = @_;
                    147:     if ($type eq 'header') {
1.485     albertel  148: 	return '<b>&nbsp;'.&mt('Fullname').'&nbsp;</b><span class="LC_internal_info">('.&mt('Username').')</span>';
1.129     ng        149:     } else {
1.398     albertel  150: 	return '&nbsp;'.$fullname.'<span class="LC_internal_info">&nbsp;('.$uname.
                    151: 	    ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</span>';
1.129     ng        152:     }
                    153: }
                    154: 
1.44      ng        155: #--- Get the partlist and the response type for a given problem. ---
                    156: #--- Indicate if a response type is coded handgraded or not. ---
1.39      ng        157: sub response_type {
1.582     raeburn   158:     my ($symb,$response_error) = @_;
1.377     albertel  159: 
                    160:     my $navmap = Apache::lonnavmaps::navmap->new();
1.582     raeburn   161:     unless (ref($navmap)) {
                    162:         if (ref($response_error)) {
                    163:             $$response_error = 1;
                    164:         }
                    165:         return;
                    166:     }
1.377     albertel  167:     my $res = $navmap->getBySymb($symb);
1.593     raeburn   168:     unless (ref($res)) {
                    169:         $$response_error = 1;
                    170:         return;
                    171:     }
1.377     albertel  172:     my $partlist = $res->parts();
1.392     albertel  173:     my %vPart = 
                    174: 	map { $_ => 1 } (&Apache::loncommon::get_env_multiple('form.vPart'));
1.377     albertel  175:     my (%response_types,%handgrade);
                    176:     foreach my $part (@{ $partlist }) {
1.392     albertel  177: 	next if (%vPart && !exists($vPart{$part}));
                    178: 
1.377     albertel  179: 	my @types = $res->responseType($part);
                    180: 	my @ids = $res->responseIds($part);
                    181: 	for (my $i=0; $i < scalar(@ids); $i++) {
                    182: 	    $response_types{$part}{$ids[$i]} = $types[$i];
                    183: 	    $handgrade{$part.'_'.$ids[$i]} = 
                    184: 		&Apache::lonnet::EXT('resource.'.$part.'_'.$ids[$i].
                    185: 				     '.handgrade',$symb);
1.41      ng        186: 	}
                    187:     }
1.377     albertel  188:     return ($partlist,\%handgrade,\%response_types);
1.39      ng        189: }
                    190: 
1.375     albertel  191: sub flatten_responseType {
                    192:     my ($responseType) = @_;
                    193:     my @part_response_id =
                    194: 	map { 
                    195: 	    my $part = $_;
                    196: 	    map {
                    197: 		[$part,$_]
                    198: 		} sort(keys(%{ $responseType->{$part} }));
                    199: 	} sort(keys(%$responseType));
                    200:     return @part_response_id;
                    201: }
                    202: 
1.207     albertel  203: sub get_display_part {
1.324     albertel  204:     my ($partID,$symb)=@_;
1.207     albertel  205:     my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
                    206:     if (defined($display) and $display ne '') {
1.577     bisitz    207:         $display.= ' (<span class="LC_internal_info">'
                    208:                   .&mt('Part ID: [_1]',$partID).'</span>)';
1.207     albertel  209:     } else {
                    210: 	$display=$partID;
                    211:     }
                    212:     return $display;
                    213: }
1.269     raeburn   214: 
1.118     ng        215: #--- Show resource title
                    216: #--- and parts and response type
                    217: sub showResourceInfo {
1.582     raeburn   218:     my ($symb,$probTitle,$checkboxes,$res_error) = @_;
1.398     albertel  219:     my $result = '<h3>'.&mt('Current Resource').': '.$probTitle.'</h3>'."\n";
1.582     raeburn   220:     my ($partlist,$handgrade,$responseType) = &response_type($symb,$res_error);
                    221:     if (ref($res_error)) {
                    222:         if ($$res_error) {
                    223:             return;
                    224:         }
                    225:     }
1.584     bisitz    226:     $result.=&Apache::loncommon::start_data_table()
                    227:             .&Apache::loncommon::start_data_table_header_row();
                    228:     if ($checkboxes) {
                    229:         $result.='<th>&nbsp;</th>';
                    230:     }
                    231:     $result.='<th>'.&mt('Problem Part').'</th>'
                    232:             .'<th>'.&mt('Res. ID').'</th>'
                    233:             .'<th>'.&mt('Type').'</th>'
                    234:             .&Apache::loncommon::end_data_table_header_row();
1.126     ng        235:     my %resptype = ();
1.122     ng        236:     my $hdgrade='no';
1.154     albertel  237:     my %partsseen;
1.524     raeburn   238:     foreach my $partID (sort(keys(%$responseType))) {
1.584     bisitz    239:         foreach my $resID (sort(keys(%{ $responseType->{$partID} }))) {
                    240:             my $handgrade=$$handgrade{$partID.'_'.$resID};
                    241:             my $responsetype = $responseType->{$partID}->{$resID};
                    242:             $hdgrade = $handgrade if ($handgrade eq 'yes');
                    243:             $result.=&Apache::loncommon::start_data_table_row();
                    244:             if ($checkboxes) {
                    245:                 if (exists($partsseen{$partID})) {
                    246:                     $result.="<td>&nbsp;</td>";
                    247:                 } else {
                    248:                     $result.="<td><input type='checkbox' name='vPart' value='$partID' checked='checked' /></td>";
                    249:                 }
                    250:                 $partsseen{$partID}=1;
                    251:             }
                    252:             my $display_part=&get_display_part($partID,$symb);
                    253:             $result.='<td>'.$display_part.'</td>'
                    254:                     .'<td>'.'<span class="LC_internal_info">'.$resID.'</span></td>'
                    255:                     .'<td>'.&mt($responsetype).'</td>'
1.596.2.12.2.  2(raebur  256:2): #                   .'<td><b>'.&mt('Handgrade: [_1]',$handgrade).'</b></td>'
1.584     bisitz    257:                     .&Apache::loncommon::end_data_table_row();
                    258:         }
1.118     ng        259:     }
1.584     bisitz    260:     $result.=&Apache::loncommon::end_data_table();
1.147     albertel  261:     return $result,$responseType,$hdgrade,$partlist,$handgrade;
1.118     ng        262: }
                    263: 
1.434     albertel  264: sub reset_caches {
                    265:     &reset_analyze_cache();
                    266:     &reset_perm();
1.596.2.12.2.  (raeburn  267:):     &reset_old_essays();
1.434     albertel  268: }
                    269: 
                    270: {
                    271:     my %analyze_cache;
1.557     raeburn   272:     my %analyze_cache_formkeys;
1.148     albertel  273: 
1.434     albertel  274:     sub reset_analyze_cache {
                    275: 	undef(%analyze_cache);
1.557     raeburn   276:         undef(%analyze_cache_formkeys);
1.434     albertel  277:     }
                    278: 
                    279:     sub get_analyze {
1.596.2.12.2.  (raeburn  280:): 	my ($symb,$uname,$udom,$no_increment,$add_to_hash,$type,$trial,$rndseed,$bubbles_per_row)=@_;
1.434     albertel  281: 	my $key = "$symb\0$uname\0$udom";
1.596.2.2  raeburn   282:         if ($type eq 'randomizetry') {
                    283:             if ($trial ne '') {
                    284:                 $key .= "\0".$trial;
                    285:             }
                    286:         }
1.557     raeburn   287: 	if (exists($analyze_cache{$key})) {
                    288:             my $getupdate = 0;
                    289:             if (ref($add_to_hash) eq 'HASH') {
                    290:                 foreach my $item (keys(%{$add_to_hash})) {
                    291:                     if (ref($analyze_cache_formkeys{$key}) eq 'HASH') {
                    292:                         if (!exists($analyze_cache_formkeys{$key}{$item})) {
                    293:                             $getupdate = 1;
                    294:                             last;
                    295:                         }
                    296:                     } else {
                    297:                         $getupdate = 1;
                    298:                     }
                    299:                 }
                    300:             }
                    301:             if (!$getupdate) {
                    302:                 return $analyze_cache{$key};
                    303:             }
                    304:         }
1.434     albertel  305: 
                    306: 	my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
                    307: 	$url=&Apache::lonnet::clutter($url);
1.557     raeburn   308:         my %form = ('grade_target'      => 'analyze',
                    309:                     'grade_domain'      => $udom,
                    310:                     'grade_symb'        => $symb,
                    311:                     'grade_courseid'    =>  $env{'request.course.id'},
                    312:                     'grade_username'    => $uname,
                    313:                     'grade_noincrement' => $no_increment);
1.596.2.12.2.  (raeburn  314:):         if ($bubbles_per_row ne '') {
                    315:):             $form{'bubbles_per_row'} = $bubbles_per_row;
                    316:):         }
1.596.2.2  raeburn   317:         if ($type eq 'randomizetry') {
                    318:             $form{'grade_questiontype'} = $type;
                    319:             if ($rndseed ne '') {
                    320:                 $form{'grade_rndseed'} = $rndseed;
                    321:             }
                    322:         }
1.557     raeburn   323:         if (ref($add_to_hash)) {
                    324:             %form = (%form,%{$add_to_hash});
1.596.2.2  raeburn   325:         }
1.557     raeburn   326: 	my $subresult=&ssi_with_retries($url, $ssi_retries,%form);
1.434     albertel  327: 	(undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
                    328: 	my %analyze=&Apache::lonnet::str2hash($subresult);
1.557     raeburn   329:         if (ref($add_to_hash) eq 'HASH') {
                    330:             $analyze_cache_formkeys{$key} = $add_to_hash;
                    331:         } else {
                    332:             $analyze_cache_formkeys{$key} = {};
                    333:         }
1.434     albertel  334: 	return $analyze_cache{$key} = \%analyze;
                    335:     }
                    336: 
                    337:     sub get_order {
1.596.2.2  raeburn   338: 	my ($partid,$respid,$symb,$uname,$udom,$no_increment,$type,$trial,$rndseed)=@_;
                    339: 	my $analyze = &get_analyze($symb,$uname,$udom,$no_increment,undef,$type,$trial,$rndseed);
1.434     albertel  340: 	return $analyze->{"$partid.$respid.shown"};
                    341:     }
                    342: 
                    343:     sub get_radiobutton_correct_foil {
1.596.2.2  raeburn   344: 	my ($partid,$respid,$symb,$uname,$udom,$type,$trial,$rndseed)=@_;
                    345: 	my $analyze = &get_analyze($symb,$uname,$udom,undef,undef,$type,$trial,$rndseed);
                    346:         my $foils = &get_order($partid,$respid,$symb,$uname,$udom,undef,$type,$trial,$rndseed);
1.555     raeburn   347:         if (ref($foils) eq 'ARRAY') {
                    348: 	    foreach my $foil (@{$foils}) {
                    349: 	        if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {
                    350: 		    return $foil;
                    351: 	        }
1.434     albertel  352: 	    }
                    353: 	}
                    354:     }
1.554     raeburn   355: 
                    356:     sub scantron_partids_tograde {
1.596.2.12.2.  (raeburn  357:):         my ($resource,$cid,$uname,$udom,$check_for_randomlist,$bubbles_per_row) = @_;
1.554     raeburn   358:         my (%analysis,@parts);
                    359:         if (ref($resource)) {
                    360:             my $symb = $resource->symb();
1.557     raeburn   361:             my $add_to_form;
                    362:             if ($check_for_randomlist) {
                    363:                 $add_to_form = { 'check_parts_withrandomlist' => 1,};
                    364:             }
1.596.2.12.2.  (raeburn  365:):             my $analyze =
                    366:):                 &get_analyze($symb,$uname,$udom,undef,$add_to_form,
                    367:):                              undef,undef,undef,$bubbles_per_row);
1.554     raeburn   368:             if (ref($analyze) eq 'HASH') {
                    369:                 %analysis = %{$analyze};
                    370:             }
                    371:             if (ref($analysis{'parts'}) eq 'ARRAY') {
                    372:                 foreach my $part (@{$analysis{'parts'}}) {
                    373:                     my ($id,$respid) = split(/\./,$part);
                    374:                     if (!&Apache::loncommon::check_if_partid_hidden($id,$symb,$udom,$uname)) {
                    375:                         push(@parts,$part);
                    376:                     }
                    377:                 }
                    378:             }
                    379:         }
                    380:         return (\%analysis,\@parts);
                    381:     }
                    382: 
1.148     albertel  383: }
1.434     albertel  384: 
1.118     ng        385: #--- Clean response type for display
1.335     albertel  386: #--- Currently filters option/rank/radiobutton/match/essay/Task
                    387: #        response types only.
1.118     ng        388: sub cleanRecord {
1.336     albertel  389:     my ($answer,$response,$symb,$partid,$respid,$record,$order,$version,
1.596.2.2  raeburn   390: 	$uname,$udom,$type,$trial,$rndseed) = @_;
1.398     albertel  391:     my $grayFont = '<span class="LC_internal_info">';
1.148     albertel  392:     if ($response =~ /^(option|rank)$/) {
                    393: 	my %answer=&Apache::lonnet::str2hash($answer);
1.596.2.12.2.  8(raebur  394:4):         my @answer = %answer;
                    395:4):         %answer = map {&HTML::Entities::encode($_, '"<>&')} @answer;
1.148     albertel  396: 	my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
                    397: 	my ($toprow,$bottomrow);
                    398: 	foreach my $foil (@$order) {
                    399: 	    if ($grading{$foil} == 1) {
                    400: 		$toprow.='<td><b>'.$answer{$foil}.'&nbsp;</b></td>';
                    401: 	    } else {
                    402: 		$toprow.='<td><i>'.$answer{$foil}.'&nbsp;</i></td>';
                    403: 	    }
1.398     albertel  404: 	    $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
1.148     albertel  405: 	}
                    406: 	return '<blockquote><table border="1">'.
1.466     albertel  407: 	    '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
                    408: 	    '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
1.596.2.1  raeburn   409: 	    $bottomrow.'</tr></table></blockquote>';
1.148     albertel  410:     } elsif ($response eq 'match') {
                    411: 	my %answer=&Apache::lonnet::str2hash($answer);
1.596.2.12.2.  8(raebur  412:4):         my @answer = %answer;
                    413:4):         %answer = map {&HTML::Entities::encode($_, '"<>&')} @answer;
1.148     albertel  414: 	my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
                    415: 	my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"});
                    416: 	my ($toprow,$middlerow,$bottomrow);
                    417: 	foreach my $foil (@$order) {
                    418: 	    my $item=shift(@items);
                    419: 	    if ($grading{$foil} == 1) {
                    420: 		$toprow.='<td><b>'.$item.'&nbsp;</b></td>';
1.398     albertel  421: 		$middlerow.='<td><b>'.$grayFont.$answer{$foil}.'&nbsp;</span></b></td>';
1.148     albertel  422: 	    } else {
                    423: 		$toprow.='<td><i>'.$item.'&nbsp;</i></td>';
1.398     albertel  424: 		$middlerow.='<td><i>'.$grayFont.$answer{$foil}.'&nbsp;</span></i></td>';
1.148     albertel  425: 	    }
1.398     albertel  426: 	    $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
1.118     ng        427: 	}
1.126     ng        428: 	return '<blockquote><table border="1">'.
1.466     albertel  429: 	    '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
                    430: 	    '<tr valign="top"><td>'.$grayFont.&mt('Item ID').'</span></td>'.
1.148     albertel  431: 	    $middlerow.'</tr>'.
1.466     albertel  432: 	    '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
1.596.2.8  raeburn   433: 	    $bottomrow.'</tr></table></blockquote>';
1.148     albertel  434:     } elsif ($response eq 'radiobutton') {
                    435: 	my %answer=&Apache::lonnet::str2hash($answer);
                    436: 	my ($toprow,$bottomrow);
1.434     albertel  437: 	my $correct = 
1.596.2.2  raeburn   438: 	    &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom,$type,$trial,$rndseed);
1.434     albertel  439: 	foreach my $foil (@$order) {
1.148     albertel  440: 	    if (exists($answer{$foil})) {
1.434     albertel  441: 		if ($foil eq $correct) {
1.466     albertel  442: 		    $toprow.='<td><b>'.&mt('true').'</b></td>';
1.148     albertel  443: 		} else {
1.466     albertel  444: 		    $toprow.='<td><i>'.&mt('true').'</i></td>';
1.148     albertel  445: 		}
                    446: 	    } else {
1.466     albertel  447: 		$toprow.='<td>'.&mt('false').'</td>';
1.148     albertel  448: 	    }
1.398     albertel  449: 	    $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
1.148     albertel  450: 	}
                    451: 	return '<blockquote><table border="1">'.
1.466     albertel  452: 	    '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
                    453: 	    '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
1.596.2.4  raeburn   454: 	    $bottomrow.'</tr></table></blockquote>';
1.148     albertel  455:     } elsif ($response eq 'essay') {
1.257     albertel  456: 	if (! exists ($env{'form.'.$symb})) {
1.122     ng        457: 	    my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade',
1.257     albertel  458: 						  $env{'course.'.$env{'request.course.id'}.'.domain'},
                    459: 						  $env{'course.'.$env{'request.course.id'}.'.num'});
1.122     ng        460: 
1.257     albertel  461: 	    my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
                    462: 	    $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
                    463: 	    $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
                    464: 	    $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
                    465: 	    $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
                    466: 	    $env{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob.
1.122     ng        467: 	}
1.596.2.12.2.  2(raebur  468:5): 	return '<br /><br /><blockquote><tt>'.&keywords_highlight($answer).'</tt></blockquote>';
1.268     albertel  469:     } elsif ( $response eq 'organic') {
1.596.2.12.2.  8(raebur  470:4):         my $result=&mt('Smile representation: [_1]',
                    471:4):                            '"<tt>'.&HTML::Entities::encode($answer, '"<>&').'</tt>"');
1.268     albertel  472: 	my $jme=$record->{$version."resource.$partid.$respid.molecule"};
                    473: 	$result.=&Apache::chemresponse::jme_img($jme,$answer,400);
                    474: 	return $result;
1.335     albertel  475:     } elsif ( $response eq 'Task') {
                    476: 	if ( $answer eq 'SUBMITTED') {
                    477: 	    my $files = $record->{$version."resource.$respid.$partid.bridgetask.portfiles"};
1.336     albertel  478: 	    my $result = &Apache::bridgetask::file_list($files,$uname,$udom);
1.335     albertel  479: 	    return $result;
                    480: 	} elsif ( grep(/^\Q$version\E.*?\.instance$/, keys(%{$record})) ) {
                    481: 	    my @matches = grep(/^\Q$version\E.*?\.instance$/,
                    482: 			       keys(%{$record}));
                    483: 	    return join('<br />',($version,@matches));
                    484: 			       
                    485: 			       
                    486: 	} else {
                    487: 	    my $result =
                    488: 		'<p>'
                    489: 		.&mt('Overall result: [_1]',
                    490: 		     $record->{$version."resource.$respid.$partid.status"})
                    491: 		.'</p>';
                    492: 	    
                    493: 	    $result .= '<ul>';
                    494: 	    my @grade = grep(/^\Q${version}resource.$respid.$partid.\E[^.]*[.]status$/,
                    495: 			     keys(%{$record}));
                    496: 	    foreach my $grade (sort(@grade)) {
                    497: 		my ($dim) = ($grade =~/[.]([^.]+)[.]status$/);
                    498: 		$result.= '<li>'.&mt("Dimension: [_1], status [_2] ",
                    499: 				     $dim, $record->{$grade}).
                    500: 			  '</li>';
                    501: 	    }
                    502: 	    $result.='</ul>';
                    503: 	    return $result;
                    504: 	}
1.596.2.12.2.  8(raebur  505:4):     } elsif ( $response =~ m/(?:numerical|formula|custom)/) {
                    506:4):         # Respect multiple input fields, see Bug #5409 
1.440     albertel  507: 	$answer = 
                    508: 	    &Apache::loncommon::format_previous_attempt_value('submission',
                    509: 							      $answer);
1.596.2.12.2.  8(raebur  510:4):         return $answer;
1.122     ng        511:     }
1.596.2.12.2.  8(raebur  512:4):     return &HTML::Entities::encode($answer, '"<>&');
1.118     ng        513: }
                    514: 
                    515: #-- A couple of common js functions
                    516: sub commonJSfunctions {
                    517:     my $request = shift;
                    518:     $request->print(<<COMMONJSFUNCTIONS);
                    519: <script type="text/javascript" language="javascript">
                    520:     function radioSelection(radioButton) {
                    521: 	var selection=null;
                    522: 	if (radioButton.length > 1) {
                    523: 	    for (var i=0; i<radioButton.length; i++) {
                    524: 		if (radioButton[i].checked) {
                    525: 		    return radioButton[i].value;
                    526: 		}
                    527: 	    }
                    528: 	} else {
                    529: 	    if (radioButton.checked) return radioButton.value;
                    530: 	}
                    531: 	return selection;
                    532:     }
                    533: 
                    534:     function pullDownSelection(selectOne) {
                    535: 	var selection="";
                    536: 	if (selectOne.length > 1) {
                    537: 	    for (var i=0; i<selectOne.length; i++) {
                    538: 		if (selectOne[i].selected) {
                    539: 		    return selectOne[i].value;
                    540: 		}
                    541: 	    }
                    542: 	} else {
1.138     albertel  543:             // only one value it must be the selected one
                    544: 	    return selectOne.value;
1.118     ng        545: 	}
                    546:     }
                    547: </script>
                    548: COMMONJSFUNCTIONS
                    549: }
                    550: 
1.44      ng        551: #--- Dumps the class list with usernames,list of sections,
                    552: #--- section, ids and fullnames for each user.
                    553: sub getclasslist {
1.449     banghart  554:     my ($getsec,$filterlist,$getgroup) = @_;
1.291     albertel  555:     my @getsec;
1.450     banghart  556:     my @getgroup;
1.442     banghart  557:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
1.291     albertel  558:     if (!ref($getsec)) {
                    559: 	if ($getsec ne '' && $getsec ne 'all') {
                    560: 	    @getsec=($getsec);
                    561: 	}
                    562:     } else {
                    563: 	@getsec=@{$getsec};
                    564:     }
                    565:     if (grep(/^all$/,@getsec)) { undef(@getsec); }
1.450     banghart  566:     if (!ref($getgroup)) {
                    567: 	if ($getgroup ne '' && $getgroup ne 'all') {
                    568: 	    @getgroup=($getgroup);
                    569: 	}
                    570:     } else {
                    571: 	@getgroup=@{$getgroup};
                    572:     }
                    573:     if (grep(/^all$/,@getgroup)) { undef(@getgroup); }
1.291     albertel  574: 
1.449     banghart  575:     my ($classlist,$keylist)=&Apache::loncoursedata::get_classlist();
1.49      albertel  576:     # Bail out if we were unable to get the classlist
1.56      matthew   577:     return if (! defined($classlist));
1.449     banghart  578:     &Apache::loncoursedata::get_group_memberships($classlist,$keylist);
1.56      matthew   579:     #
                    580:     my %sections;
                    581:     my %fullnames;
1.205     matthew   582:     foreach my $student (keys(%$classlist)) {
                    583:         my $end      = 
                    584:             $classlist->{$student}->[&Apache::loncoursedata::CL_END()];
                    585:         my $start    = 
                    586:             $classlist->{$student}->[&Apache::loncoursedata::CL_START()];
                    587:         my $id       = 
                    588:             $classlist->{$student}->[&Apache::loncoursedata::CL_ID()];
                    589:         my $section  = 
                    590:             $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
                    591:         my $fullname = 
                    592:             $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()];
                    593:         my $status   = 
                    594:             $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];
1.449     banghart  595:         my $group   = 
                    596:             $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
1.76      ng        597: 	# filter students according to status selected
1.442     banghart  598: 	if ($filterlist && (!($stu_status =~ /Any/))) {
                    599: 	    if (!($stu_status =~ $status)) {
1.450     banghart  600: 		delete($classlist->{$student});
1.76      ng        601: 		next;
                    602: 	    }
                    603: 	}
1.450     banghart  604: 	# filter students according to groups selected
1.453     banghart  605: 	my @stu_groups = split(/,/,$group);
1.450     banghart  606: 	if (@getgroup) {
                    607: 	    my $exclude = 1;
1.454     banghart  608: 	    foreach my $grp (@getgroup) {
                    609: 	        foreach my $stu_group (@stu_groups) {
1.453     banghart  610: 	            if ($stu_group eq $grp) {
                    611: 	                $exclude = 0;
                    612:     	            } 
1.450     banghart  613: 	        }
1.453     banghart  614:     	        if (($grp eq 'none') && !$group) {
                    615:         	        $exclude = 0;
                    616:         	}
1.450     banghart  617: 	    }
                    618: 	    if ($exclude) {
                    619: 	        delete($classlist->{$student});
                    620: 	    }
                    621: 	}
1.205     matthew   622: 	$section = ($section ne '' ? $section : 'none');
1.106     albertel  623: 	if (&canview($section)) {
1.291     albertel  624: 	    if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
1.103     albertel  625: 		$sections{$section}++;
1.450     banghart  626: 		if ($classlist->{$student}) {
                    627: 		    $fullnames{$student}=$fullname;
                    628: 		}
1.103     albertel  629: 	    } else {
1.205     matthew   630: 		delete($classlist->{$student});
1.103     albertel  631: 	    }
                    632: 	} else {
1.205     matthew   633: 	    delete($classlist->{$student});
1.103     albertel  634: 	}
1.44      ng        635:     }
                    636:     my %seen = ();
1.56      matthew   637:     my @sections = sort(keys(%sections));
                    638:     return ($classlist,\@sections,\%fullnames);
1.44      ng        639: }
                    640: 
1.103     albertel  641: sub canmodify {
                    642:     my ($sec)=@_;
                    643:     if ($perm{'mgr'}) {
                    644: 	if (!defined($perm{'mgr_section'})) {
                    645: 	    # can modify whole class
                    646: 	    return 1;
                    647: 	} else {
                    648: 	    if ($sec eq $perm{'mgr_section'}) {
                    649: 		#can modify the requested section
                    650: 		return 1;
                    651: 	    } else {
                    652: 		# can't modify the request section
                    653: 		return 0;
                    654: 	    }
                    655: 	}
                    656:     }
                    657:     #can't modify
                    658:     return 0;
                    659: }
                    660: 
                    661: sub canview {
                    662:     my ($sec)=@_;
                    663:     if ($perm{'vgr'}) {
                    664: 	if (!defined($perm{'vgr_section'})) {
                    665: 	    # can modify whole class
                    666: 	    return 1;
                    667: 	} else {
                    668: 	    if ($sec eq $perm{'vgr_section'}) {
                    669: 		#can modify the requested section
                    670: 		return 1;
                    671: 	    } else {
                    672: 		# can't modify the request section
                    673: 		return 0;
                    674: 	    }
                    675: 	}
                    676:     }
                    677:     #can't modify
                    678:     return 0;
                    679: }
                    680: 
1.44      ng        681: #--- Retrieve the grade status of a student for all the parts
                    682: sub student_gradeStatus {
1.324     albertel  683:     my ($symb,$udom,$uname,$partlist) = @_;
1.257     albertel  684:     my %record     = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
1.44      ng        685:     my %partstatus = ();
                    686:     foreach (@$partlist) {
1.128     ng        687: 	my ($status,undef)   = split(/_/,$record{"resource.$_.solved"},2);
1.44      ng        688: 	$status              = 'nothing' if ($status eq '');
                    689: 	$partstatus{$_}      = $status;
                    690: 	my $subkey           = "resource.$_.submitted_by";
                    691: 	$partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
                    692:     }
                    693:     return %partstatus;
                    694: }
                    695: 
1.45      ng        696: # hidden form and javascript that calls the form
                    697: # Use by verifyscript and viewgrades
                    698: # Shows a student's view of problem and submission
                    699: sub jscriptNform {
1.324     albertel  700:     my ($symb) = @_;
1.442     banghart  701:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
1.45      ng        702:     my $jscript='<script type="text/javascript" language="javascript">'."\n".
                    703: 	'    function viewOneStudent(user,domain) {'."\n".
                    704: 	'	document.onestudent.student.value = user;'."\n".
                    705: 	'	document.onestudent.userdom.value = domain;'."\n".
                    706: 	'	document.onestudent.submit();'."\n".
                    707: 	'    }'."\n".
                    708: 	'</script>'."\n";
                    709:     $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n".
1.418     albertel  710: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1.257     albertel  711: 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
                    712: 	'<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".
1.442     banghart  713: 	'<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
1.45      ng        714: 	'<input type="hidden" name="command" value="submission" />'."\n".
                    715: 	'<input type="hidden" name="student" value="" />'."\n".
                    716: 	'<input type="hidden" name="userdom" value="" />'."\n".
                    717: 	'</form>'."\n";
                    718:     return $jscript;
                    719: }
1.39      ng        720: 
1.447     foxr      721: 
                    722: 
1.315     bowersj2  723: # Given the score (as a number [0-1] and the weight) what is the final
                    724: # point value? This function will round to the nearest tenth, third,
                    725: # or quarter if one of those is within the tolerance of .00001.
1.316     albertel  726: sub compute_points {
1.315     bowersj2  727:     my ($score, $weight) = @_;
                    728:     
                    729:     my $tolerance = .00001;
                    730:     my $points = $score * $weight;
                    731: 
                    732:     # Check for nearness to 1/x.
                    733:     my $check_for_nearness = sub {
                    734:         my ($factor) = @_;
                    735:         my $num = ($points * $factor) + $tolerance;
                    736:         my $floored_num = floor($num);
1.316     albertel  737:         if ($num - $floored_num < 2 * $tolerance * $factor) {
1.315     bowersj2  738:             return $floored_num / $factor;
                    739:         }
                    740:         return $points;
                    741:     };
                    742: 
                    743:     $points = $check_for_nearness->(10);
                    744:     $points = $check_for_nearness->(3);
                    745:     $points = $check_for_nearness->(4);
                    746:     
                    747:     return $points;
                    748: }
                    749: 
1.44      ng        750: #------------------ End of general use routines --------------------
1.87      www       751: 
                    752: #
                    753: # Find most similar essay
                    754: #
                    755: 
                    756: sub most_similar {
1.596.2.12.2.  (raeburn  757:):     my ($uname,$udom,$symb,$uessay)=@_;
                    758:): 
                    759:):     unless ($symb) { return ''; }
                    760:): 
                    761:):     unless (ref($old_essays{$symb}) eq 'HASH') { return ''; }
1.87      www       762: 
                    763: # ignore spaces and punctuation
                    764: 
                    765:     $uessay=~s/\W+/ /gs;
                    766: 
1.282     www       767: # ignore empty submissions (occuring when only files are sent)
                    768: 
1.596.2.4  raeburn   769:     unless ($uessay=~/\w+/s) { return ''; }
1.282     www       770: 
1.87      www       771: # these will be returned. Do not care if not at least 50 percent similar
1.88      www       772:     my $limit=0.6;
1.87      www       773:     my $sname='';
                    774:     my $sdom='';
                    775:     my $scrsid='';
                    776:     my $sessay='';
                    777: # go through all essays ...
1.596.2.12.2.  (raeburn  778:):     foreach my $tkey (keys(%{$old_essays{$symb}})) {
1.426     albertel  779: 	my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey));
1.87      www       780: # ... except the same student
1.426     albertel  781:         next if (($tname eq $uname) && ($tdom eq $udom));
1.596.2.12.2.  (raeburn  782:): 	my $tessay=$old_essays{$symb}{$tkey};
1.426     albertel  783: 	$tessay=~s/\W+/ /gs;
1.87      www       784: # String similarity gives up if not even limit
1.426     albertel  785: 	my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit);
1.87      www       786: # Found one
1.426     albertel  787: 	if ($tsimilar>$limit) {
                    788: 	    $limit=$tsimilar;
                    789: 	    $sname=$tname;
                    790: 	    $sdom=$tdom;
                    791: 	    $scrsid=$tcrsid;
1.596.2.12.2.  (raeburn  792:): 	    $sessay=$old_essays{$symb}{$tkey};
1.426     albertel  793: 	}
1.87      www       794:     }
1.88      www       795:     if ($limit>0.6) {
1.87      www       796:        return ($sname,$sdom,$scrsid,$sessay,$limit);
                    797:     } else {
                    798:        return ('','','','',0);
                    799:     }
                    800: }
                    801: 
1.44      ng        802: #-------------------------------------------------------------------
                    803: 
                    804: #------------------------------------ Receipt Verification Routines
1.45      ng        805: #
1.44      ng        806: #--- Check whether a receipt number is valid.---
                    807: sub verifyreceipt {
                    808:     my $request  = shift;
                    809: 
1.257     albertel  810:     my $courseid = $env{'request.course.id'};
1.184     www       811:     my $receipt  = &Apache::lonnet::recprefix($courseid).'-'.
1.257     albertel  812: 	$env{'form.receipt'};
1.44      ng        813:     $receipt     =~ s/[^\-\d]//g;
1.378     albertel  814:     my ($symb)   = &get_symb($request);
1.44      ng        815: 
1.487     albertel  816:     my $title.=
                    817: 	'<h3><span class="LC_info">'.
1.584     bisitz    818: 	&mt('Verifying Receipt No. [_1]',$receipt).
1.487     albertel  819: 	'</span></h3>'."\n".
1.596.2.12.2.  2(raebur  820:3): 	'<h4>'.&mt('[_1]Resource: [_2]','<b>','</b>'.$env{'form.probTitle'}).
1.487     albertel  821: 	'</h4>'."\n";
1.44      ng        822: 
                    823:     my ($string,$contents,$matches) = ('','',0);
1.56      matthew   824:     my (undef,undef,$fullname) = &getclasslist('all','0');
1.177     albertel  825:     
                    826:     my $receiptparts=0;
1.390     albertel  827:     if ($env{"course.$courseid.receiptalg"} eq 'receipt2' ||
                    828: 	$env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; }
1.177     albertel  829:     my $parts=['0'];
1.582     raeburn   830:     if ($receiptparts) {
                    831:         my $res_error; 
                    832:         ($parts)=&response_type($symb,\$res_error);
                    833:         if ($res_error) {
                    834:             return &navmap_errormsg();
                    835:         } 
                    836:     }
1.486     albertel  837:     
                    838:     my $header = 
                    839: 	&Apache::loncommon::start_data_table().
                    840: 	&Apache::loncommon::start_data_table_header_row().
1.487     albertel  841: 	'<th>&nbsp;'.&mt('Fullname').'&nbsp;</th>'."\n".
                    842: 	'<th>&nbsp;'.&mt('Username').'&nbsp;</th>'."\n".
                    843: 	'<th>&nbsp;'.&mt('Domain').'&nbsp;</th>';
1.486     albertel  844:     if ($receiptparts) {
1.487     albertel  845: 	$header.='<th>&nbsp;'.&mt('Problem Part').'&nbsp;</th>';
1.486     albertel  846:     }
                    847:     $header.=
                    848: 	&Apache::loncommon::end_data_table_header_row();
                    849: 
1.294     albertel  850:     foreach (sort 
                    851: 	     {
                    852: 		 if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
                    853: 		     return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
                    854: 		 }
                    855: 		 return $a cmp $b;
                    856: 	     } (keys(%$fullname))) {
1.44      ng        857: 	my ($uname,$udom)=split(/\:/);
1.177     albertel  858: 	foreach my $part (@$parts) {
                    859: 	    if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {
1.486     albertel  860: 		$contents.=
                    861: 		    &Apache::loncommon::start_data_table_row().
                    862: 		    '<td>&nbsp;'."\n".
1.177     albertel  863: 		    '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
1.417     albertel  864: 		    '\');" target="_self">'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".
1.177     albertel  865: 		    '<td>&nbsp;'.$uname.'&nbsp;</td>'.
                    866: 		    '<td>&nbsp;'.$udom.'&nbsp;</td>';
                    867: 		if ($receiptparts) {
                    868: 		    $contents.='<td>&nbsp;'.$part.'&nbsp;</td>';
                    869: 		}
1.486     albertel  870: 		$contents.= 
                    871: 		    &Apache::loncommon::end_data_table_row()."\n";
1.177     albertel  872: 		
                    873: 		$matches++;
                    874: 	    }
1.44      ng        875: 	}
                    876:     }
                    877:     if ($matches == 0) {
1.584     bisitz    878:         $string = $title
                    879:                  .'<p class="LC_warning">'
                    880:                  .&mt('No match found for the above receipt number.')
                    881:                  .'</p>';
1.44      ng        882:     } else {
1.324     albertel  883: 	$string = &jscriptNform($symb).$title.
1.487     albertel  884: 	    '<p>'.
1.584     bisitz    885: 	    &mt('The above receipt number matches the following [quant,_1,student].',$matches).
1.487     albertel  886: 	    '</p>'.
1.486     albertel  887: 	    $header.
                    888: 	    $contents.
                    889: 	    &Apache::loncommon::end_data_table()."\n";
1.44      ng        890:     }
1.324     albertel  891:     return $string.&show_grading_menu_form($symb);
1.44      ng        892: }
                    893: 
                    894: #--- This is called by a number of programs.
                    895: #--- Called from the Grading Menu - View/Grade an individual student
                    896: #--- Also called directly when one clicks on the subm button 
                    897: #    on the problem page.
1.30      ng        898: sub listStudents {
1.41      ng        899:     my ($request) = shift;
1.49      albertel  900: 
1.324     albertel  901:     my ($symb) = &get_symb($request);
1.257     albertel  902:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
                    903:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
                    904:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
1.449     banghart  905:     my $getgroup  = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
1.257     albertel  906:     my $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
1.548     bisitz    907:     my $viewgrade = $env{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View';
1.257     albertel  908:     $env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
                    909: 	&Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
1.49      albertel  910: 
1.548     bisitz    911:     my $result='<h3><span class="LC_info">&nbsp;'
                    912: 	.&mt("$viewgrade Submissions for a Student or a Group of Students")
1.485     albertel  913: 	.'</span></h3>';
1.118     ng        914: 
1.324     albertel  915:     my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($symb,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes'));
1.49      albertel  916: 
1.596.2.12.2.  6(raebur  917:6):     my %js_lt = &Apache::lonlocal::texthash (
1.559     raeburn   918: 		'multiple' => 'Please select a student or group of students before clicking on the Next button.',
                    919: 		'single'   => 'Please select the student before clicking on the Next button.',
                    920: 	     );
1.596.2.12.2.  6(raebur  921:6):     &js_escape(\%js_lt);
1.45      ng        922:     $request->print(<<LISTJAVASCRIPT);
                    923: <script type="text/javascript" language="javascript">
1.110     ng        924:     function checkSelect(checkBox) {
                    925: 	var ctr=0;
                    926: 	var sense="";
                    927: 	if (checkBox.length > 1) {
                    928: 	    for (var i=0; i<checkBox.length; i++) {
                    929: 		if (checkBox[i].checked) {
                    930: 		    ctr++;
                    931: 		}
                    932: 	    }
1.596.2.12.2.  6(raebur  933:6): 	    sense = '$js_lt{'multiple'}';
1.110     ng        934: 	} else {
                    935: 	    if (checkBox.checked) {
                    936: 		ctr = 1;
                    937: 	    }
1.596.2.12.2.  6(raebur  938:6): 	    sense = '$js_lt{'single'}';
1.110     ng        939: 	}
                    940: 	if (ctr == 0) {
1.485     albertel  941: 	    alert(sense);
1.110     ng        942: 	    return false;
                    943: 	}
                    944: 	document.gradesub.submit();
                    945:     }
                    946: 
                    947:     function reLoadList(formname) {
1.112     ng        948: 	if (formname.saveStatusOld.value == pullDownSelection(formname.Status)) {return;}
1.110     ng        949: 	formname.command.value = 'submission';
                    950: 	formname.submit();
                    951:     }
1.45      ng        952: </script>
                    953: LISTJAVASCRIPT
                    954: 
1.118     ng        955:     &commonJSfunctions($request);
1.41      ng        956:     $request->print($result);
1.39      ng        957: 
1.401     albertel  958:     my $checkhdgrade = ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked="checked"' : '';
                    959:     my $checklastsub = $checkhdgrade eq '' ? 'checked="checked"' : '';
1.154     albertel  960:     my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.
1.485     albertel  961: 	"\n".$table;
                    962: 	
1.561     bisitz    963:     $gradeTable .= &Apache::lonhtmlcommon::start_pick_box();
                    964:     $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Problem Text'))
                    965:                   .'<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n"
                    966:                   .'<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n"
                    967:                   .'<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label><br />'."\n"
                    968:                   .&Apache::lonhtmlcommon::row_closure();
                    969:     $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Answer'))
                    970:                   .'<label><input type="radio" name="vAns" value="no"  /> '.&mt('no').' </label>'."\n"
                    971:                   .'<label><input type="radio" name="vAns" value="yes" /> '.&mt('one student').' </label>'."\n"
                    972:                   .'<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label><br />'."\n"
                    973:                   .&Apache::lonhtmlcommon::row_closure();
1.485     albertel  974: 
                    975:     my $submission_options;
1.257     albertel  976:     if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) {
1.485     albertel  977: 	$submission_options.=
                    978: 	    '<label><input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> '.&mt('essay part only').' </label>'."\n";
1.49      albertel  979:     }
1.442     banghart  980:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
                    981:     my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status;
1.257     albertel  982:     $env{'form.Status'} = $saveStatus;
1.485     albertel  983:     $submission_options.=
1.592     bisitz    984:         '<span class="LC_nobreak">'.
                    985:         '<label><input type="radio" name="lastSub" value="lastonly" '.$checklastsub.' /> '.
                    986:         &mt('last submission only').' </label></span>'."\n".
                    987:         '<span class="LC_nobreak">'.
                    988:         '<label><input type="radio" name="lastSub" value="last" /> '.
                    989:         &mt('last submission &amp; parts info').' </label></span>'."\n".
                    990:         '<span class="LC_nobreak">'.
                    991:         '<label><input type="radio" name="lastSub" value="datesub" /> '.
                    992:         &mt('by dates and submissions').'</label></span>'."\n".
                    993:         '<span class="LC_nobreak">'.
                    994:         '<label><input type="radio" name="lastSub" value="all" /> '.
                    995:         &mt('all details').'</label></span>';
1.561     bisitz    996:     $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Submissions'))
                    997:                   .$submission_options
                    998:                   .&Apache::lonhtmlcommon::row_closure();
                    999: 
                   1000:     $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Grading Increments'))
                   1001:                   .'<select name="increment">'
                   1002:                   .'<option value="1">'.&mt('Whole Points').'</option>'
                   1003:                   .'<option value=".5">'.&mt('Half Points').'</option>'
                   1004:                   .'<option value=".25">'.&mt('Quarter Points').'</option>'
                   1005:                   .'<option value=".1">'.&mt('Tenths of a Point').'</option>'
                   1006:                   .'</select>'
                   1007:                   .&Apache::lonhtmlcommon::row_closure();
1.485     albertel 1008: 
                   1009:     $gradeTable .= 
1.432     banghart 1010:         &build_section_inputs().
1.45      ng       1011: 	'<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".
1.257     albertel 1012: 	'<input type="hidden" name="handgrade"   value="'.$env{'form.handgrade'}.'" /><br />'."\n".
                   1013: 	'<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\n".
                   1014: 	'<input type="hidden" name="saveState"   value="'.$env{'form.saveState'}.'" />'."\n".
                   1015: 	'<input type="hidden" name="probTitle"   value="'.$env{'form.probTitle'}.'" />'."\n".
1.418     albertel 1016: 	'<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1.110     ng       1017: 	'<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";
                   1018: 
1.257     albertel 1019:     if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) {
1.561     bisitz   1020: 	$gradeTable .= '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n";
1.124     ng       1021:     } else {
1.561     bisitz   1022:         $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Student Status'))
                   1023:                       .&Apache::lonhtmlcommon::StatusOptions(
                   1024:                            $saveStatus,undef,1,'javascript:reLoadList(this.form);')
                   1025:                       .&Apache::lonhtmlcommon::row_closure();
1.124     ng       1026:     }
1.112     ng       1027: 
1.561     bisitz   1028:     $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Check For Plagiarism'))
                   1029:                   .'<input type="checkbox" name="checkPlag" checked="checked" />'
                   1030:                   .&Apache::lonhtmlcommon::row_closure(1)
                   1031:                   .&Apache::lonhtmlcommon::end_pick_box();
                   1032: 
                   1033:     $gradeTable .= '<p>'
                   1034:                   .&mt('To '.lc($viewgrade)." 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"
                   1035:                   .'<input type="hidden" name="command" value="processGroup" />'
                   1036:                   .'</p>';
1.249     albertel 1037: 
                   1038: # checkall buttons
                   1039:     $gradeTable.=&check_script('gradesub', 'stuinfo');
1.110     ng       1040:     $gradeTable.='<input type="button" '."\n".
1.589     bisitz   1041:         'onclick="javascript:checkSelect(this.form.stuinfo);" '."\n".
                   1042:         'value="'.&mt('Next').' &rarr;" /> <br />'."\n";
1.249     albertel 1043:     $gradeTable.=&check_buttons();
1.450     banghart 1044:     my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup);
1.474     albertel 1045:     $gradeTable.= &Apache::loncommon::start_data_table().
                   1046: 	&Apache::loncommon::start_data_table_header_row();
1.110     ng       1047:     my $loop = 0;
                   1048:     while ($loop < 2) {
1.485     albertel 1049: 	$gradeTable.='<th>'.&mt('No.').'</th><th>'.&mt('Select').'</th>'.
                   1050: 	    '<th>'.&nameUserString('header').'&nbsp;'.&mt('Section/Group').'</th>';
1.301     albertel 1051: 	if ($env{'form.showgrading'} eq 'yes' 
                   1052: 	    && $submitonly ne 'queued'
                   1053: 	    && $submitonly ne 'all') {
1.485     albertel 1054: 	    foreach my $part (sort(@$partlist)) {
                   1055: 		my $display_part=
                   1056: 		    &get_display_part((split(/_/,$part))[0],$symb);
                   1057: 		$gradeTable.=
                   1058: 		    '<th>'.&mt('Part: [_1] Status',$display_part).'</th>';
1.110     ng       1059: 	    }
1.301     albertel 1060: 	} elsif ($submitonly eq 'queued') {
1.474     albertel 1061: 	    $gradeTable.='<th>'.&mt('Queue Status').'&nbsp;</th>';
1.110     ng       1062: 	}
                   1063: 	$loop++;
1.126     ng       1064: #	$gradeTable.='<td></td>' if ($loop%2 ==1);
1.41      ng       1065:     }
1.474     albertel 1066:     $gradeTable.=&Apache::loncommon::end_data_table_header_row()."\n";
1.41      ng       1067: 
1.45      ng       1068:     my $ctr = 0;
1.294     albertel 1069:     foreach my $student (sort 
                   1070: 			 {
                   1071: 			     if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
                   1072: 				 return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
                   1073: 			     }
                   1074: 			     return $a cmp $b;
                   1075: 			 }
                   1076: 			 (keys(%$fullname))) {
1.41      ng       1077: 	my ($uname,$udom) = split(/:/,$student);
1.301     albertel 1078: 
1.110     ng       1079: 	my %status = ();
1.301     albertel 1080: 
                   1081: 	if ($submitonly eq 'queued') {
                   1082: 	    my %queue_status = 
                   1083: 		&Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
                   1084: 							$udom,$uname);
                   1085: 	    next if (!defined($queue_status{'gradingqueue'}));
                   1086: 	    $status{'gradingqueue'} = $queue_status{'gradingqueue'};
                   1087: 	}
                   1088: 
                   1089: 	if ($env{'form.showgrading'} eq 'yes' 
                   1090: 	    && $submitonly ne 'queued'
                   1091: 	    && $submitonly ne 'all') {
1.324     albertel 1092: 	    (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);
1.145     albertel 1093: 	    my $submitted = 0;
1.164     albertel 1094: 	    my $graded = 0;
1.248     albertel 1095: 	    my $incorrect = 0;
1.110     ng       1096: 	    foreach (keys(%status)) {
1.145     albertel 1097: 		$submitted = 1 if ($status{$_} ne 'nothing');
1.248     albertel 1098: 		$graded = 1 if ($status{$_} =~ /^ungraded/);
                   1099: 		$incorrect = 1 if ($status{$_} =~ /^incorrect/);
                   1100: 		
1.110     ng       1101: 		my ($foo,$partid,$foo1) = split(/\./,$_);
                   1102: 		if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
1.145     albertel 1103: 		    $submitted = 0;
1.150     albertel 1104: 		    my ($part)=split(/\./,$partid);
1.110     ng       1105: 		    $gradeTable.='<input type="hidden" name="'.
1.150     albertel 1106: 			$student.':'.$part.':submitted_by" value="'.
1.110     ng       1107: 			$status{'resource.'.$partid.'.submitted_by'}.'" />';
                   1108: 		}
1.41      ng       1109: 	    }
1.248     albertel 1110: 	    
1.156     albertel 1111: 	    next if (!$submitted && ($submitonly eq 'yes' ||
                   1112: 				     $submitonly eq 'incorrect' ||
                   1113: 				     $submitonly eq 'graded'));
1.248     albertel 1114: 	    next if (!$graded && ($submitonly eq 'graded'));
                   1115: 	    next if (!$incorrect && $submitonly eq 'incorrect');
1.41      ng       1116: 	}
1.34      ng       1117: 
1.45      ng       1118: 	$ctr++;
1.249     albertel 1119: 	my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
1.452     banghart 1120:         my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
1.104     albertel 1121: 	if ( $perm{'vgr'} eq 'F' ) {
1.474     albertel 1122: 	    if ($ctr%2 ==1) {
                   1123: 		$gradeTable.= &Apache::loncommon::start_data_table_row();
                   1124: 	    }
1.126     ng       1125: 	    $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.
1.563     bisitz   1126:                '<td align="center"><label><input type="checkbox" name="stuinfo" value="'.
1.249     albertel 1127:                $student.':'.$$fullname{$student}.':::SECTION'.$section.
                   1128: 	       ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.
                   1129: 	       &nameUserString(undef,$$fullname{$student},$uname,$udom).
1.474     albertel 1130: 	       '&nbsp;'.$section.($group ne '' ?'/'.$group:'').'</td>'."\n";
1.110     ng       1131: 
1.257     albertel 1132: 	    if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') {
1.524     raeburn  1133: 		foreach (sort(keys(%status))) {
1.485     albertel 1134: 		    next if ($_ =~ /^resource.*?submitted_by$/);
                   1135: 		    $gradeTable.='<td align="center">&nbsp;'.&mt($status{$_}).'&nbsp;</td>'."\n";
1.110     ng       1136: 		}
1.41      ng       1137: 	    }
1.126     ng       1138: #	    $gradeTable.='<td></td>' if ($ctr%2 ==1);
1.474     albertel 1139: 	    if ($ctr%2 ==0) {
                   1140: 		$gradeTable.=&Apache::loncommon::end_data_table_row()."\n";
                   1141: 	    }
1.41      ng       1142: 	}
                   1143:     }
1.110     ng       1144:     if ($ctr%2 ==1) {
1.126     ng       1145: 	$gradeTable.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>';
1.301     albertel 1146: 	    if ($env{'form.showgrading'} eq 'yes' 
                   1147: 		&& $submitonly ne 'queued'
                   1148: 		&& $submitonly ne 'all') {
1.110     ng       1149: 		foreach (@$partlist) {
                   1150: 		    $gradeTable.='<td>&nbsp;</td>';
                   1151: 		}
1.301     albertel 1152: 	    } elsif ($submitonly eq 'queued') {
                   1153: 		$gradeTable.='<td>&nbsp;</td>';
1.110     ng       1154: 	    }
1.474     albertel 1155: 	$gradeTable.=&Apache::loncommon::end_data_table_row();
1.110     ng       1156:     }
                   1157: 
1.474     albertel 1158:     $gradeTable.=&Apache::loncommon::end_data_table()."\n".
1.589     bisitz   1159:         '<input type="button" '.
                   1160:         'onclick="javascript:checkSelect(this.form.stuinfo);" '.
                   1161:         'value="'.&mt('Next').' &rarr;" /></form>'."\n";
1.45      ng       1162:     if ($ctr == 0) {
1.96      albertel 1163: 	my $num_students=(scalar(keys(%$fullname)));
                   1164: 	if ($num_students eq 0) {
1.485     albertel 1165: 	    $gradeTable='<br />&nbsp;<span class="LC_warning">'.&mt('There are no students currently enrolled.').'</span>';
1.96      albertel 1166: 	} else {
1.171     albertel 1167: 	    my $submissions='submissions';
                   1168: 	    if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; }
                   1169: 	    if ($submitonly eq 'graded'   ) { $submissions = 'ungraded submissions'; }
1.301     albertel 1170: 	    if ($submitonly eq 'queued'   ) { $submissions = 'queued submissions'; }
1.398     albertel 1171: 	    $gradeTable='<br />&nbsp;<span class="LC_warning">'.
1.596.2.12.2.  4(raebur 1172:3): 		&mt('No '.$submissions.' found for this resource for any students. ([quant,_1,student] checked for '.$submissions.')',
1.485     albertel 1173: 		    $num_students).
                   1174: 		'</span><br />';
1.96      albertel 1175: 	}
1.46      ng       1176:     } elsif ($ctr == 1) {
1.474     albertel 1177: 	$gradeTable =~ s/type="checkbox"/type="checkbox" checked="checked"/;
1.45      ng       1178:     }
1.324     albertel 1179:     $gradeTable.=&show_grading_menu_form($symb);
1.45      ng       1180:     $request->print($gradeTable);
1.44      ng       1181:     return '';
1.10      ng       1182: }
                   1183: 
1.44      ng       1184: #---- Called from the listStudents routine
1.249     albertel 1185: 
                   1186: sub check_script {
                   1187:     my ($form, $type)=@_;
                   1188:     my $chkallscript='<script type="text/javascript">
                   1189:     function checkall() {
                   1190:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
                   1191:             ele = document.forms.'.$form.'.elements[i];
                   1192:             if (ele.name == "'.$type.'") {
                   1193:             document.forms.'.$form.'.elements[i].checked=true;
                   1194:                                        }
                   1195:         }
                   1196:     }
                   1197: 
                   1198:     function checksec() {
                   1199:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
                   1200:             ele = document.forms.'.$form.'.elements[i];
                   1201:            string = document.forms.'.$form.'.chksec.value;
                   1202:            if
                   1203:           (ele.value.indexOf(":::SECTION"+string)>0) {
                   1204:               document.forms.'.$form.'.elements[i].checked=true;
                   1205:             }
                   1206:         }
                   1207:     }
                   1208: 
                   1209: 
                   1210:     function uncheckall() {
                   1211:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
                   1212:             ele = document.forms.'.$form.'.elements[i];
                   1213:             if (ele.name == "'.$type.'") {
                   1214:             document.forms.'.$form.'.elements[i].checked=false;
                   1215:                                        }
                   1216:         }
                   1217:     }
                   1218: 
                   1219: </script>'."\n";
                   1220:     return $chkallscript;
                   1221: }
                   1222: 
                   1223: sub check_buttons {
1.485     albertel 1224:     my $buttons.='<input type="button" onclick="checkall()" value="'.&mt('Check All').'" />';
                   1225:     $buttons.='<input type="button" onclick="uncheckall()" value="'.&mt('Uncheck All').'" />&nbsp;';
                   1226:     $buttons.='<input type="button" onclick="checksec()" value="'.&mt('Check Section/Group').'" />';
1.249     albertel 1227:     $buttons.='<input type="text" size="5" name="chksec" />&nbsp;';
                   1228:     return $buttons;
                   1229: }
                   1230: 
1.44      ng       1231: #     Displays the submissions for one student or a group of students
1.34      ng       1232: sub processGroup {
1.41      ng       1233:     my ($request)  = shift;
                   1234:     my $ctr        = 0;
1.155     albertel 1235:     my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo');
1.41      ng       1236:     my $total      = scalar(@stuchecked)-1;
1.45      ng       1237: 
1.396     banghart 1238:     foreach my $student (@stuchecked) {
                   1239: 	my ($uname,$udom,$fullname) = split(/:/,$student);
1.257     albertel 1240: 	$env{'form.student'}        = $uname;
                   1241: 	$env{'form.userdom'}        = $udom;
                   1242: 	$env{'form.fullname'}       = $fullname;
1.41      ng       1243: 	&submission($request,$ctr,$total);
                   1244: 	$ctr++;
                   1245:     }
                   1246:     return '';
1.35      ng       1247: }
1.34      ng       1248: 
1.44      ng       1249: #------------------------------------------------------------------------------------
                   1250: #
                   1251: #-------------------------- Next few routines handles grading by student, essentially
                   1252: #                           handles essay response type problem/part
                   1253: #
                   1254: #--- Javascript to handle the submission page functionality ---
                   1255: sub sub_page_js {
                   1256:     my $request = shift;
1.596.2.12.2.  6(raebur 1257:6):     my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = ');
          7(raebur 1258:6):     &js_escape(\$alertmsg);
1.44      ng       1259:     $request->print(<<SUBJAVASCRIPT);
                   1260: <script type="text/javascript" language="javascript">
1.71      ng       1261:     function updateRadio(formname,id,weight) {
1.125     ng       1262: 	var gradeBox = formname["GD_BOX"+id];
                   1263: 	var radioButton = formname["RADVAL"+id];
                   1264: 	var oldpts = formname["oldpts"+id].value;
1.72      ng       1265: 	var pts = checkSolved(formname,id) == 'update' ? gradeBox.value : oldpts;
1.71      ng       1266: 	gradeBox.value = pts;
                   1267: 	var resetbox = false;
                   1268: 	if (isNaN(pts) || pts < 0) {
1.539     riegler  1269: 	    alert("$alertmsg"+pts);
1.71      ng       1270: 	    for (var i=0; i<radioButton.length; i++) {
                   1271: 		if (radioButton[i].checked) {
                   1272: 		    gradeBox.value = i;
                   1273: 		    resetbox = true;
                   1274: 		}
                   1275: 	    }
                   1276: 	    if (!resetbox) {
                   1277: 		formtextbox.value = "";
                   1278: 	    }
                   1279: 	    return;
1.44      ng       1280: 	}
1.71      ng       1281: 
                   1282: 	if (pts > weight) {
                   1283: 	    var resp = confirm("You entered a value ("+pts+
                   1284: 			       ") greater than the weight for the part. Accept?");
                   1285: 	    if (resp == false) {
1.125     ng       1286: 		gradeBox.value = oldpts;
1.71      ng       1287: 		return;
                   1288: 	    }
1.44      ng       1289: 	}
1.13      albertel 1290: 
1.71      ng       1291: 	for (var i=0; i<radioButton.length; i++) {
                   1292: 	    radioButton[i].checked=false;
                   1293: 	    if (pts == i && pts != "") {
                   1294: 		radioButton[i].checked=true;
                   1295: 	    }
                   1296: 	}
                   1297: 	updateSelect(formname,id);
1.125     ng       1298: 	formname["stores"+id].value = "0";
1.41      ng       1299:     }
1.5       albertel 1300: 
1.72      ng       1301:     function writeBox(formname,id,pts) {
1.125     ng       1302: 	var gradeBox = formname["GD_BOX"+id];
1.71      ng       1303: 	if (checkSolved(formname,id) == 'update') {
                   1304: 	    gradeBox.value = pts;
                   1305: 	} else {
1.125     ng       1306: 	    var oldpts = formname["oldpts"+id].value;
1.72      ng       1307: 	    gradeBox.value = oldpts;
1.125     ng       1308: 	    var radioButton = formname["RADVAL"+id];
1.71      ng       1309: 	    for (var i=0; i<radioButton.length; i++) {
                   1310: 		radioButton[i].checked=false;
1.72      ng       1311: 		if (i == oldpts) {
1.71      ng       1312: 		    radioButton[i].checked=true;
                   1313: 		}
                   1314: 	    }
1.41      ng       1315: 	}
1.125     ng       1316: 	formname["stores"+id].value = "0";
1.71      ng       1317: 	updateSelect(formname,id);
                   1318: 	return;
1.41      ng       1319:     }
1.44      ng       1320: 
1.71      ng       1321:     function clearRadBox(formname,id) {
                   1322: 	if (checkSolved(formname,id) == 'noupdate') {
                   1323: 	    updateSelect(formname,id);
                   1324: 	    return;
                   1325: 	}
1.125     ng       1326: 	gradeSelect = formname["GD_SEL"+id];
1.71      ng       1327: 	for (var i=0; i<gradeSelect.length; i++) {
                   1328: 	    if (gradeSelect[i].selected) {
                   1329: 		var selectx=i;
                   1330: 	    }
                   1331: 	}
1.125     ng       1332: 	var stores = formname["stores"+id];
1.71      ng       1333: 	if (selectx == stores.value) { return };
1.125     ng       1334: 	var gradeBox = formname["GD_BOX"+id];
1.71      ng       1335: 	gradeBox.value = "";
1.125     ng       1336: 	var radioButton = formname["RADVAL"+id];
1.71      ng       1337: 	for (var i=0; i<radioButton.length; i++) {
                   1338: 	    radioButton[i].checked=false;
                   1339: 	}
                   1340: 	stores.value = selectx;
                   1341:     }
1.5       albertel 1342: 
1.71      ng       1343:     function checkSolved(formname,id) {
1.125     ng       1344: 	if (formname["solved"+id].value == "correct_by_student" && formname.overRideScore.value == 'no') {
1.118     ng       1345: 	    var reply = confirm("This problem has been graded correct by the computer. Do you want to change the score?");
                   1346: 	    if (!reply) {return "noupdate";}
1.120     ng       1347: 	    formname.overRideScore.value = 'yes';
1.41      ng       1348: 	}
1.71      ng       1349: 	return "update";
1.13      albertel 1350:     }
1.71      ng       1351: 
                   1352:     function updateSelect(formname,id) {
1.125     ng       1353: 	formname["GD_SEL"+id][0].selected = true;
1.71      ng       1354: 	return;
1.41      ng       1355:     }
1.33      ng       1356: 
1.121     ng       1357: //=========== Check that a point is assigned for all the parts  ============
1.71      ng       1358:     function checksubmit(formname,val,total,parttot) {
1.121     ng       1359: 	formname.gradeOpt.value = val;
1.71      ng       1360: 	if (val == "Save & Next") {
                   1361: 	    for (i=0;i<=total;i++) {
                   1362: 		for (j=0;j<parttot;j++) {
1.125     ng       1363: 		    var partid = formname["partid"+i+"_"+j].value;
1.127     ng       1364: 		    if (formname["GD_SEL"+i+"_"+partid][0].selected) {
1.125     ng       1365: 			var points = formname["GD_BOX"+i+"_"+partid].value;
1.71      ng       1366: 			if (points == "") {
1.125     ng       1367: 			    var name = formname["name"+i].value;
1.129     ng       1368: 			    var studentID = (name != '' ? name : formname["unamedom"+i].value);
                   1369: 			    var resp = confirm("You did not assign a score for "+studentID+
                   1370: 					       ", part "+partid+". Continue?");
1.71      ng       1371: 			    if (resp == false) {
1.125     ng       1372: 				formname["GD_BOX"+i+"_"+partid].focus();
1.71      ng       1373: 				return false;
                   1374: 			    }
                   1375: 			}
                   1376: 		    }
                   1377: 		}
                   1378: 	    }
                   1379: 	}
1.121     ng       1380: 	if (val == "Grade Student") {
                   1381: 	    formname.showgrading.value = "yes";
                   1382: 	    if (formname.Status.value == "") {
                   1383: 		formname.Status.value = "Active";
                   1384: 	    }
                   1385: 	    formname.studentNo.value = total;
                   1386: 	}
1.120     ng       1387: 	formname.submit();
                   1388:     }
                   1389: 
1.71      ng       1390: //======= Check that a score is assigned for all the problems (page/sequence grading only) =========
                   1391:     function checkSubmitPage(formname,total) {
                   1392: 	noscore = new Array(100);
                   1393: 	var ptr = 0;
                   1394: 	for (i=1;i<total;i++) {
1.125     ng       1395: 	    var partid = formname["q_"+i].value;
1.127     ng       1396: 	    if (formname["GD_SEL"+i+"_"+partid][0].selected) {
1.125     ng       1397: 		var points = formname["GD_BOX"+i+"_"+partid].value;
                   1398: 		var status = formname["solved"+i+"_"+partid].value;
1.71      ng       1399: 		if (points == "" && status != "correct_by_student") {
                   1400: 		    noscore[ptr] = i;
                   1401: 		    ptr++;
                   1402: 		}
                   1403: 	    }
                   1404: 	}
                   1405: 	if (ptr != 0) {
                   1406: 	    var sense = ptr == 1 ? ": " : "s: ";
                   1407: 	    var prolist = "";
                   1408: 	    if (ptr == 1) {
                   1409: 		prolist = noscore[0];
                   1410: 	    } else {
                   1411: 		var i = 0;
                   1412: 		while (i < ptr-1) {
                   1413: 		    prolist += noscore[i]+", ";
                   1414: 		    i++;
                   1415: 		}
                   1416: 		prolist += "and "+noscore[i];
                   1417: 	    }
                   1418: 	    var resp = confirm("You did not assign any score for the following problem"+sense+prolist+". Continue?");
                   1419: 	    if (resp == false) {
                   1420: 		return false;
                   1421: 	    }
                   1422: 	}
1.45      ng       1423: 
1.71      ng       1424: 	formname.submit();
                   1425:     }
                   1426: </script>
                   1427: SUBJAVASCRIPT
                   1428: }
1.45      ng       1429: 
1.71      ng       1430: #--- javascript for essay type problem --
                   1431: sub sub_page_kw_js {
                   1432:     my $request = shift;
1.80      ng       1433:     my $iconpath = $request->dir_config('lonIconsURL');
1.118     ng       1434:     &commonJSfunctions($request);
1.350     albertel 1435: 
1.351     albertel 1436:     my $inner_js_msg_central=<<INNERJS;
1.350     albertel 1437:     <script text="text/javascript">
                   1438:     function checkInput() {
                   1439:       opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);
                   1440:       var nmsg   = opener.document.SCORE.savemsgN.value;
                   1441:       var usrctr = document.msgcenter.usrctr.value;
                   1442:       var newval = opener.document.SCORE["newmsg"+usrctr];
                   1443:       newval.value = opener.checkEntities(document.msgcenter.newmsg.value);
                   1444: 
                   1445:       var msgchk = "";
                   1446:       if (document.msgcenter.subchk.checked) {
                   1447:          msgchk = "msgsub,";
                   1448:       }
                   1449:       var includemsg = 0;
                   1450:       for (var i=1; i<=nmsg; i++) {
                   1451:           var opnmsg = opener.document.SCORE["savemsg"+i];
                   1452:           var frmmsg = document.msgcenter["msg"+i];
                   1453:           opnmsg.value = opener.checkEntities(frmmsg.value);
                   1454:           var showflg = opener.document.SCORE["shownOnce"+i];
                   1455:           showflg.value = "1";
                   1456:           var chkbox = document.msgcenter["msgn"+i];
                   1457:           if (chkbox.checked) {
                   1458:              msgchk += "savemsg"+i+",";
                   1459:              includemsg = 1;
                   1460:           }
                   1461:       }
                   1462:       if (document.msgcenter.newmsgchk.checked) {
                   1463:          msgchk += "newmsg"+usrctr;
                   1464:          includemsg = 1;
                   1465:       }
                   1466:       imgformname = opener.document.SCORE["mailicon"+usrctr];
                   1467:       imgformname.src = "$iconpath/"+((includemsg) ? "mailto.gif" : "mailbkgrd.gif");
                   1468:       var includemsg = opener.document.SCORE["includemsg"+usrctr];
                   1469:       includemsg.value = msgchk;
                   1470: 
                   1471:       self.close()
                   1472: 
                   1473:     }
                   1474:     </script>
                   1475: INNERJS
                   1476: 
1.351     albertel 1477:     my $inner_js_highlight_central=<<INNERJS;
                   1478:  <script type="text/javascript">
                   1479:     function updateChoice(flag) {
                   1480:       opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr);
                   1481:       opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize);
                   1482:       opener.document.SCORE.kwstyle.value = opener.radioSelection(document.hlCenter.kwdstyle);
                   1483:       opener.document.SCORE.refresh.value = "on";
                   1484:       if (opener.document.SCORE.keywords.value!=""){
                   1485:          opener.document.SCORE.submit();
                   1486:       }
                   1487:       self.close()
                   1488:     }
                   1489: </script>
                   1490: INNERJS
                   1491: 
                   1492:     my $start_page_msg_central = 
                   1493:         &Apache::loncommon::start_page('Message Central',$inner_js_msg_central,
                   1494: 				       {'js_ready'  => 1,
                   1495: 					'only_body' => 1,
                   1496: 					'bgcolor'   =>'#FFFFFF',});
                   1497:     my $end_page_msg_central = 
                   1498: 	&Apache::loncommon::end_page({'js_ready' => 1});
                   1499: 
                   1500: 
                   1501:     my $start_page_highlight_central = 
                   1502:         &Apache::loncommon::start_page('Highlight Central',
                   1503: 				       $inner_js_highlight_central,
1.350     albertel 1504: 				       {'js_ready'  => 1,
                   1505: 					'only_body' => 1,
                   1506: 					'bgcolor'   =>'#FFFFFF',});
1.351     albertel 1507:     my $end_page_highlight_central = 
1.350     albertel 1508: 	&Apache::loncommon::end_page({'js_ready' => 1});
                   1509: 
1.219     www      1510:     my $docopen=&Apache::lonhtmlcommon::javascript_docopen();
1.236     albertel 1511:     $docopen=~s/^document\.//;
1.596.2.12.2.  6(raebur 1512:6):     my %js_lt = &Apache::lonlocal::texthash(
1.596.2.4  raeburn  1513:                 keyw => 'Keywords list, separated by a space. Add/delete to list if desired.',
                   1514:                 plse => 'Please select a word or group of words from document and then click this link.',
                   1515:                 adds => 'Add selection to keyword list? Edit if desired.',
1.596.2.12.2.  6(raebur 1516:6):                 col1 => 'red',
                   1517:6):                 col2 => 'green',
                   1518:6):                 col3 => 'blue',
                   1519:6):                 siz1 => 'normal',
                   1520:6):                 siz2 => '+1',
                   1521:6):                 siz3 => '+2',
                   1522:6):                 sty1 => 'normal',
                   1523:6):                 sty2 => 'italic',
                   1524:6):                 sty3 => 'bold',
                   1525:6):              );
                   1526:6):     my %html_js_lt = &Apache::lonlocal::texthash(
1.596.2.4  raeburn  1527:                 comp => 'Compose Message for: ',
                   1528:                 incl => 'Include',
                   1529:                 type => 'Type',
                   1530:                 subj => 'Subject',
                   1531:                 mesa => 'Message',
                   1532:                 new  => 'New',
                   1533:                 save => 'Save',
                   1534:                 canc => 'Cancel',
                   1535:                 kehi => 'Keyword Highlight Options',
                   1536:                 txtc => 'Text Color',
                   1537:                 font => 'Font Size',
                   1538:                 fnst => 'Font Style',
                   1539:              );
1.596.2.12.2.  6(raebur 1540:6):     &js_escape(\%js_lt);
                   1541:6):     &html_escape(\%html_js_lt);
                   1542:6):     &js_escape(\%html_js_lt);
1.71      ng       1543:     $request->print(<<SUBJAVASCRIPT);
                   1544: <script type="text/javascript" language="javascript">
1.45      ng       1545: 
1.44      ng       1546: //===================== Show list of keywords ====================
1.122     ng       1547:   function keywords(formname) {
1.596.2.12.2.  6(raebur 1548:6):     var nret = prompt("$js_lt{'keyw'}",formname.keywords.value);
1.44      ng       1549:     if (nret==null) return;
1.122     ng       1550:     formname.keywords.value = nret;
1.44      ng       1551: 
1.122     ng       1552:     if (formname.keywords.value != "") {
1.128     ng       1553: 	formname.refresh.value = "on";
1.122     ng       1554: 	formname.submit();
1.44      ng       1555:     }
                   1556:     return;
                   1557:   }
                   1558: 
                   1559: //===================== Script to view submitted by ==================
                   1560:   function viewSubmitter(submitter) {
                   1561:     document.SCORE.refresh.value = "on";
                   1562:     document.SCORE.NCT.value = "1";
                   1563:     document.SCORE.unamedom0.value = submitter;
                   1564:     document.SCORE.submit();
                   1565:     return;
                   1566:   }
                   1567: 
                   1568: //===================== Script to add keyword(s) ==================
                   1569:   function getSel() {
                   1570:     if (document.getSelection) txt = document.getSelection();
                   1571:     else if (document.selection) txt = document.selection.createRange().text;
                   1572:     else return;
                   1573:     var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");
                   1574:     if (cleantxt=="") {
1.596.2.12.2.  6(raebur 1575:6): 	alert("$js_lt{'plse'}");
1.44      ng       1576: 	return;
                   1577:     }
1.596.2.12.2.  6(raebur 1578:6):     var nret = prompt("$js_lt{'adds'}",cleantxt);
1.44      ng       1579:     if (nret==null) return;
1.127     ng       1580:     document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret;
1.44      ng       1581:     if (document.SCORE.keywords.value != "") {
1.127     ng       1582: 	document.SCORE.refresh.value = "on";
1.44      ng       1583: 	document.SCORE.submit();
                   1584:     }
                   1585:     return;
                   1586:   }
                   1587: 
                   1588: //====================== Script for composing message ==============
1.80      ng       1589:    // preload images
                   1590:    img1 = new Image();
                   1591:    img1.src = "$iconpath/mailbkgrd.gif";
                   1592:    img2 = new Image();
                   1593:    img2.src = "$iconpath/mailto.gif";
                   1594: 
1.44      ng       1595:   function msgCenter(msgform,usrctr,fullname) {
                   1596:     var Nmsg  = msgform.savemsgN.value;
                   1597:     savedMsgHeader(Nmsg,usrctr,fullname);
                   1598:     var subject = msgform.msgsub.value;
1.127     ng       1599:     var msgchk = document.SCORE["includemsg"+usrctr].value;
1.44      ng       1600:     re = /msgsub/;
                   1601:     var shwsel = "";
                   1602:     if (re.test(msgchk)) { shwsel = "checked" }
1.123     ng       1603:     subject = (document.SCORE.shownSub.value == 0 ? checkEntities(subject) : subject);
                   1604:     displaySubject(checkEntities(subject),shwsel);
1.44      ng       1605:     for (var i=1; i<=Nmsg; i++) {
1.123     ng       1606: 	var testmsg = "savemsg"+i+",";
                   1607: 	re = new RegExp(testmsg,"g");
1.44      ng       1608: 	shwsel = "";
                   1609: 	if (re.test(msgchk)) { shwsel = "checked" }
1.125     ng       1610: 	var message = document.SCORE["savemsg"+i].value;
1.126     ng       1611: 	message = (document.SCORE["shownOnce"+i].value == 0 ? checkEntities(message) : message);
1.123     ng       1612: 	displaySavedMsg(i,message,shwsel); //I do not get it. w/o checkEntities on saved messages,
                   1613: 	                                   //any &lt; is already converted to <, etc. However, only once!!
1.44      ng       1614:     }
1.125     ng       1615:     newmsg = document.SCORE["newmsg"+usrctr].value;
1.44      ng       1616:     shwsel = "";
                   1617:     re = /newmsg/;
                   1618:     if (re.test(msgchk)) { shwsel = "checked" }
                   1619:     newMsg(newmsg,shwsel);
                   1620:     msgTail(); 
                   1621:     return;
                   1622:   }
                   1623: 
1.123     ng       1624:   function checkEntities(strx) {
                   1625:     if (strx.length == 0) return strx;
                   1626:     var orgStr = ["&", "<", ">", '"']; 
                   1627:     var newStr = ["&amp;", "&lt;", "&gt;", "&quot;"];
                   1628:     var counter = 0;
                   1629:     while (counter < 4) {
                   1630: 	strx = strReplace(strx,orgStr[counter],newStr[counter]);
                   1631: 	counter++;
                   1632:     }
                   1633:     return strx;
                   1634:   }
                   1635: 
                   1636:   function strReplace(strx, orgStr, newStr) {
                   1637:     return strx.split(orgStr).join(newStr);
                   1638:   }
                   1639: 
1.44      ng       1640:   function savedMsgHeader(Nmsg,usrctr,fullname) {
1.76      ng       1641:     var height = 70*Nmsg+250;
1.44      ng       1642:     if (height > 600) {
                   1643: 	height = 600;
                   1644:     }
1.118     ng       1645:     var xpos = (screen.width-600)/2;
                   1646:     xpos = (xpos < 0) ? '0' : xpos;
                   1647:     var ypos = (screen.height-height)/2-30;
                   1648:     ypos = (ypos < 0) ? '0' : ypos;
                   1649: 
1.596.2.12.2.  (raeburn 1650:):     pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars=yes,screenx='+xpos+',screeny='+ypos+',width=700,height='+height);
1.76      ng       1651:     pWin.focus();
                   1652:     pDoc = pWin.document;
1.219     www      1653:     pDoc.$docopen;
1.351     albertel 1654:     pDoc.write('$start_page_msg_central');
1.76      ng       1655: 
                   1656:     pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");
                   1657:     pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");
1.596.2.12.2.  6(raebur 1658:6):     pDoc.write("<h3><span class=\\"LC_info\\">&nbsp;$html_js_lt{'comp'}\"+fullname+\"<\\/span><\\/h3><br /><br />");
1.76      ng       1659: 
1.564     bisitz   1660:     pDoc.write('<table border="0" width="100%"><tr><td bgcolor="#777777">');
                   1661:     pDoc.write('<table border="0" width="100%"><tr bgcolor="#DDFFFF">');
1.596.2.12.2.  6(raebur 1662:6):     pDoc.write("<td><b>$html_js_lt{'type'}<\\/b><\\/td><td><b>$html_js_lt{'incl'}<\\/b><\\/td><td><b>$html_js_lt{'mesa'}<\\/td><\\/tr>");
1.44      ng       1663: }
                   1664:     function displaySubject(msg,shwsel) {
1.76      ng       1665:     pDoc = pWin.document;
                   1666:     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
1.596.2.12.2.  6(raebur 1667:6):     pDoc.write("<td>$html_js_lt{'subj'}<\\/td>");
1.465     albertel 1668:     pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
                   1669:     pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" maxlength=\\"80\\"><\\/td><\\/tr>");
1.44      ng       1670: }
                   1671: 
1.72      ng       1672:   function displaySavedMsg(ctr,msg,shwsel) {
1.76      ng       1673:     pDoc = pWin.document;
                   1674:     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
1.465     albertel 1675:     pDoc.write("<td align=\\"center\\">"+ctr+"<\\/td>");
                   1676:     pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
                   1677:     pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"<\\/textarea><\\/td><\\/tr>");
1.44      ng       1678: }
                   1679: 
                   1680:   function newMsg(newmsg,shwsel) {
1.76      ng       1681:     pDoc = pWin.document;
                   1682:     pDoc.write("<tr bgcolor=\\"#ffffdd\\">");
1.596.2.12.2.  6(raebur 1683:6):     pDoc.write("<td align=\\"center\\">$html_js_lt{'new'}<\\/td>");
1.465     albertel 1684:     pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
                   1685:     pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"<\\/textarea><\\/td><\\/tr>");
1.44      ng       1686: }
                   1687: 
                   1688:   function msgTail() {
1.76      ng       1689:     pDoc = pWin.document;
1.465     albertel 1690:     pDoc.write("<\\/table>");
                   1691:     pDoc.write("<\\/td><\\/tr><\\/table>&nbsp;");
1.596.2.12.2.  6(raebur 1692:6):     pDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'save'}\\" onclick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");
                   1693:6):     pDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'canc'}\\" onclick=\\"self.close()\\"><br /><br />");
1.465     albertel 1694:     pDoc.write("<\\/form>");
1.351     albertel 1695:     pDoc.write('$end_page_msg_central');
1.128     ng       1696:     pDoc.close();
1.44      ng       1697: }
                   1698: 
                   1699: //====================== Script for keyword highlight options ==============
                   1700:   function kwhighlight() {
                   1701:     var kwclr    = document.SCORE.kwclr.value;
                   1702:     var kwsize   = document.SCORE.kwsize.value;
                   1703:     var kwstyle  = document.SCORE.kwstyle.value;
                   1704:     var redsel = "";
                   1705:     var grnsel = "";
                   1706:     var blusel = "";
1.596.2.12.2.  6(raebur 1707:6):     var txtcol1 = "$js_lt{'col1'}";
                   1708:6):     var txtcol2 = "$js_lt{'col2'}";
                   1709:6):     var txtcol3 = "$js_lt{'col3'}";
                   1710:6):     var txtsiz1 = "$js_lt{'siz1'}";
                   1711:6):     var txtsiz2 = "$js_lt{'siz2'}";
                   1712:6):     var txtsiz3 = "$js_lt{'siz3'}";
                   1713:6):     var txtsty1 = "$js_lt{'sty1'}";
                   1714:6):     var txtsty2 = "$js_lt{'sty2'}";
                   1715:6):     var txtsty3 = "$js_lt{'sty3'}";
          8(raebur 1716:4):     if (kwclr=="red")   {var redsel="checked='checked'"};
                   1717:4):     if (kwclr=="green") {var grnsel="checked='checked'"};
                   1718:4):     if (kwclr=="blue")  {var blusel="checked='checked'"};
1.44      ng       1719:     var sznsel = "";
                   1720:     var sz1sel = "";
                   1721:     var sz2sel = "";
1.596.2.12.2.  8(raebur 1722:4):     if (kwsize=="0")  {var sznsel="checked='checked'"};
                   1723:4):     if (kwsize=="+1") {var sz1sel="checked='checked'"};
                   1724:4):     if (kwsize=="+2") {var sz2sel="checked='checked'"};
1.44      ng       1725:     var synsel = "";
                   1726:     var syisel = "";
                   1727:     var sybsel = "";
1.596.2.12.2.  8(raebur 1728:4):     if (kwstyle=="")    {var synsel="checked='checked'"};
                   1729:4):     if (kwstyle=="<i>") {var syisel="checked='checked'"};
                   1730:4):     if (kwstyle=="<b>") {var sybsel="checked='checked'"};
1.44      ng       1731:     highlightCentral();
1.596.2.12.2.  8(raebur 1732:4):     highlightbody('red',txtcol1,redsel,'0',txtsiz1,sznsel,'',txtsty1,synsel);
                   1733:4):     highlightbody('green',txtcol2,grnsel,'+1',txtsiz2,sz1sel,'<i>',txtsty2,syisel);
                   1734:4):     highlightbody('blue',txtcol3,blusel,'+2',txtsiz3,sz2sel,'<b>',txtsty3,sybsel);
1.44      ng       1735:     highlightend();
                   1736:     return;
                   1737:   }
                   1738: 
                   1739:   function highlightCentral() {
1.76      ng       1740: //    if (window.hwdWin) window.hwdWin.close();
1.118     ng       1741:     var xpos = (screen.width-400)/2;
                   1742:     xpos = (xpos < 0) ? '0' : xpos;
                   1743:     var ypos = (screen.height-330)/2-30;
                   1744:     ypos = (ypos < 0) ? '0' : ypos;
                   1745: 
1.206     albertel 1746:     hwdWin = window.open('', 'KeywordHighlightCentral', 'resizeable=yes,toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx='+xpos+',screeny='+ypos);
1.76      ng       1747:     hwdWin.focus();
                   1748:     var hDoc = hwdWin.document;
1.219     www      1749:     hDoc.$docopen;
1.351     albertel 1750:     hDoc.write('$start_page_highlight_central');
1.76      ng       1751:     hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
1.596.2.12.2.  6(raebur 1752:6):     hDoc.write("<h1>$html_js_lt{'kehi'}<\\/h1>");
1.76      ng       1753: 
1.596.2.12.2.  8(raebur 1754:4):     hDoc.write('<table border="0" width="100%"><tr style="background-color:#A1D676">');
          6(raebur 1755:6):     hDoc.write("<th>$html_js_lt{'txtc'}<\\/th><th>$html_js_lt{'font'}<\\/th><th>$html_js_lt{'fnst'}<\\/th><\\/tr>");
1.44      ng       1756:   }
                   1757: 
                   1758:   function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { 
1.76      ng       1759:     var hDoc = hwdWin.document;
1.596.2.12.2.  8(raebur 1760:4):     hDoc.write("<tr>");
1.76      ng       1761:     hDoc.write("<td align=\\"left\\">");
1.596.2.12.2.  8(raebur 1762:4):     hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+" \\/>&nbsp;"+clrtxt+"<\\/td>");
1.76      ng       1763:     hDoc.write("<td align=\\"left\\">");
1.596.2.12.2.  8(raebur 1764:4):     hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+" \\/>&nbsp;"+sztxt+"<\\/td>");
1.76      ng       1765:     hDoc.write("<td align=\\"left\\">");
1.596.2.12.2.  8(raebur 1766:4):     hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+" \\/>&nbsp;"+sytxt+"<\\/td>");
1.465     albertel 1767:     hDoc.write("<\\/tr>");
1.44      ng       1768:   }
                   1769: 
                   1770:   function highlightend() { 
1.76      ng       1771:     var hDoc = hwdWin.document;
1.596.2.12.2.  8(raebur 1772:4):     hDoc.write("<\\/table><br \\/>");
          6(raebur 1773:6):     hDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'save'}\\" onclick=\\"javascript:updateChoice(1)\\" \\/>&nbsp;&nbsp;");
                   1774:6):     hDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'canc'}\\" onclick=\\"self.close()\\" \\/><br /><br />");
1.465     albertel 1775:     hDoc.write("<\\/form>");
1.351     albertel 1776:     hDoc.write('$end_page_highlight_central');
1.128     ng       1777:     hDoc.close();
1.44      ng       1778:   }
                   1779: 
                   1780: </script>
                   1781: SUBJAVASCRIPT
                   1782: }
                   1783: 
1.349     albertel 1784: sub get_increment {
1.348     bowersj2 1785:     my $increment = $env{'form.increment'};
                   1786:     if ($increment != 1 && $increment != .5 && $increment != .25 &&
                   1787:         $increment != .1) {
                   1788:         $increment = 1;
                   1789:     }
                   1790:     return $increment;
                   1791: }
                   1792: 
1.585     bisitz   1793: sub gradeBox_start {
                   1794:     return (
                   1795:         &Apache::loncommon::start_data_table()
                   1796:        .&Apache::loncommon::start_data_table_header_row()
                   1797:        .'<th>'.&mt('Part').'</th>'
                   1798:        .'<th>'.&mt('Points').'</th>'
                   1799:        .'<th>&nbsp;</th>'
                   1800:        .'<th>'.&mt('Assign Grade').'</th>'
                   1801:        .'<th>'.&mt('Weight').'</th>'
                   1802:        .'<th>'.&mt('Grade Status').'</th>'
                   1803:        .&Apache::loncommon::end_data_table_header_row()
                   1804:     );
                   1805: }
                   1806: 
                   1807: sub gradeBox_end {
                   1808:     return (
                   1809:         &Apache::loncommon::end_data_table()
                   1810:     );
                   1811: }
1.71      ng       1812: #--- displays the grading box, used in essay type problem and grading by page/sequence
                   1813: sub gradeBox {
1.322     albertel 1814:     my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;
1.381     albertel 1815:     my $checkIcon = '<img alt="'.&mt('Check Mark').
1.485     albertel 1816: 	'" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
1.71      ng       1817:     my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
1.466     albertel 1818:     my $wgtmsg = ($wgt > 0) ? &mt('(problem weight)') 
                   1819:                            : '<span class="LC_info">'.&mt('problem weight assigned by computer').'</span>';
1.71      ng       1820:     $wgt       = ($wgt > 0 ? $wgt : '1');
                   1821:     my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
1.320     albertel 1822: 		  '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
1.596.2.12.2.  8(raebur 1823:3):     my $data_WGT='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
1.466     albertel 1824:     my $display_part= &get_display_part($partid,$symb);
1.270     albertel 1825:     my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
                   1826: 				       [$partid]);
                   1827:     my $aggtries = $$record{'resource.'.$partid.'.tries'};
1.269     raeburn  1828:     if ($last_resets{$partid}) {
                   1829:         $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
                   1830:     }
1.596.2.12.2.  8(raebur 1831:3):     my $result=&Apache::loncommon::start_data_table_row();
1.71      ng       1832:     my $ctr = 0;
1.348     bowersj2 1833:     my $thisweight = 0;
1.349     albertel 1834:     my $increment = &get_increment();
1.485     albertel 1835: 
                   1836:     my $radio.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
1.348     bowersj2 1837:     while ($thisweight<=$wgt) {
1.532     bisitz   1838: 	$radio.= '<td><span class="LC_nobreak"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
1.589     bisitz   1839:         'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.
1.348     bowersj2 1840: 	    $thisweight.')" value="'.$thisweight.'" '.
1.401     albertel 1841: 	    ($score eq $thisweight ? 'checked="checked"':'').' /> '.$thisweight."</label></span></td>\n";
1.485     albertel 1842: 	$radio.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
1.348     bowersj2 1843:         $thisweight += $increment;
1.71      ng       1844: 	$ctr++;
                   1845:     }
1.485     albertel 1846:     $radio.='</tr></table>';
                   1847: 
                   1848:     my $line.='<input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
1.71      ng       1849: 	($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
1.589     bisitz   1850: 	'onchange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.
1.71      ng       1851: 	$wgt.')" /></td>'."\n";
1.485     albertel 1852:     $line.='<td>/'.$wgt.' '.$wgtmsg.
1.71      ng       1853: 	($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').
1.585     bisitz   1854: 	' </td>'."\n";
                   1855:     $line.='<td><select name="GD_SEL'.$counter.'_'.$partid.'" '.
1.589     bisitz   1856: 	'onchange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
1.71      ng       1857:     if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
1.485     albertel 1858: 	$line.='<option></option>'.
                   1859: 	    '<option value="excused" selected="selected">'.&mt('excused').'</option>';
1.71      ng       1860:     } else {
1.485     albertel 1861: 	$line.='<option selected="selected"></option>'.
                   1862: 	    '<option value="excused" >'.&mt('excused').'</option>';
1.71      ng       1863:     }
1.485     albertel 1864:     $line.='<option value="reset status">'.&mt('reset status').'</option></select>'."\n";
                   1865: 
                   1866: 
                   1867:     $result .= 
1.596.2.12.2.  8(raebur 1868:3): 	    '<td>'.$data_WGT.$display_part.'</td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>';
                   1869:3):     $result.=&Apache::loncommon::end_data_table_row().'<td colspan="6">';
1.71      ng       1870:     $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n".
                   1871: 	'<input type="hidden" name="oldpts'.$counter.'_'.$partid.'" value="'.$score.'" />'."\n".
                   1872: 	'<input type="hidden" name="solved'.$counter.'_'.$partid.'" value="'.
1.269     raeburn  1873: 	$$record{'resource.'.$partid.'.solved'}.'" />'."\n".
                   1874:         '<input type="hidden" name="totaltries'.$counter.'_'.$partid.'" value="'.
                   1875:         $$record{'resource.'.$partid.'.tries'}.'" />'."\n".
                   1876:         '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
                   1877:         $aggtries.'" />'."\n";
1.582     raeburn  1878:     my $res_error;
                   1879:     $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record,\$res_error);
1.596.2.12.2.  8(raebur 1880:3):     $result.='</td>'.&Apache::loncommon::end_data_table_row();
1.582     raeburn  1881:     if ($res_error) {
                   1882:         return &navmap_errormsg();
                   1883:     }
1.318     banghart 1884:     return $result;
                   1885: }
1.322     albertel 1886: 
                   1887: sub handback_box {
1.582     raeburn  1888:     my ($symb,$uname,$udom,$counter,$partid,$record,$res_error) = @_;
                   1889:     my ($partlist,$handgrade,$responseType) = &response_type($symb,$res_error);
1.323     banghart 1890:     my (@respids);
1.596.2.4  raeburn  1891:     my @part_response_id = &flatten_responseType($responseType);
1.375     albertel 1892:     foreach my $part_response_id (@part_response_id) {
                   1893:     	my ($part,$resp) = @{ $part_response_id };
1.323     banghart 1894:         if ($part eq $partid) {
1.375     albertel 1895:             push(@respids,$resp);
1.323     banghart 1896:         }
                   1897:     }
1.318     banghart 1898:     my $result;
1.323     banghart 1899:     foreach my $respid (@respids) {
1.322     albertel 1900: 	my $prefix = $counter.'_'.$partid.'_'.$respid.'_';
                   1901: 	my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record);
                   1902: 	next if (!@$files);
1.596.2.4  raeburn  1903: 	my $file_counter = 0;
1.313     banghart 1904: 	foreach my $file (@$files) {
1.368     banghart 1905: 	    if ($file =~ /\/portfolio\//) {
1.596.2.4  raeburn  1906:                 $file_counter++;
1.368     banghart 1907:     	        my ($file_path, $file_disp) = ($file =~ m|(.+/)(.+)$|);
                   1908:     	        my ($name,$version,$ext) = &file_name_version_ext($file_disp);
                   1909:     	        $file_disp = "$name.$ext";
                   1910:     	        $file = $file_path.$file_disp;
                   1911:     	        $result.=&mt('Return commented version of [_1] to student.',
                   1912:     			 '<span class="LC_filename">'.$file_disp.'</span>');
                   1913:     	        $result.='<input type="file"   name="'.$prefix.'returndoc'.$file_counter.'" />'."\n";
1.596.2.4  raeburn  1914:     	        $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />'."\n";
1.368     banghart 1915: 	    }
1.322     albertel 1916: 	}
1.596.2.4  raeburn  1917:         if ($file_counter) {
                   1918:             $result .= '<input type="hidden" name="'.$prefix.'countreturndoc" value="'.$file_counter.'" />'."\n".
                   1919:                        '<span class="LC_info">'.
                   1920:                        '('.&mt('File(s) will be uploaded when you click on Save &amp; Next below.',$file_counter).')</span><br /><br />';
                   1921:         }
1.313     banghart 1922:     }
1.318     banghart 1923:     return $result;    
1.71      ng       1924: }
1.44      ng       1925: 
1.58      albertel 1926: sub show_problem {
1.382     albertel 1927:     my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode,$form) = @_;
1.144     albertel 1928:     my $rendered;
1.382     albertel 1929:     my %form = ((ref($form) eq 'HASH')? %{$form} : ());
1.329     albertel 1930:     &Apache::lonxml::remember_problem_counter();
1.144     albertel 1931:     if ($mode eq 'both' or $mode eq 'text') {
                   1932: 	$rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,
1.382     albertel 1933: 						       $env{'request.course.id'},
                   1934: 						       undef,\%form);
1.144     albertel 1935:     }
1.58      albertel 1936:     if ($removeform) {
                   1937: 	$rendered=~s|<form(.*?)>||g;
                   1938: 	$rendered=~s|</form>||g;
1.374     albertel 1939: 	$rendered=~s|(<input[^>]*name\s*=\s*"?)(\w+)("?)|$1would_have_been_$2$3|g;
1.58      albertel 1940:     }
1.144     albertel 1941:     my $companswer;
                   1942:     if ($mode eq 'both' or $mode eq 'answer') {
1.329     albertel 1943: 	&Apache::lonxml::restore_problem_counter();
1.382     albertel 1944: 	$companswer=
                   1945: 	    &Apache::loncommon::get_student_answers($symb,$uname,$udom,
                   1946: 						    $env{'request.course.id'},
                   1947: 						    %form);
1.144     albertel 1948:     }
1.58      albertel 1949:     if ($removeform) {
                   1950: 	$companswer=~s|<form(.*?)>||g;
                   1951: 	$companswer=~s|</form>||g;
1.144     albertel 1952: 	$companswer=~s|name="submit"|name="would_have_been_submit"|g;
1.58      albertel 1953:     }
1.596.2.12.2.  (raeburn 1954:):     my $renderheading = &mt('View of the problem');
                   1955:):     my $answerheading = &mt('Correct answer');
                   1956:):     if (($uname ne $env{'user.name'}) || ($udom ne $env{'user.domain'})) {
                   1957:):         my $stu_fullname = $env{'form.fullname'};
                   1958:):         if ($stu_fullname eq '') {
                   1959:):             $stu_fullname = &Apache::loncommon::plainname($uname,$udom,'lastname');
                   1960:):         }
                   1961:):         my $forwhom = &nameUserString(undef,$stu_fullname,$uname,$udom);
                   1962:):         if ($forwhom ne '') {
                   1963:):             $renderheading = &mt('View of the problem for[_1]',$forwhom);
                   1964:):             $answerheading = &mt('Correct answer for[_1]',$forwhom);
                   1965:):         }
                   1966:):     }
1.468     albertel 1967:     $rendered=
1.588     bisitz   1968:         '<div class="LC_Box">'
1.596.2.12.2.  (raeburn 1969:):        .'<h3 class="LC_hcell">'.$renderheading.'</h3>'
1.588     bisitz   1970:        .$rendered
                   1971:        .'</div>';
1.468     albertel 1972:     $companswer=
1.588     bisitz   1973:         '<div class="LC_Box">'
1.596.2.12.2.  (raeburn 1974:):        .'<h3 class="LC_hcell">'.$answerheading.'</h3>'
1.588     bisitz   1975:        .$companswer
                   1976:        .'</div>';
1.468     albertel 1977:     my $result;
1.144     albertel 1978:     if ($mode eq 'both') {
1.588     bisitz   1979:         $result=$rendered.$companswer;
1.144     albertel 1980:     } elsif ($mode eq 'text') {
1.588     bisitz   1981:         $result=$rendered;
1.144     albertel 1982:     } elsif ($mode eq 'answer') {
1.588     bisitz   1983:         $result=$companswer;
1.144     albertel 1984:     }
1.71      ng       1985:     return $result;
1.58      albertel 1986: }
1.397     albertel 1987: 
1.396     banghart 1988: sub files_exist {
                   1989:     my ($r, $symb) = @_;
                   1990:     my @students = &Apache::loncommon::get_env_multiple('form.stuinfo');
1.397     albertel 1991: 
1.396     banghart 1992:     foreach my $student (@students) {
                   1993:         my ($uname,$udom,$fullname) = split(/:/,$student);
1.397     albertel 1994:         my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
                   1995: 					      $udom,$uname);
1.396     banghart 1996:         my ($string,$timestamp)= &get_last_submission(\%record);
1.397     albertel 1997:         foreach my $submission (@$string) {
                   1998:             my ($partid,$respid) =
                   1999: 		($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
                   2000:             my $files=&get_submitted_files($udom,$uname,$partid,$respid,
                   2001: 					   \%record);
                   2002:             return 1 if (@$files);
1.396     banghart 2003:         }
                   2004:     }
1.397     albertel 2005:     return 0;
1.396     banghart 2006: }
1.397     albertel 2007: 
1.394     banghart 2008: sub download_all_link {
                   2009:     my ($r,$symb) = @_;
1.395     albertel 2010:     my $all_students = 
                   2011: 	join("\n", &Apache::loncommon::get_env_multiple('form.stuinfo'));
                   2012: 
                   2013:     my $parts =
                   2014: 	join("\n",&Apache::loncommon::get_env_multiple('form.vPart'));
                   2015: 
1.394     banghart 2016:     my $identifier = &Apache::loncommon::get_cgi_id();
1.514     raeburn  2017:     &Apache::lonnet::appenv({'cgi.'.$identifier.'.students' => $all_students,
                   2018:                              'cgi.'.$identifier.'.symb' => $symb,
                   2019:                              'cgi.'.$identifier.'.parts' => $parts,});
1.395     albertel 2020:     $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.
                   2021: 	      &mt('Download All Submitted Documents').'</a>');
1.394     banghart 2022:     return
                   2023: }
1.395     albertel 2024: 
1.432     banghart 2025: sub build_section_inputs {
                   2026:     my $section_inputs;
                   2027:     if ($env{'form.section'} eq '') {
                   2028:         $section_inputs .= '<input type="hidden" name="section" value="all" />'."\n";
                   2029:     } else {
                   2030:         my @sections = &Apache::loncommon::get_env_multiple('form.section');
1.434     albertel 2031:         foreach my $section (@sections) {
1.432     banghart 2032:             $section_inputs .= '<input type="hidden" name="section" value="'.$section.'" />'."\n";
                   2033:         }
                   2034:     }
                   2035:     return $section_inputs;
                   2036: }
                   2037: 
1.44      ng       2038: # --------------------------- show submissions of a student, option to grade 
                   2039: sub submission {
                   2040:     my ($request,$counter,$total) = @_;
1.257     albertel 2041:     my ($uname,$udom)     = ($env{'form.student'},$env{'form.userdom'});
                   2042:     $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student?
                   2043:     my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
                   2044:     $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq '';
1.596.2.12.2.  (raeburn 2045:):     my ($symb) = &get_symb($request); 
1.324     albertel 2046:     if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }
1.104     albertel 2047: 
                   2048:     if (!&canview($usec)) {
1.596.2.12.2.  8(raebur 2049:4):         $request->print(
                   2050:4):             '<span class="LC_warning">'.
                   2051:4):             &mt('Unable to view requested student.').
                   2052:4):             ' '.&mt('([_1] in section [_2] in course id [_3])',
                   2053:4):                         $uname.':'.$udom,$usec,$env{'request.course.id'}).
                   2054:4):             '</span>');
1.324     albertel 2055: 	$request->print(&show_grading_menu_form($symb));
1.104     albertel 2056: 	return;
                   2057:     }
                   2058: 
1.257     albertel 2059:     if (!$env{'form.lastSub'}) { $env{'form.lastSub'} = 'datesub'; }
                   2060:     if (!$env{'form.vProb'}) { $env{'form.vProb'} = 'yes'; }
                   2061:     if (!$env{'form.vAns'}) { $env{'form.vAns'} = 'yes'; }
                   2062:     my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
1.381     albertel 2063:     my $checkIcon = '<img alt="'.&mt('Check Mark').
                   2064: 	'" src="'.$request->dir_config('lonIconsURL').
1.122     ng       2065: 	'/check.gif" height="16" border="0" />';
1.41      ng       2066: 
                   2067:     # header info
                   2068:     if ($counter == 0) {
                   2069: 	&sub_page_js($request);
1.257     albertel 2070: 	&sub_page_kw_js($request) if ($env{'form.handgrade'} eq 'yes');
                   2071: 	$env{'form.probTitle'} = $env{'form.probTitle'} eq '' ? 
                   2072: 	    &Apache::lonnet::gettitle($symb) : $env{'form.probTitle'};
1.397     albertel 2073: 	if ($env{'form.handgrade'} eq 'yes' && &files_exist($request, $symb)) {
1.396     banghart 2074: 	    &download_all_link($request, $symb);
                   2075: 	}
1.485     albertel 2076: 	$request->print('<h3>&nbsp;<span class="LC_info">'.&mt('Submission Record').'</span></h3>'."\n".
1.596.2.12.2.  2(raebur 2077:3): 			'<h4>&nbsp;'.&mt('[_1]Resource: [_2]','<b>','</b>'.$env{'form.probTitle'}).'</h4>'."\n");
1.118     ng       2078: 
1.44      ng       2079: 	# option to display problem, only once else it cause problems 
                   2080:         # with the form later since the problem has a form.
1.257     albertel 2081: 	if ($env{'form.vProb'} eq 'yes' or $env{'form.vAns'} eq 'yes') {
1.144     albertel 2082: 	    my $mode;
1.257     albertel 2083: 	    if ($env{'form.vProb'} eq 'yes' && $env{'form.vAns'} eq 'yes') {
1.144     albertel 2084: 		$mode='both';
1.257     albertel 2085: 	    } elsif ($env{'form.vProb'} eq 'yes') {
1.144     albertel 2086: 		$mode='text';
1.257     albertel 2087: 	    } elsif ($env{'form.vAns'} eq 'yes') {
1.144     albertel 2088: 		$mode='answer';
                   2089: 	    }
1.329     albertel 2090: 	    &Apache::lonxml::clear_problem_counter();
1.144     albertel 2091: 	    $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode));
1.41      ng       2092: 	}
1.441     www      2093: 
1.596.2.12.2.  0(raebur 2094:3): 	# kwclr is the only variable that is guaranteed not to be blank 
1.44      ng       2095:         # if this subroutine has been called once.
1.41      ng       2096: 	my %keyhash = ();
1.257     albertel 2097: 	if ($env{'form.kwclr'} eq '' && $env{'form.handgrade'} eq 'yes') {
1.41      ng       2098: 	    %keyhash = &Apache::lonnet::dump('nohist_handgrade',
1.257     albertel 2099: 					     $env{'course.'.$env{'request.course.id'}.'.domain'},
                   2100: 					     $env{'course.'.$env{'request.course.id'}.'.num'});
1.41      ng       2101: 
1.257     albertel 2102: 	    my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
                   2103: 	    $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
                   2104: 	    $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
                   2105: 	    $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
                   2106: 	    $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
                   2107: 	    $env{'form.msgsub'}   = $keyhash{$symb.'_subject'} ne '' ? 
                   2108: 		$keyhash{$symb.'_subject'} : $env{'form.probTitle'};
                   2109: 	    $env{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0';
1.41      ng       2110: 	}
1.257     albertel 2111: 	my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'};
1.442     banghart 2112: 	my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
1.303     banghart 2113: 	$request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n".
1.41      ng       2114: 			'<input type="hidden" name="command"    value="handgrade" />'."\n".
1.257     albertel 2115: 			'<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
1.442     banghart 2116: 			'<input type="hidden" name="Status"     value="'.$stu_status.'" />'."\n".
1.120     ng       2117: 			'<input type="hidden" name="overRideScore" value="'.$overRideScore.'" />'."\n".
1.257     albertel 2118: 			'<input type="hidden" name="probTitle"  value="'.$env{'form.probTitle'}.'" />'."\n".
1.41      ng       2119: 			'<input type="hidden" name="refresh"    value="off" />'."\n".
1.120     ng       2120: 			'<input type="hidden" name="studentNo"  value="" />'."\n".
                   2121: 			'<input type="hidden" name="gradeOpt"   value="" />'."\n".
1.418     albertel 2122: 			'<input type="hidden" name="symb"       value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1.257     albertel 2123: 			'<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" />'."\n".
                   2124: 			'<input type="hidden" name="vProb"      value="'.$env{'form.vProb'}.'" />'."\n".
                   2125: 			'<input type="hidden" name="vAns"       value="'.$env{'form.vAns'}.'" />'."\n".
                   2126: 			'<input type="hidden" name="lastSub"    value="'.$env{'form.lastSub'}.'" />'."\n".
1.432     banghart 2127: 			&build_section_inputs().
1.326     albertel 2128: 			'<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'" />'."\n".
                   2129: 			'<input type="hidden" name="handgrade"  value="'.$env{'form.handgrade'}.'" />'."\n".
1.41      ng       2130: 			'<input type="hidden" name="NCT"'.
1.257     albertel 2131: 			' value="'.($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : $total+1).'" />'."\n");
                   2132: 	if ($env{'form.handgrade'} eq 'yes') {
                   2133: 	    $request->print('<input type="hidden" name="keywords" value="'.$env{'form.keywords'}.'" />'."\n".
                   2134: 			    '<input type="hidden" name="kwclr"    value="'.$env{'form.kwclr'}.'" />'."\n".
                   2135: 			    '<input type="hidden" name="kwsize"   value="'.$env{'form.kwsize'}.'" />'."\n".
                   2136: 			    '<input type="hidden" name="kwstyle"  value="'.$env{'form.kwstyle'}.'" />'."\n".
                   2137: 			    '<input type="hidden" name="msgsub"   value="'.$env{'form.msgsub'}.'" />'."\n".
1.123     ng       2138: 			    '<input type="hidden" name="shownSub" value="0" />'."\n".
1.257     albertel 2139: 			    '<input type="hidden" name="savemsgN" value="'.$env{'form.savemsgN'}.'" />'."\n");
1.154     albertel 2140: 	    foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) {
                   2141: 		$request->print('<input type="hidden" name="vPart" value="'.$partid.'" />'."\n");
                   2142: 	    }
1.123     ng       2143: 	}
1.41      ng       2144: 	
                   2145: 	my ($cts,$prnmsg) = (1,'');
1.257     albertel 2146: 	while ($cts <= $env{'form.savemsgN'}) {
1.41      ng       2147: 	    $prnmsg.='<input type="hidden" name="savemsg'.$cts.'" value="'.
1.123     ng       2148: 		(!exists($keyhash{$symb.'_savemsg'.$cts}) ? 
1.257     albertel 2149: 		 &Apache::lonfeedback::clear_out_html($env{'form.savemsg'.$cts}) :
1.80      ng       2150: 		 &Apache::lonfeedback::clear_out_html($keyhash{$symb.'_savemsg'.$cts})).
1.123     ng       2151: 		'" />'."\n".
                   2152: 		'<input type="hidden" name="shownOnce'.$cts.'" value="0" />'."\n";
1.41      ng       2153: 	    $cts++;
                   2154: 	}
                   2155: 	$request->print($prnmsg);
1.32      ng       2156: 
1.257     albertel 2157: 	if ($env{'form.handgrade'} eq 'yes' && $env{'form.showgrading'} eq 'yes') {
1.596.2.4  raeburn  2158: 
                   2159:             my %lt = &Apache::lonlocal::texthash(
1.596.2.12.2.  8(raebur 2160:4):                           keyh => 'Keyword Highlighting for Essays',
1.596.2.4  raeburn  2161:                           keyw => 'Keyword Options',
                   2162:                           list => 'List',
                   2163:                           past => 'Paste Selection to List',
1.596.2.9  raeburn  2164:                           high => 'Highlight Attribute',
1.596.2.4  raeburn  2165:                      );
1.88      www      2166: #
                   2167: # Print out the keyword options line
                   2168: #
1.596.2.12.2.  8(raebur 2169:4):             $request->print(
                   2170:4):                 '<div class="LC_columnSection">'
                   2171:4):                .'<fieldset><legend>'.$lt{'keyh'}.'</legend>'
                   2172:4):                .&Apache::lonhtmlcommon::funclist_from_array(
                   2173:4):                     ['<a href="javascript:keywords(document.SCORE);" target="_self">'.$lt{'list'}.'</a>',
                   2174:4):                      '<a href="#" onmousedown="javascript:getSel(); return false"
                   2175:4):  class="page">'.$lt{'past'}.'</a>',
                   2176:4):                      '<a href="javascript:kwhighlight();" target="_self">'.$lt{'high'}.'</a>'],
                   2177:4):                     {legend => $lt{'keyw'}})
                   2178:4):                .'</fieldset></div>'
                   2179:4):             );
                   2180:4): 
1.88      www      2181: #
                   2182: # Load the other essays for similarity check
                   2183: #
1.324     albertel 2184:             my (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb);
1.384     albertel 2185: 	    my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);
1.359     www      2186: 	    $apath=&escape($apath);
1.88      www      2187: 	    $apath=~s/\W/\_/gs;
1.596.2.12.2.  (raeburn 2188:):             &init_old_essays($symb,$apath,$adom,$aname);
1.41      ng       2189:         }
                   2190:     }
1.44      ng       2191: 
1.441     www      2192: # This is where output for one specific student would start
1.592     bisitz   2193:     my $add_class = ($counter%2) ? ' LC_grade_show_user_odd_row' : '';
                   2194:     $request->print(
                   2195:         "\n\n"
                   2196:        .'<div class="LC_grade_show_user'.$add_class.'">'
                   2197:        .'<h2>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'</h2>'
                   2198:        ."\n"
                   2199:     );
1.441     www      2200: 
1.592     bisitz   2201:     # Show additional functions if allowed
                   2202:     if ($perm{'vgr'}) {
                   2203:         $request->print(
                   2204:             &Apache::loncommon::track_student_link(
1.596.2.12.2.  4(raebur 2205:3):                 'View recent activity',
1.592     bisitz   2206:                 $uname,$udom,'check')
                   2207:            .' '
                   2208:         );
                   2209:     }
                   2210:     if ($perm{'opa'}) {
                   2211:         $request->print(
                   2212:             &Apache::loncommon::pprmlink(
                   2213:                 &mt('Set/Change parameters'),
                   2214:                 $uname,$udom,$symb,'check'));
                   2215:     }
                   2216: 
                   2217:     # Show Problem
1.257     albertel 2218:     if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {
1.144     albertel 2219: 	my $mode;
1.257     albertel 2220: 	if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {
1.144     albertel 2221: 	    $mode='both';
1.257     albertel 2222: 	} elsif ($env{'form.vProb'} eq 'all' ) {
1.144     albertel 2223: 	    $mode='text';
1.257     albertel 2224: 	} elsif ($env{'form.vAns'} eq 'all') {
1.144     albertel 2225: 	    $mode='answer';
                   2226: 	}
1.329     albertel 2227: 	&Apache::lonxml::clear_problem_counter();
1.475     albertel 2228: 	$request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode,{'request.prefix' => 'ctr'.$counter}));
1.58      albertel 2229:     }
1.144     albertel 2230: 
1.257     albertel 2231:     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
1.582     raeburn  2232:     my $res_error;
                   2233:     my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
                   2234:     if ($res_error) {
                   2235:         $request->print(&navmap_errormsg());
                   2236:         return;
                   2237:     }
1.41      ng       2238: 
1.44      ng       2239:     # Display student info
1.41      ng       2240:     $request->print(($counter == 0 ? '' : '<br />'));
1.590     bisitz   2241: 
                   2242:     my $result='<div class="LC_Box">'
                   2243:               .'<h3 class="LC_hcell">'.&mt('Submissions').'</h3>';
1.45      ng       2244:     $result.='<input type="hidden" name="name'.$counter.
1.588     bisitz   2245:              '" value="'.$env{'form.fullname'}.'" />'."\n";
1.469     albertel 2246:     if ($env{'form.handgrade'} eq 'no') {
1.588     bisitz   2247:         $result.='<p class="LC_info">'
                   2248:                 .&mt('Part(s) graded correct by the computer is marked with a [_1] symbol.',$checkIcon)
                   2249:                 ."</p>\n";
1.469     albertel 2250:     }
                   2251: 
1.118     ng       2252:     # If any part of the problem is an essay-response (handgraded), then check for collaborators
1.464     albertel 2253:     my $fullname;
                   2254:     my $col_fullnames = [];
1.257     albertel 2255:     if ($env{'form.handgrade'} eq 'yes') {
1.464     albertel 2256: 	(my $sub_result,$fullname,$col_fullnames)=
                   2257: 	    &check_collaborators($symb,$uname,$udom,\%record,$handgrade,
                   2258: 				 $counter);
                   2259: 	$result.=$sub_result;
1.41      ng       2260:     }
1.44      ng       2261:     $request->print($result."\n");
1.588     bisitz   2262: 
1.44      ng       2263:     # print student answer/submission
1.588     bisitz   2264:     # Options are (1) Handgraded submission only
1.44      ng       2265:     #             (2) Last submission, includes submission that is not handgraded 
                   2266:     #                  (for multi-response type part)
                   2267:     #             (3) Last submission plus the parts info
                   2268:     #             (4) The whole record for this student
1.596.2.12.2.  1(raebur 2269:3): 
1.151     albertel 2270: 	my ($string,$timestamp)= &get_last_submission(\%record);
1.468     albertel 2271: 	
                   2272: 	my $lastsubonly;
                   2273: 
1.588     bisitz   2274:         if ($$timestamp eq '') {
                   2275:             $lastsubonly.='<div class="LC_grade_submissions_body">'.$$string[0].'</div>'; 
                   2276:         } else {
1.592     bisitz   2277:             $lastsubonly =
                   2278:                 '<div class="LC_grade_submissions_body">'
                   2279:                .'<b>'.&mt('Date Submitted:').'</b> '.$$timestamp."\n";
1.468     albertel 2280: 
1.151     albertel 2281: 	    my %seenparts;
1.375     albertel 2282: 	    my @part_response_id = &flatten_responseType($responseType);
                   2283: 	    foreach my $part (@part_response_id) {
1.393     albertel 2284: 		next if ($env{'form.lastSub'} eq 'hdgrade' 
                   2285: 			 && $$handgrade{$$part[0].'_'.$$part[1]} ne 'yes');
                   2286: 
1.375     albertel 2287: 		my ($partid,$respid) = @{ $part };
1.324     albertel 2288: 		my $display_part=&get_display_part($partid,$symb);
1.257     albertel 2289: 		if ($env{"form.$uname:$udom:$partid:submitted_by"}) {
1.151     albertel 2290: 		    if (exists($seenparts{$partid})) { next; }
                   2291: 		    $seenparts{$partid}=1;
1.596.2.12.2.  8(raebur 2292:3):                     $request->print(
                   2293:3):                         '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
                   2294:3):                         ' <b>'.&mt('Collaborative submission by: [_1]',
                   2295:3):                                    '<a href="javascript:viewSubmitter(\''.
                   2296:3):                                    $env{"form.$uname:$udom:$partid:submitted_by"}.
                   2297:3):                                    '\');" target="_self">'.
                   2298:3):                                    $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a>').
                   2299:3):                         '<br />');
1.151     albertel 2300: 		    next;
                   2301: 		}
                   2302: 		my $responsetype = $responseType->{$partid}->{$respid};
                   2303: 		if (!exists($record{"resource.$partid.$respid.submission"})) {
1.577     bisitz   2304:                     $lastsubonly.="\n".'<div class="LC_grade_submission_part">'.
                   2305:                         '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
                   2306:                         ' <span class="LC_internal_info">'.
1.596.2.4  raeburn  2307:                         '('.&mt('Response ID: [_1]',$respid).')'.
1.577     bisitz   2308:                         '</span>&nbsp; &nbsp;'.
1.539     riegler  2309: 			'<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br /><br /></div>';
1.151     albertel 2310: 		    next;
                   2311: 		}
1.468     albertel 2312: 		foreach my $submission (@$string) {
                   2313: 		    my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
1.375     albertel 2314: 		    if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
1.596.2.12.2.  0(raebur 2315:4): 		    my ($ressub,$hide,$draft,$subval) = split(/:/,$submission,4);
1.151     albertel 2316: 		    # Similarity check
                   2317: 		    my $similar='';
1.596.2.2  raeburn  2318:                     my ($type,$trial,$rndseed);
                   2319:                     if ($hide eq 'rand') {
                   2320:                         $type = 'randomizetry';
                   2321:                         $trial = $record{"resource.$partid.tries"};
                   2322:                         $rndseed = $record{"resource.$partid.rndseed"};
                   2323:                     }
1.596.2.12.2.  1(raebur 2324:3): 		    if ($env{'form.checkPlag'}) {
1.151     albertel 2325: 			my ($oname,$odom,$ocrsid,$oessay,$osim)=
1.596.2.12.2.  (raeburn 2326:): 			    &most_similar($uname,$udom,$symb,$subval);
1.151     albertel 2327: 			if ($osim) {
                   2328: 			    $osim=int($osim*100.0);
1.426     albertel 2329: 			    my %old_course_desc = 
                   2330: 				&Apache::lonnet::coursedescription($ocrsid,
                   2331: 								   {'one_time' => 1});
                   2332: 
1.596.2.2  raeburn  2333:                             if ($hide eq 'anon') {
1.596     raeburn  2334:                                 $similar='<hr /><span class="LC_warning">'.&mt("Essay was found to be similar to another essay submitted for this assignment.").'<br />'.
                   2335:                                          &mt('As the current submission is for an anonymous survey, no other details are available.').'</span><hr />';
                   2336:                             } else {
                   2337: 			        $similar="<hr /><h3><span class=\"LC_warning\">".
                   2338: 				    &mt('Essay is [_1]% similar to an essay by [_2] in course [_3] (course id [_4]:[_5])',
                   2339: 				        $osim,
                   2340: 				        &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')',
                   2341: 				        $old_course_desc{'description'},
                   2342: 				        $old_course_desc{'num'},
                   2343: 				        $old_course_desc{'domain'}).
                   2344: 				    '</span></h3><blockquote><i>'.
                   2345: 				    &keywords_highlight($oessay).
                   2346: 				    '</i></blockquote><hr />';
                   2347:                             }
1.151     albertel 2348: 			}
1.150     albertel 2349: 		    }
1.596.2.2  raeburn  2350: 		    my $order=&get_order($partid,$respid,$symb,$uname,$udom,
                   2351:                                          undef,$type,$trial,$rndseed);
1.596.2.12.2.  1(raebur 2352:3):                     if ($env{'form.lastSub'} eq 'lastonly' || $env{'form.lastSub'} eq 'datesub' || $env{'form.lastSub'} =~ /^(last|all)$/ || ($env{'form.lastSub'} eq 'hdgrade' &&
                   2353:3):                          $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {
1.324     albertel 2354: 			my $display_part=&get_display_part($partid,$symb);
1.577     bisitz   2355:                         $lastsubonly.='<div class="LC_grade_submission_part">'.
                   2356:                             '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
                   2357:                             ' <span class="LC_internal_info">'.
1.596.2.4  raeburn  2358:                             '('.&mt('Response ID: [_1]',$respid).')'.
                   2359:                             '</span>&nbsp; &nbsp;';
1.313     banghart 2360: 			my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
                   2361: 			if (@$files) {
1.596.2.2  raeburn  2362:                             if ($hide eq 'anon') {
1.596     raeburn  2363:                                 $lastsubonly.='<br />'.&mt('[quant,_1,file] uploaded to this anonymous survey',scalar(@{$files}));
                   2364:                             } else {
1.596.2.12.2.  8(raebur 2365:3):                                 $lastsubonly.='<br /><br />'.'<b>'.&mt('Submitted Files:').'</b>'
                   2366:3):                                             .'<br /><span class="LC_warning">';
                   2367:3):                                 if(@$files == 1) {
                   2368:3):                                     $lastsubonly .= &mt('Like all files provided by users, this file may contain viruses!');
                   2369:3):                                 } else {
                   2370:3):                                     $lastsubonly .= &mt('Like all files provided by users, these files may contain viruses!');
                   2371:3):                                 }
                   2372:3):                                 $lastsubonly .= '</span>';
                   2373:3): 
1.596     raeburn  2374:                                 foreach my $file (@$files) {
                   2375:                                     &Apache::lonnet::allowuploaded('/adm/grades',$file);
1.596.2.12.2.  8(raebur 2376:3):                                     $lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border="0" alt="" /> '.$file.'</a>';
1.596     raeburn  2377:                                 }
                   2378:                             }
1.236     albertel 2379: 			    $lastsubonly.='<br />';
1.41      ng       2380: 			}
1.596.2.2  raeburn  2381:                         if ($hide eq 'anon') {
1.596.2.12.2.  8(raebur 2382:3):                             $lastsubonly.='<br /><b>'.&mt('Anonymous Survey').'</b>'; 
1.596     raeburn  2383:                         } else {
1.596.2.12.2.  0(raebur 2384:4): 			    $lastsubonly.='<br /><b>'.&mt('Submitted Answer:').' </b>';
                   2385:4):                             if ($draft) {
                   2386:4):                                 $lastsubonly.= ' <span class="LC_warning">'.&mt('Draft Copy').'</span>';
                   2387:4):                             }
                   2388:4):                             $subval =
1.596     raeburn  2389: 			        &cleanRecord($subval,$responsetype,$symb,$partid,
1.596.2.2  raeburn  2390: 					     $respid,\%record,$order,undef,$uname,$udom,$type,$trial,$rndseed);
1.596.2.12.2.  0(raebur 2391:4):                             if ($responsetype eq 'essay') {
                   2392:4):                                 $subval =~ s{\n}{<br />}g;
                   2393:4):                             }
                   2394:4):                             $lastsubonly.=$subval."\n";
1.596     raeburn  2395:                         }
1.151     albertel 2396: 			if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
1.468     albertel 2397: 			$lastsubonly.='</div>';
1.41      ng       2398: 		    }
                   2399: 		}
                   2400: 	    }
1.588     bisitz   2401: 	    $lastsubonly.='</div>'."\n"; # End: LC_grade_submissions_body
1.151     albertel 2402: 	}
                   2403: 	$request->print($lastsubonly);
1.596.2.12.2.  1(raebur 2404:3):    if ($env{'form.lastSub'} eq 'datesub') {
1.324     albertel 2405: 	my (undef,$responseType,undef,$parts) = &showResourceInfo($symb);
1.148     albertel 2406: 	$request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));
1.596.2.12.2.  1(raebur 2407:3):     }
                   2408:3):     if ($env{'form.lastSub'} =~ /^(last|all)$/) {
                   2409:5):         my $identifier = (&canmodify($usec)? $counter : '');
1.41      ng       2410: 	$request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,
1.257     albertel 2411: 								 $env{'request.course.id'},
1.44      ng       2412: 								 $last,'.submission',
1.596.2.12.2.  1(raebur 2413:5): 								 'Apache::grades::keywords_highlight',
                   2414:5):                                                                  $usec,$identifier));
1.41      ng       2415:     }
1.120     ng       2416: 
1.121     ng       2417:     $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'
                   2418: 	.$udom.'" />'."\n");
1.44      ng       2419:     # return if view submission with no grading option
1.257     albertel 2420:     if ($env{'form.showgrading'} eq '' || (!&canmodify($usec))) {
1.120     ng       2421: 	my $toGrade.='<input type="button" value="Grade Student" '.
1.589     bisitz   2422: 	    'onclick="javascript:checksubmit(this.form,\'Grade Student\',\''
1.417     albertel 2423: 	    .$counter.'\');" target="_self" /> &nbsp;'."\n" if (&canmodify($usec));
1.468     albertel 2424: 	$toGrade.='</div>'."\n";
1.257     albertel 2425: 	if (($env{'form.command'} eq 'submission') || 
                   2426: 	    ($env{'form.command'} eq 'processGroup' && $counter == $total)) {
1.324     albertel 2427: 	    $toGrade.='</form>'.&show_grading_menu_form($symb); 
1.169     albertel 2428: 	}
1.180     albertel 2429: 	$request->print($toGrade);
1.41      ng       2430: 	return;
1.180     albertel 2431:     } else {
1.468     albertel 2432: 	$request->print('</div>'."\n");
1.41      ng       2433:     }
1.33      ng       2434: 
1.121     ng       2435:     # essay grading message center
1.257     albertel 2436:     if ($env{'form.handgrade'} eq 'yes') {
1.468     albertel 2437: 	my $result='<div class="LC_grade_message_center">';
                   2438:     
                   2439: 	$result.='<div class="LC_grade_message_center_header">'.
                   2440: 	    &mt('Send Message').'</div><div class="LC_grade_message_center_body">';
1.257     albertel 2441: 	my ($lastname,$givenn) = split(/,/,$env{'form.fullname'});
1.118     ng       2442: 	my $msgfor = $givenn.' '.$lastname;
1.464     albertel 2443: 	if (scalar(@$col_fullnames) > 0) {
                   2444: 	    my $lastone = pop(@$col_fullnames);
                   2445: 	    $msgfor .= ', '.(join ', ',@$col_fullnames).' and '.$lastone.'.';
1.118     ng       2446: 	}
                   2447: 	$msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript
1.468     albertel 2448: 	$result.='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".
1.121     ng       2449: 	    '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";
                   2450: 	$result.='&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.
1.417     albertel 2451: 	    ',\''.$msgfor.'\');" target="_self">'.
1.596.2.12.2.  8(raebur 2452:3): 	    &mt('Compose message to student'.(scalar(@$col_fullnames) >= 1 ? 's' : '')).'</a><label> ('.
1.350     albertel 2453: 	    &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'.
1.596.2.12.2.  8(raebur 2454:3): 	    ' <img src="'.$request->dir_config('lonIconsURL').
1.118     ng       2455: 	    '/mailbkgrd.gif" width="14" height="10" name="mailicon'.$counter.'" />'."\n".
1.298     www      2456: 	    '<br />&nbsp;('.
1.468     albertel 2457: 	    &mt('Message will be sent when you click on Save &amp; Next below.').")\n";
                   2458: 	$result.='</div></div>';
1.121     ng       2459: 	$request->print($result);
1.118     ng       2460:     }
1.41      ng       2461: 
                   2462:     my %seen = ();
                   2463:     my @partlist;
1.129     ng       2464:     my @gradePartRespid;
1.375     albertel 2465:     my @part_response_id = &flatten_responseType($responseType);
1.585     bisitz   2466:     $request->print(
1.588     bisitz   2467:         '<div class="LC_Box">'
                   2468:        .'<h3 class="LC_hcell">'.&mt('Assign Grades').'</h3>'
1.585     bisitz   2469:     );
1.592     bisitz   2470:     $request->print(&gradeBox_start());
1.375     albertel 2471:     foreach my $part_response_id (@part_response_id) {
                   2472:     	my ($partid,$respid) = @{ $part_response_id };
                   2473: 	my $part_resp = join('_',@{ $part_response_id });
1.322     albertel 2474: 	next if ($seen{$partid} > 0);
1.41      ng       2475: 	$seen{$partid}++;
1.393     albertel 2476: 	next if ($$handgrade{$part_resp} ne 'yes' 
                   2477: 		 && $env{'form.lastSub'} eq 'hdgrade');
1.524     raeburn  2478: 	push(@partlist,$partid);
                   2479: 	push(@gradePartRespid,$partid.'.'.$respid);
1.322     albertel 2480: 	$request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
1.41      ng       2481:     }
1.585     bisitz   2482:     $request->print(&gradeBox_end()); # </div>
                   2483:     $request->print('</div>');
1.468     albertel 2484: 
                   2485:     $request->print('<div class="LC_grade_info_links">');
                   2486:     $request->print('</div>');
                   2487: 
1.45      ng       2488:     $result='<input type="hidden" name="partlist'.$counter.
                   2489: 	'" value="'.(join ":",@partlist).'" />'."\n";
1.129     ng       2490:     $result.='<input type="hidden" name="gradePartRespid'.
                   2491: 	'" value="'.(join ":",@gradePartRespid).'" />'."\n" if ($counter == 0);
1.45      ng       2492:     my $ctr = 0;
                   2493:     while ($ctr < scalar(@partlist)) {
                   2494: 	$result.='<input type="hidden" name="partid'.$counter.'_'.$ctr.'" value="'.
                   2495: 	    $partlist[$ctr].'" />'."\n";
                   2496: 	$ctr++;
                   2497:     }
1.468     albertel 2498:     $request->print($result.''."\n");
1.41      ng       2499: 
1.441     www      2500: # Done with printing info for one student
                   2501: 
1.468     albertel 2502:     $request->print('</div>');#LC_grade_show_user
1.441     www      2503: 
                   2504: 
1.41      ng       2505:     # print end of form
                   2506:     if ($counter == $total) {
1.592     bisitz   2507:         my $endform='<br /><hr /><table border="0"><tr><td>'."\n";
1.485     albertel 2508: 	$endform.='<input type="button" value="'.&mt('Save &amp; Next').'" '.
1.589     bisitz   2509: 	    'onclick="javascript:checksubmit(this.form,\'Save & Next\','.
1.417     albertel 2510: 	    $total.','.scalar(@partlist).');" target="_self" /> &nbsp;'."\n";
1.119     ng       2511: 	my $ntstu ='<select name="NTSTU">'.
                   2512: 	    '<option>1</option><option>2</option>'.
                   2513: 	    '<option>3</option><option>5</option>'.
                   2514: 	    '<option>7</option><option>10</option></select>'."\n";
1.257     albertel 2515: 	my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1');
1.401     albertel 2516: 	$ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</;
1.578     raeburn  2517:         $endform.=&mt('[_1]student(s)',$ntstu);
1.485     albertel 2518: 	$endform.='&nbsp;&nbsp;<input type="button" value="'.&mt('Previous').'" '.
1.589     bisitz   2519: 	    'onclick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> &nbsp;'."\n".
1.485     albertel 2520: 	    '<input type="button" value="'.&mt('Next').'" '.
1.589     bisitz   2521: 	    'onclick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> &nbsp;';
1.592     bisitz   2522:         $endform.='<span class="LC_warning">'.
                   2523:                   &mt('(Next and Previous (student) do not save the scores.)').
                   2524:                   '</span>'."\n" ;
1.349     albertel 2525:         $endform.="<input type='hidden' value='".&get_increment().
1.348     bowersj2 2526:             "' name='increment' />";
1.485     albertel 2527: 	$endform.='</td></tr></table></form>';
1.324     albertel 2528: 	$endform.=&show_grading_menu_form($symb);
1.41      ng       2529: 	$request->print($endform);
                   2530:     }
                   2531:     return '';
1.38      ng       2532: }
                   2533: 
1.464     albertel 2534: sub check_collaborators {
                   2535:     my ($symb,$uname,$udom,$record,$handgrade,$counter) = @_;
                   2536:     my ($result,@col_fullnames);
                   2537:     my ($classlist,undef,$fullname) = &getclasslist('all','0');
                   2538:     foreach my $part (keys(%$handgrade)) {
                   2539: 	my $ncol = &Apache::lonnet::EXT('resource.'.$part.
                   2540: 					'.maxcollaborators',
                   2541: 					$symb,$udom,$uname);
                   2542: 	next if ($ncol <= 0);
                   2543: 	$part =~ s/\_/\./g;
                   2544: 	next if ($record->{'resource.'.$part.'.collaborators'} eq '');
                   2545: 	my (@good_collaborators, @bad_collaborators);
                   2546: 	foreach my $possible_collaborator
1.596.2.4  raeburn  2547: 	    (split(/[,;\s]+/,$record->{'resource.'.$part.'.collaborators'})) { 
1.464     albertel 2548: 	    $possible_collaborator =~ s/[\$\^\(\)]//g;
                   2549: 	    next if ($possible_collaborator eq '');
1.596.2.8  raeburn  2550: 	    my ($co_name,$co_dom) = split(/:/,$possible_collaborator);
1.464     albertel 2551: 	    $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);
                   2552: 	    next if ($co_name eq $uname && $co_dom eq $udom);
                   2553: 	    # Doing this grep allows 'fuzzy' specification
                   2554: 	    my @matches = grep(/^\Q$co_name\E:\Q$co_dom\E$/i, 
                   2555: 			       keys(%$classlist));
                   2556: 	    if (! scalar(@matches)) {
                   2557: 		push(@bad_collaborators, $possible_collaborator);
                   2558: 	    } else {
                   2559: 		push(@good_collaborators, @matches);
                   2560: 	    }
                   2561: 	}
                   2562: 	if (scalar(@good_collaborators) != 0) {
1.596.2.8  raeburn  2563: 	    $result.='<br />'.&mt('Collaborators:').'<ol>';
1.464     albertel 2564: 	    foreach my $name (@good_collaborators) {
                   2565: 		my ($lastname,$givenn) = split(/,/,$$fullname{$name});
                   2566: 		push(@col_fullnames, $givenn.' '.$lastname);
1.596.2.4  raeburn  2567: 		$result.='<li>'.$fullname->{$name}.'</li>';
1.464     albertel 2568: 	    }
1.596.2.4  raeburn  2569: 	    $result.='</ol><br />'."\n";
1.466     albertel 2570: 	    my ($part)=split(/\./,$part);
1.464     albertel 2571: 	    $result.='<input type="hidden" name="collaborator'.$counter.
                   2572: 		'" value="'.$part.':'.(join ':',@good_collaborators).'" />'.
                   2573: 		"\n";
                   2574: 	}
                   2575: 	if (scalar(@bad_collaborators) > 0) {
1.466     albertel 2576: 	    $result.='<div class="LC_warning">';
1.464     albertel 2577: 	    $result.=&mt('This student has submitted [quant,_1,invalid collaborator]: [_2]',scalar(@bad_collaborators),join(', ',@bad_collaborators));
                   2578: 	    $result .= '</div>';
                   2579: 	}         
                   2580: 	if (scalar(@bad_collaborators > $ncol)) {
1.466     albertel 2581: 	    $result .= '<div class="LC_warning">';
1.464     albertel 2582: 	    $result .= &mt('This student has submitted too many '.
                   2583: 		'collaborators.  Maximum is [_1].',$ncol);
                   2584: 	    $result .= '</div>';
                   2585: 	}
                   2586:     }
                   2587:     return ($result,$fullname,\@col_fullnames);
                   2588: }
                   2589: 
1.44      ng       2590: #--- Retrieve the last submission for all the parts
1.38      ng       2591: sub get_last_submission {
1.119     ng       2592:     my ($returnhash)=@_;
1.596     raeburn  2593:     my (@string,$timestamp,%lasthidden);
1.119     ng       2594:     if ($$returnhash{'version'}) {
1.46      ng       2595: 	my %lasthash=();
                   2596: 	my ($version);
1.119     ng       2597: 	for ($version=1;$version<=$$returnhash{'version'};$version++) {
1.397     albertel 2598: 	    foreach my $key (sort(split(/\:/,
                   2599: 					$$returnhash{$version.':keys'}))) {
                   2600: 		$lasthash{$key}=$$returnhash{$version.':'.$key};
                   2601: 		$timestamp = 
1.545     raeburn  2602: 		    &Apache::lonlocal::locallocaltime($$returnhash{$version.':timestamp'});
1.46      ng       2603: 	    }
                   2604: 	}
1.596.2.2  raeburn  2605:         my (%typeparts,%randombytry);
1.596     raeburn  2606:         my $showsurv = 
                   2607:             &Apache::lonnet::allowed('vas',$env{'request.course.id'});
                   2608:         foreach my $key (sort(keys(%lasthash))) {
                   2609:             if ($key =~ /\.type$/) {
                   2610:                 if (($lasthash{$key} eq 'anonsurvey') || 
1.596.2.2  raeburn  2611:                     ($lasthash{$key} eq 'anonsurveycred') ||
                   2612:                     ($lasthash{$key} eq 'randomizetry')) {
1.596     raeburn  2613:                     my ($ign,@parts) = split(/\./,$key);
                   2614:                     pop(@parts);
1.596.2.3  raeburn  2615:                     my $id = join('.',@parts);
1.596.2.2  raeburn  2616:                     if ($lasthash{$key} eq 'randomizetry') {
                   2617:                         $randombytry{$ign.'.'.$id} = $lasthash{$key};
                   2618:                     } else {
                   2619:                         unless ($showsurv) {
                   2620:                             $typeparts{$ign.'.'.$id} = $lasthash{$key};
                   2621:                         }
1.596     raeburn  2622:                     }
                   2623:                     delete($lasthash{$key});
                   2624:                 }
                   2625:             }
                   2626:         }
                   2627:         my @hidden = keys(%typeparts);
1.596.2.2  raeburn  2628:         my @randomize = keys(%randombytry);
1.397     albertel 2629: 	foreach my $key (keys(%lasthash)) {
                   2630: 	    next if ($key !~ /\.submission$/);
1.596     raeburn  2631:             my $hide;
                   2632:             if (@hidden) {
                   2633:                 foreach my $id (@hidden) {
                   2634:                     if ($key =~ /^\Q$id\E/) {
1.596.2.2  raeburn  2635:                         $hide = 'anon';
1.596     raeburn  2636:                         last;
                   2637:                     }
                   2638:                 }
                   2639:             }
1.596.2.2  raeburn  2640:             unless ($hide) {
                   2641:                 if (@randomize) {
1.596.2.12.2.  3(raebur 2642:5):                     foreach my $id (@randomize) {
1.596.2.2  raeburn  2643:                         if ($key =~ /^\Q$id\E/) {
                   2644:                             $hide = 'rand';
                   2645:                             last;
                   2646:                         }
                   2647:                     }
                   2648:                 }
                   2649:             }
1.397     albertel 2650: 	    my ($partid,$foo) = split(/submission$/,$key);
1.596.2.12.2.  0(raebur 2651:4): 	    my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ? 1: 0;
                   2652:4):             push(@string, join(':', $key, $hide, $draft, (
          8(raebur 2653:4):                 ref($lasthash{$key}) eq 'ARRAY' ?
                   2654:4):                     join(',', @{$lasthash{$key}}) : $lasthash{$key}) ));
1.41      ng       2655: 	}
                   2656:     }
1.397     albertel 2657:     if (!@string) {
                   2658: 	$string[0] =
1.539     riegler  2659: 	    '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span>';
1.397     albertel 2660:     }
                   2661:     return (\@string,\$timestamp);
1.38      ng       2662: }
1.35      ng       2663: 
1.44      ng       2664: #--- High light keywords, with style choosen by user.
1.38      ng       2665: sub keywords_highlight {
1.44      ng       2666:     my $string    = shift;
1.257     albertel 2667:     my $size      = $env{'form.kwsize'} eq '0' ? '' : 'size='.$env{'form.kwsize'};
                   2668:     my $styleon   = $env{'form.kwstyle'} eq ''  ? '' : $env{'form.kwstyle'};
1.41      ng       2669:     (my $styleoff = $styleon) =~ s/\</\<\//;
1.257     albertel 2670:     my @keylist   = split(/[,\s+]/,$env{'form.keywords'});
1.398     albertel 2671:     foreach my $keyword (@keylist) {
                   2672: 	$string =~ s/\b\Q$keyword\E(\b|\.)/<font color\=$env{'form.kwclr'} $size\>$styleon$keyword$styleoff<\/font>/gi;
1.41      ng       2673:     }
                   2674:     return $string;
1.38      ng       2675: }
1.36      ng       2676: 
1.596.2.12.2.  (raeburn 2677:): # For Tasks provide a mechanism to display previous version for one specific student
                   2678:): 
                   2679:): sub show_previous_task_version {
                   2680:):     my ($request,$symb) = @_;
                   2681:):     if ($symb eq '') {
          8(raebur 2682:4):         $request->print(
                   2683:4):             '<span class="LC_error">'.
                   2684:4):             &mt('Unable to handle ambiguous references.').
                   2685:4):             '</span>');
          (raeburn 2686:):         return '';
                   2687:):     }
                   2688:):     my ($uname,$udom) = ($env{'form.student'},$env{'form.userdom'});
                   2689:):     my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
                   2690:):     if (!&canview($usec)) {
          8(raebur 2691:4):         $request->print('<span class="LC_warning">'.
                   2692:4):                         &mt('Unable to view previous version for requested student.').
                   2693:4):                         ' '.&mt('([_1] in section [_2] in course id [_3])',
          9(raebur 2694:4):                                 $uname.':'.$udom,$usec,$env{'request.course.id'}).
          8(raebur 2695:4):                         '</span>');
          (raeburn 2696:):         return;
                   2697:):     }
                   2698:):     my $mode = 'both';
                   2699:):     my $isTask = ($symb =~/\.task$/);
                   2700:):     if ($isTask) {
                   2701:):         if ($env{'form.previousversion'} =~ /^\d+$/) {
                   2702:):             if ($env{'form.fullname'} eq '') {
                   2703:):                 $env{'form.fullname'} =
                   2704:):                     &Apache::loncommon::plainname($uname,$udom,'lastname');
                   2705:):             }
                   2706:):             my $probtitle=&Apache::lonnet::gettitle($symb);
                   2707:):             $request->print("\n\n".
                   2708:):                             '<div class="LC_grade_show_user">'.
                   2709:):                             '<h2>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
                   2710:):                             '</h2>'."\n");
                   2711:):             &Apache::lonxml::clear_problem_counter();
                   2712:):             $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode,
                   2713:):                             {'previousversion' => $env{'form.previousversion'} }));
                   2714:):             $request->print("\n</div>");
                   2715:):         }
                   2716:):     }
                   2717:):     return;
                   2718:): }
                   2719:): 
                   2720:): sub choose_task_version_form {
                   2721:):     my ($symb,$uname,$udom,$nomenu) = @_;
                   2722:):     my $isTask = ($symb =~/\.task$/);
                   2723:):     my ($current,$version,$result,$js,$displayed,$rowtitle);
                   2724:):     if ($isTask) {
                   2725:):         my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
                   2726:):                                               $udom,$uname);
                   2727:):         if (($record{'resource.0.version'} eq '') ||
                   2728:):             ($record{'resource.0.version'} < 2)) {
                   2729:):             return ($record{'resource.0.version'},
                   2730:):                     $record{'resource.0.version'},$result,$js);
                   2731:):         } else {
                   2732:):             $current = $record{'resource.0.version'};
                   2733:):         }
                   2734:):         if ($env{'form.previousversion'}) {
                   2735:):             $displayed = $env{'form.previousversion'};
                   2736:):             $rowtitle = &mt('Choose another version:')
                   2737:):         } else {
                   2738:):             $displayed = $current;
                   2739:):             $rowtitle = &mt('Show earlier version:');
                   2740:):         }
                   2741:):         $result = '<div class="LC_left_float">';
                   2742:):         my $list;
                   2743:):         my $numversions = 0;
                   2744:):         for (my $i=1; $i<=$record{'resource.0.version'}; $i++) {
                   2745:):             if ($i == $current) {
                   2746:):                 if (!$env{'form.previousversion'} || $nomenu) {
                   2747:):                     next;
                   2748:):                 } else {
                   2749:):                     $list .= '<option value="'.$i.'">'.&mt('Current').'</option>'."\n";
                   2750:):                     $numversions ++;
                   2751:):                 }
                   2752:):             } elsif (defined($record{'resource.'.$i.'.0.status'})) {
                   2753:):                 unless ($i == $env{'form.previousversion'}) {
                   2754:):                     $numversions ++;
                   2755:):                 }
                   2756:):                 $list .= '<option value="'.$i.'">'.$i.'</option>'."\n";
                   2757:):             }
                   2758:):         }
                   2759:):         if ($numversions) {
                   2760:):             $symb = &HTML::Entities::encode($symb,'<>"&');
                   2761:):             $result .=
                   2762:):                 '<form name="getprev" method="post" action=""'.
                   2763:):                 ' onsubmit="return previousVersion('."'$uname','$udom','$symb','$displayed'".');">'.
                   2764:):                 &Apache::loncommon::start_data_table().
                   2765:):                 &Apache::loncommon::start_data_table_row().
                   2766:):                 '<th align="left">'.$rowtitle.'</th>'.
                   2767:):                 '<td><select name="version">'.
                   2768:):                 '<option>'.&mt('Select').'</option>'.
                   2769:):                 $list.
                   2770:):                 '</select></td>'.
                   2771:):                 &Apache::loncommon::end_data_table_row();
                   2772:):             unless ($nomenu) {
                   2773:):                 $result .= &Apache::loncommon::start_data_table_row().
                   2774:):                 '<th align="left">'.&mt('Open in new window').'</th>'.
                   2775:):                 '<td><span class="LC_nobreak">'.
                   2776:):                 '<label><input type="radio" name="prevwin" value="1" />'.
                   2777:):                 &mt('Yes').'</label>'.
                   2778:):                 '<label><input type="radio" name="prevwin" value="0" checked="checked" />'.&mt('No').'</label>'.
                   2779:):                 '</span></td>'.
                   2780:):                 &Apache::loncommon::end_data_table_row();
                   2781:):             }
                   2782:):             $result .=
                   2783:):                 &Apache::loncommon::start_data_table_row().
                   2784:):                 '<th align="left">&nbsp;</th>'.
                   2785:):                 '<td>'.
                   2786:):                 '<input type="submit" name="prevsub" value="'.&mt('Display').'" />'.
                   2787:):                 '</td>'.
                   2788:):                 &Apache::loncommon::end_data_table_row().
                   2789:):                 &Apache::loncommon::end_data_table().
                   2790:):                 '</form>';
                   2791:):             $js = &previous_display_javascript($nomenu,$current);
                   2792:):         } elsif ($displayed && $nomenu) {
                   2793:):             $result .= '<a href="javascript:window.close()">'.&mt('Close window').'</a>';
                   2794:):         } else {
                   2795:):             $result .= &mt('No previous versions to show for this student');
                   2796:):         }
                   2797:):         $result .= '</div>';
                   2798:):     }
                   2799:):     return ($current,$displayed,$result,$js);
                   2800:): }
                   2801:): 
                   2802:): sub previous_display_javascript {
                   2803:):     my ($nomenu,$current) = @_;
                   2804:):     my $js = <<"JSONE";
                   2805:): <script type="text/javascript">
                   2806:): // <![CDATA[
                   2807:): function previousVersion(uname,udom,symb) {
                   2808:):     var current = '$current';
                   2809:):     var version = document.getprev.version.options[document.getprev.version.selectedIndex].value;
                   2810:):     var prevstr = new RegExp("^\\\\d+\$");
                   2811:):     if (!prevstr.test(version)) {
                   2812:):         return false;
                   2813:):     }
                   2814:):     var url = '';
                   2815:):     if (version == current) {
                   2816:):         url = '/adm/grades?student='+uname+'&userdom='+udom+'&symb='+symb+'&command=submission';
                   2817:):     } else {
                   2818:):         url = '/adm/grades?student='+uname+'&userdom='+udom+'&symb='+symb+'&command=versionsub&previousversion='+version;
                   2819:):     }
                   2820:): JSONE
                   2821:):     if ($nomenu) {
                   2822:):         $js .= <<"JSTWO";
                   2823:):     document.location.href = url;
                   2824:): JSTWO
                   2825:):     } else {
                   2826:):         $js .= <<"JSTHREE";
                   2827:):     var newwin = 0;
                   2828:):     for (var i=0; i<document.getprev.prevwin.length; i++) {
                   2829:):         if (document.getprev.prevwin[i].checked == true) {
                   2830:):             newwin = document.getprev.prevwin[i].value;
                   2831:):         }
                   2832:):     }
                   2833:):     if (newwin == 1) {
                   2834:):         var options = 'height=600,width=800,resizable=yes,scrollbars=yes,location=no,menubar=no,toolbar=no';
                   2835:):         url = url+'&inhibitmenu=yes';
                   2836:):         if (typeof(previousWin) == 'undefined' || previousWin.closed) {
                   2837:):             previousWin = window.open(url,'',options,1);
                   2838:):         } else {
                   2839:):             previousWin.location.href = url;
                   2840:):         }
                   2841:):         previousWin.focus();
                   2842:):         return false;
                   2843:):     } else {
                   2844:):         document.location.href = url;
                   2845:):         return false;
                   2846:):     }
                   2847:): JSTHREE
                   2848:):     }
                   2849:):     $js .= <<"ENDJS";
                   2850:):     return false;
                   2851:): }
                   2852:): // ]]>
                   2853:): </script>
                   2854:): ENDJS
                   2855:): 
                   2856:): }
                   2857:): 
1.44      ng       2858: #--- Called from submission routine
1.38      ng       2859: sub processHandGrade {
1.41      ng       2860:     my ($request) = shift;
1.596.2.12.2.  (raeburn 2861:):     my ($symb)   = &get_symb($request);
1.324     albertel 2862:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
1.257     albertel 2863:     my $button = $env{'form.gradeOpt'};
                   2864:     my $ngrade = $env{'form.NCT'};
                   2865:     my $ntstu  = $env{'form.NTSTU'};
1.301     albertel 2866:     my $cdom   = $env{'course.'.$env{'request.course.id'}.'.domain'};
                   2867:     my $cnum   = $env{'course.'.$env{'request.course.id'}.'.num'};
                   2868: 
1.44      ng       2869:     if ($button eq 'Save & Next') {
                   2870: 	my $ctr = 0;
                   2871: 	while ($ctr < $ngrade) {
1.257     albertel 2872: 	    my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr});
1.596.2.12.2.  1(raebur 2873:5): 	    my ($errorflag,$pts,$wgt,$numhidden) = 
                   2874:5):                 &saveHandGrade($request,$symb,$uname,$udom,$ctr);
1.71      ng       2875: 	    if ($errorflag eq 'no_score') {
                   2876: 		$ctr++;
                   2877: 		next;
                   2878: 	    }
1.104     albertel 2879: 	    if ($errorflag eq 'not_allowed') {
1.596.2.12.2.  8(raebur 2880:4):                 $request->print(
                   2881:4):                     '<span class="LC_error">'
                   2882:4):                    .&mt('Not allowed to modify grades for [_1]',"$uname:$udom")
                   2883:4):                    .'</span>');
1.104     albertel 2884: 		$ctr++;
                   2885: 		next;
                   2886: 	    }
1.596.2.12.2.  1(raebur 2887:5):             if ($numhidden) {
                   2888:5):                 $request->print(
                   2889:5):                     '<span class="LC_info">'
                   2890:5):                    .&mt('For [_1]: [quant,_2,transaction] hidden',"$uname:$udom",$numhidden)
                   2891:5):                    .'</span><br />');
                   2892:5):             }
1.257     albertel 2893: 	    my $includemsg = $env{'form.includemsg'.$ctr};
1.44      ng       2894: 	    my ($subject,$message,$msgstatus) = ('','','');
1.418     albertel 2895: 	    my $restitle = &Apache::lonnet::gettitle($symb);
                   2896:             my ($feedurl,$showsymb) =
                   2897: 		&get_feedurl_and_symb($symb,$uname,$udom);
                   2898: 	    my $messagetail;
1.62      albertel 2899: 	    if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) {
1.298     www      2900: 		$subject = $env{'form.msgsub'} if ($includemsg =~ /msgsub/);
1.295     www      2901: 		unless ($subject=~/\w/) { $subject=&mt('Grading Feedback'); }
1.386     raeburn  2902: 		$subject.=' ['.$restitle.']';
1.44      ng       2903: 		my (@msgnum) = split(/,/,$includemsg);
                   2904: 		foreach (@msgnum) {
1.257     albertel 2905: 		    $message.=$env{'form.'.$_} if ($_ =~ /savemsg|newmsg/ && $_ ne '');
1.44      ng       2906: 		}
1.80      ng       2907: 		$message =&Apache::lonfeedback::clear_out_html($message);
1.298     www      2908: 		if ($env{'form.withgrades'.$ctr}) {
                   2909: 		    $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;
1.386     raeburn  2910: 		    $messagetail = " for <a href=\"".
1.418     albertel 2911: 		                   $feedurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
1.386     raeburn  2912: 		}
                   2913: 		$msgstatus = 
                   2914:                     &Apache::lonmsg::user_normal_msg($uname,$udom,$subject,
                   2915: 						     $message.$messagetail,
1.418     albertel 2916:                                                      undef,$feedurl,undef,
1.386     raeburn  2917:                                                      undef,undef,$showsymb,
                   2918:                                                      $restitle);
1.574     bisitz   2919: 		$request->print('<br />'.&mt('Sending message to [_1]',$uname.':'.$udom).': '.
1.596.2.4  raeburn  2920: 				$msgstatus.'<br />');
1.44      ng       2921: 	    }
1.257     albertel 2922: 	    if ($env{'form.collaborator'.$ctr}) {
1.155     albertel 2923: 		my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr");
1.150     albertel 2924: 		foreach my $collabstr (@collabstrs) {
                   2925: 		    my ($part,@collaborators) = split(/:/,$collabstr);
1.310     banghart 2926: 		    foreach my $collaborator (@collaborators) {
1.150     albertel 2927: 			my ($errorflag,$pts,$wgt) = 
1.324     albertel 2928: 			    &saveHandGrade($request,$symb,$collaborator,$udom,$ctr,
1.257     albertel 2929: 					   $env{'form.unamedom'.$ctr},$part);
1.150     albertel 2930: 			if ($errorflag eq 'not_allowed') {
1.362     albertel 2931: 			    $request->print("<span class=\"LC_error\">".&mt('Not allowed to modify grades for [_1]',"$collaborator:$udom")."</span>");
1.150     albertel 2932: 			    next;
1.418     albertel 2933: 			} elsif ($message ne '') {
                   2934: 			    my ($baseurl,$showsymb) = 
                   2935: 				&get_feedurl_and_symb($symb,$collaborator,
                   2936: 						      $udom);
                   2937: 			    if ($env{'form.withgrades'.$ctr}) {
                   2938: 				$messagetail = " for <a href=\"".
1.386     raeburn  2939:                                     $baseurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
1.150     albertel 2940: 			    }
1.418     albertel 2941: 			    $msgstatus = 
                   2942: 				&Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
1.104     albertel 2943: 			}
1.44      ng       2944: 		    }
                   2945: 		}
                   2946: 	    }
                   2947: 	    $ctr++;
                   2948: 	}
                   2949:     }
                   2950: 
1.257     albertel 2951:     if ($env{'form.handgrade'} eq 'yes') {
1.119     ng       2952: 	# Keywords sorted in alphabatical order
1.257     albertel 2953: 	my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
1.119     ng       2954: 	my %keyhash = ();
1.257     albertel 2955: 	$env{'form.keywords'}           =~ s/,\s{0,}|\s+/ /g;
                   2956: 	$env{'form.keywords'}           =~ s/^\s+|\s+$//;
                   2957: 	my (@keywords) = sort(split(/\s+/,$env{'form.keywords'}));
                   2958: 	$env{'form.keywords'} = join(' ',@keywords);
                   2959: 	$keyhash{$symb.'_keywords'}     = $env{'form.keywords'};
                   2960: 	$keyhash{$symb.'_subject'}      = $env{'form.msgsub'};
                   2961: 	$keyhash{$loginuser.'_kwclr'}   = $env{'form.kwclr'};
                   2962: 	$keyhash{$loginuser.'_kwsize'}  = $env{'form.kwsize'};
                   2963: 	$keyhash{$loginuser.'_kwstyle'} = $env{'form.kwstyle'};
1.119     ng       2964: 
                   2965: 	# message center - Order of message gets changed. Blank line is eliminated.
1.257     albertel 2966: 	# New messages are saved in env for the next student.
1.119     ng       2967: 	# All messages are saved in nohist_handgrade.db
                   2968: 	my ($ctr,$idx) = (1,1);
1.257     albertel 2969: 	while ($ctr <= $env{'form.savemsgN'}) {
                   2970: 	    if ($env{'form.savemsg'.$ctr} ne '') {
                   2971: 		$keyhash{$symb.'_savemsg'.$idx} = $env{'form.savemsg'.$ctr};
1.119     ng       2972: 		$idx++;
                   2973: 	    }
                   2974: 	    $ctr++;
1.41      ng       2975: 	}
1.119     ng       2976: 	$ctr = 0;
                   2977: 	while ($ctr < $ngrade) {
1.257     albertel 2978: 	    if ($env{'form.newmsg'.$ctr} ne '') {
                   2979: 		$keyhash{$symb.'_savemsg'.$idx} = $env{'form.newmsg'.$ctr};
                   2980: 		$env{'form.savemsg'.$idx} = $env{'form.newmsg'.$ctr};
1.119     ng       2981: 		$idx++;
                   2982: 	    }
                   2983: 	    $ctr++;
1.41      ng       2984: 	}
1.257     albertel 2985: 	$env{'form.savemsgN'} = --$idx;
                   2986: 	$keyhash{$symb.'_savemsgN'} = $env{'form.savemsgN'};
1.119     ng       2987: 	my $putresult = &Apache::lonnet::put
1.301     albertel 2988: 	    ('nohist_handgrade',\%keyhash,$cdom,$cnum);
1.41      ng       2989:     }
1.44      ng       2990:     # Called by Save & Refresh from Highlight Attribute Window
1.257     albertel 2991:     my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
                   2992:     if ($env{'form.refresh'} eq 'on') {
1.86      ng       2993: 	my ($ctr,$total) = (0,0);
                   2994: 	while ($ctr < $ngrade) {
1.257     albertel 2995: 	    $total++ if  $env{'form.unamedom'.$ctr} ne '';
1.86      ng       2996: 	    $ctr++;
                   2997: 	}
1.257     albertel 2998: 	$env{'form.NTSTU'}=$ngrade;
1.86      ng       2999: 	$ctr = 0;
                   3000: 	while ($ctr < $total) {
1.257     albertel 3001: 	    my $processUser = $env{'form.unamedom'.$ctr};
                   3002: 	    ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
                   3003: 	    $env{'form.fullname'} = $$fullname{$processUser};
1.86      ng       3004: 	    &submission($request,$ctr,$total-1);
1.41      ng       3005: 	    $ctr++;
                   3006: 	}
                   3007: 	return '';
                   3008:     }
1.36      ng       3009: 
1.121     ng       3010: # Go directly to grade student - from submission or link from chart page
1.120     ng       3011:     if ($button eq 'Grade Student') {
1.324     albertel 3012: 	(undef,undef,$env{'form.handgrade'},undef,undef) = &showResourceInfo($symb);
1.257     albertel 3013: 	my $processUser = $env{'form.unamedom'.$env{'form.studentNo'}};
                   3014: 	($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
                   3015: 	$env{'form.fullname'} = $$fullname{$processUser};
1.120     ng       3016: 	&submission($request,0,0);
                   3017: 	return '';
                   3018:     }
                   3019: 
1.44      ng       3020:     # Get the next/previous one or group of students
1.257     albertel 3021:     my $firststu = $env{'form.unamedom0'};
                   3022:     my $laststu = $env{'form.unamedom'.($ngrade-1)};
1.119     ng       3023:     my $ctr = 2;
1.41      ng       3024:     while ($laststu eq '') {
1.257     albertel 3025: 	$laststu  = $env{'form.unamedom'.($ngrade-$ctr)};
1.41      ng       3026: 	$ctr++;
                   3027: 	$laststu = $firststu if ($ctr > $ngrade);
                   3028:     }
1.44      ng       3029: 
1.41      ng       3030:     my (@parsedlist,@nextlist);
                   3031:     my ($nextflg) = 0;
1.524     raeburn  3032:     foreach my $item (sort 
1.294     albertel 3033: 	     {
                   3034: 		 if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
                   3035: 		     return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
                   3036: 		 }
                   3037: 		 return $a cmp $b;
                   3038: 	     } (keys(%$fullname))) {
1.41      ng       3039: 	if ($nextflg == 1 && $button =~ /Next$/) {
1.524     raeburn  3040: 	    push(@parsedlist,$item);
1.41      ng       3041: 	}
1.524     raeburn  3042: 	$nextflg = 1 if ($item eq $laststu);
1.41      ng       3043: 	if ($button eq 'Previous') {
1.524     raeburn  3044: 	    last if ($item eq $firststu);
                   3045: 	    push(@parsedlist,$item);
1.41      ng       3046: 	}
                   3047:     }
                   3048:     $ctr = 0;
                   3049:     @parsedlist = reverse @parsedlist if ($button eq 'Previous');
1.582     raeburn  3050:     my $res_error;
                   3051:     my ($partlist) = &response_type($symb,\$res_error);
                   3052:     if ($res_error) {
                   3053:         $request->print(&navmap_errormsg());
                   3054:         return;
                   3055:     }
1.41      ng       3056:     foreach my $student (@parsedlist) {
1.257     albertel 3057: 	my $submitonly=$env{'form.submitonly'};
1.41      ng       3058: 	my ($uname,$udom) = split(/:/,$student);
1.301     albertel 3059: 	
                   3060: 	if ($submitonly eq 'queued') {
                   3061: 	    my %queue_status = 
                   3062: 		&Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
                   3063: 							$udom,$uname);
                   3064: 	    next if (!defined($queue_status{'gradingqueue'}));
                   3065: 	}
                   3066: 
1.156     albertel 3067: 	if ($submitonly =~ /^(yes|graded|incorrect)$/) {
1.257     albertel 3068: #	    my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
1.324     albertel 3069: 	    my %status=&student_gradeStatus($symb,$udom,$uname,$partlist);
1.145     albertel 3070: 	    my $submitted = 0;
1.248     albertel 3071: 	    my $ungraded = 0;
                   3072: 	    my $incorrect = 0;
1.524     raeburn  3073: 	    foreach my $item (keys(%status)) {
                   3074: 		$submitted = 1 if ($status{$item} ne 'nothing');
                   3075: 		$ungraded = 1 if ($status{$item} =~ /^ungraded/);
                   3076: 		$incorrect = 1 if ($status{$item} =~ /^incorrect/);
                   3077: 		my ($foo,$partid,$foo1) = split(/\./,$item);
1.145     albertel 3078: 		if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
                   3079: 		    $submitted = 0;
                   3080: 		}
1.41      ng       3081: 	    }
1.156     albertel 3082: 	    next if (!$submitted && ($submitonly eq 'yes' ||
                   3083: 				     $submitonly eq 'incorrect' ||
                   3084: 				     $submitonly eq 'graded'));
1.248     albertel 3085: 	    next if (!$ungraded && ($submitonly eq 'graded'));
                   3086: 	    next if (!$incorrect && $submitonly eq 'incorrect');
1.41      ng       3087: 	}
1.524     raeburn  3088: 	push(@nextlist,$student) if ($ctr < $ntstu);
1.129     ng       3089: 	last if ($ctr == $ntstu);
1.41      ng       3090: 	$ctr++;
                   3091:     }
1.36      ng       3092: 
1.41      ng       3093:     $ctr = 0;
                   3094:     my $total = scalar(@nextlist)-1;
1.39      ng       3095: 
1.524     raeburn  3096:     foreach (sort(@nextlist)) {
1.41      ng       3097: 	my ($uname,$udom,$submitter) = split(/:/);
1.257     albertel 3098: 	$env{'form.student'}  = $uname;
                   3099: 	$env{'form.userdom'}  = $udom;
                   3100: 	$env{'form.fullname'} = $$fullname{$_};
1.41      ng       3101: 	&submission($request,$ctr,$total);
                   3102: 	$ctr++;
                   3103:     }
                   3104:     if ($total < 0) {
1.485     albertel 3105: 	my $the_end = '<h3><span class="LC_info">'.&mt('LON-CAPA User Message').'</span></h3><br />'."\n";
1.596.2.4  raeburn  3106: 	$the_end.='<p>'.&mt('[_1]Message:[_2] No more students for this section or class.','<b>','</b>').'</p>'."\n";
1.485     albertel 3107: 	$the_end.=&mt('Click on the button below to return to the grading menu.').'<br /><br />'."\n";
1.324     albertel 3108: 	$the_end.=&show_grading_menu_form($symb);
1.41      ng       3109: 	$request->print($the_end);
                   3110:     }
                   3111:     return '';
1.38      ng       3112: }
1.36      ng       3113: 
1.44      ng       3114: #---- Save the score and award for each student, if changed
1.38      ng       3115: sub saveHandGrade {
1.324     albertel 3116:     my ($request,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_;
1.342     banghart 3117:     my @version_parts;
1.104     albertel 3118:     my $usec = &Apache::lonnet::getsection($domain,$stuname,
1.257     albertel 3119: 					   $env{'request.course.id'});
1.104     albertel 3120:     if (!&canmodify($usec)) { return('not_allowed'); }
1.337     banghart 3121:     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname);
1.251     banghart 3122:     my @parts_graded;
1.77      ng       3123:     my %newrecord  = ();
1.596.2.12.2.  1(raebur 3124:5):     my ($pts,$wgt,$totchg) = ('','',0);
1.269     raeburn  3125:     my %aggregate = ();
                   3126:     my $aggregateflag = 0;
1.596.2.12.2.  1(raebur 3127:5):     if ($env{'form.HIDE'.$newflg}) {
                   3128:5):         my ($version,$parts) = split(/:/,$env{'form.HIDE'.$newflg},2);
                   3129:5):         my $numchgs = &makehidden($version,$parts,\%record,$symb,$domain,$stuname,1);
                   3130:5):         $totchg += $numchgs;
                   3131:5):     }
1.301     albertel 3132:     my @parts = split(/:/,$env{'form.partlist'.$newflg});
                   3133:     foreach my $new_part (@parts) {
1.337     banghart 3134: 	#collaborator ($submi may vary for different parts
1.259     banghart 3135: 	if ($submitter && $new_part ne $part) { next; }
                   3136: 	my $dropMenu = $env{'form.GD_SEL'.$newflg.'_'.$new_part};
1.125     ng       3137: 	if ($dropMenu eq 'excused') {
1.259     banghart 3138: 	    if ($record{'resource.'.$new_part.'.solved'} ne 'excused') {
                   3139: 		$newrecord{'resource.'.$new_part.'.solved'} = 'excused';
                   3140: 		if (exists($record{'resource.'.$new_part.'.awarded'})) {
                   3141: 		    $newrecord{'resource.'.$new_part.'.awarded'} = '';
1.58      albertel 3142: 		}
1.364     banghart 3143: 	        $newrecord{'resource.'.$new_part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
1.58      albertel 3144: 	    }
1.125     ng       3145: 	} elsif ($dropMenu eq 'reset status'
1.259     banghart 3146: 		 && exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts
1.524     raeburn  3147: 	    foreach my $key (keys(%record)) {
1.259     banghart 3148: 		if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; }
1.197     albertel 3149: 	    }
1.259     banghart 3150: 	    $newrecord{'resource.'.$new_part.'.regrader'}=
1.257     albertel 3151: 		"$env{'user.name'}:$env{'user.domain'}";
1.270     albertel 3152:             my $totaltries = $record{'resource.'.$part.'.tries'};
                   3153: 
                   3154:             my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
                   3155: 					       [$new_part]);
                   3156:             my $aggtries =$totaltries;
1.269     raeburn  3157:             if ($last_resets{$new_part}) {
1.270     albertel 3158:                 $aggtries = &get_num_tries(\%record,$last_resets{$new_part},
                   3159: 					   $new_part);
1.269     raeburn  3160:             }
1.270     albertel 3161: 
                   3162:             my $solvedstatus = $record{'resource.'.$new_part.'.solved'};
1.269     raeburn  3163:             if ($aggtries > 0) {
1.327     albertel 3164:                 &decrement_aggs($symb,$new_part,\%aggregate,$aggtries,$totaltries,$solvedstatus);
1.269     raeburn  3165:                 $aggregateflag = 1;
                   3166:             }
1.125     ng       3167: 	} elsif ($dropMenu eq '') {
1.259     banghart 3168: 	    $pts = ($env{'form.GD_BOX'.$newflg.'_'.$new_part} ne '' ? 
                   3169: 		    $env{'form.GD_BOX'.$newflg.'_'.$new_part} : 
                   3170: 		    $env{'form.RADVAL'.$newflg.'_'.$new_part});
                   3171: 	    if ($pts eq '' && $env{'form.GD_SEL'.$newflg.'_'.$new_part} eq '') {
1.153     albertel 3172: 		next;
                   3173: 	    }
1.259     banghart 3174: 	    $wgt = $env{'form.WGT'.$newflg.'_'.$new_part} eq '' ? 1 : 
                   3175: 		$env{'form.WGT'.$newflg.'_'.$new_part};
1.41      ng       3176: 	    my $partial= $pts/$wgt;
1.259     banghart 3177: 	    if ($partial eq $record{'resource.'.$new_part.'.awarded'}) {
1.153     albertel 3178: 		#do not update score for part if not changed.
1.346     banghart 3179:                 &handback_files($request,$symb,$stuname,$domain,$newflg,$new_part,\%newrecord);
1.153     albertel 3180: 		next;
1.251     banghart 3181: 	    } else {
1.524     raeburn  3182: 	        push(@parts_graded,$new_part);
1.153     albertel 3183: 	    }
1.259     banghart 3184: 	    if ($record{'resource.'.$new_part.'.awarded'} ne $partial) {
                   3185: 		$newrecord{'resource.'.$new_part.'.awarded'}  = $partial;
1.153     albertel 3186: 	    }
1.259     banghart 3187: 	    my $reckey = 'resource.'.$new_part.'.solved';
1.41      ng       3188: 	    if ($partial == 0) {
1.153     albertel 3189: 		if ($record{$reckey} ne 'incorrect_by_override') {
                   3190: 		    $newrecord{$reckey} = 'incorrect_by_override';
                   3191: 		}
1.41      ng       3192: 	    } else {
1.153     albertel 3193: 		if ($record{$reckey} ne 'correct_by_override') {
                   3194: 		    $newrecord{$reckey} = 'correct_by_override';
                   3195: 		}
                   3196: 	    }	    
                   3197: 	    if ($submitter && 
1.259     banghart 3198: 		($record{'resource.'.$new_part.'.submitted_by'} ne $submitter)) {
                   3199: 		$newrecord{'resource.'.$new_part.'.submitted_by'} = $submitter;
1.41      ng       3200: 	    }
1.259     banghart 3201: 	    $newrecord{'resource.'.$new_part.'.regrader'}=
1.257     albertel 3202: 		"$env{'user.name'}:$env{'user.domain'}";
1.41      ng       3203: 	}
1.259     banghart 3204: 	# unless problem has been graded, set flag to version the submitted files
1.305     banghart 3205: 	unless ($record{'resource.'.$new_part.'.solved'} =~ /^correct_/  || 
                   3206: 	        $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override' ||
                   3207: 	        $dropMenu eq 'reset status')
                   3208: 	   {
1.524     raeburn  3209: 	    push(@version_parts,$new_part);
1.259     banghart 3210: 	}
1.41      ng       3211:     }
1.301     albertel 3212:     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
                   3213:     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
                   3214: 
1.344     albertel 3215:     if (%newrecord) {
                   3216:         if (@version_parts) {
1.364     banghart 3217:             my @changed_keys = &version_portfiles(\%record, \@parts_graded, 
                   3218:                                 $env{'request.course.id'}, $symb, $domain, $stuname, \@version_parts);
1.344     albertel 3219: 	    @newrecord{@changed_keys} = @record{@changed_keys};
1.367     albertel 3220: 	    foreach my $new_part (@version_parts) {
                   3221: 		&handback_files($request,$symb,$stuname,$domain,$newflg,
                   3222: 				$new_part,\%newrecord);
                   3223: 	    }
1.259     banghart 3224:         }
1.44      ng       3225: 	&Apache::lonnet::cstore(\%newrecord,$symb,
1.257     albertel 3226: 				$env{'request.course.id'},$domain,$stuname);
1.380     albertel 3227: 	&check_and_remove_from_queue(\@parts,\%record,\%newrecord,$symb,
                   3228: 				     $cdom,$cnum,$domain,$stuname);
1.41      ng       3229:     }
1.269     raeburn  3230:     if ($aggregateflag) {
                   3231:         &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
1.301     albertel 3232: 			      $cdom,$cnum);
1.269     raeburn  3233:     }
1.596.2.12.2.  1(raebur 3234:5):     return ('',$pts,$wgt,$totchg);
                   3235:5): }
                   3236:5): 
                   3237:5): sub makehidden {
                   3238:5):     my ($version,$parts,$record,$symb,$domain,$stuname,$tolog) = @_;
                   3239:5):     return unless (ref($record) eq 'HASH');
                   3240:5):     my %modified;
                   3241:5):     my $numchanged = 0;
                   3242:5):     if (exists($record->{$version.':keys'})) {
                   3243:5):         my $partsregexp = $parts;
                   3244:5):         $partsregexp =~ s/,/|/g;
                   3245:5):         foreach my $key (split(/\:/,$record->{$version.':keys'})) {
                   3246:5):             if ($key =~ /^resource\.(?:$partsregexp)\.([^\.]+)$/) {
                   3247:5):                  my $item = $1;
                   3248:5):                  unless (($item eq 'solved') || ($item =~ /^award(|msg|ed)$/)) {
                   3249:5):                      $modified{$key} = $record->{$version.':'.$key};
                   3250:5):                  }
                   3251:5):             } elsif ($key =~ m{^(resource\.(?:$partsregexp)\.[^\.]+\.)(.+)$}) {
                   3252:5):                 $modified{$1.'hidden'.$2} = $record->{$version.':'.$key};
                   3253:5):             } elsif ($key =~ /^(ip|timestamp|host)$/) {
                   3254:5):                 $modified{$key} = $record->{$version.':'.$key};
                   3255:5):             }
                   3256:5):         }
                   3257:5):         if (keys(%modified)) {
                   3258:5):             if (&Apache::lonnet::putstore($env{'request.course.id'},$symb,$version,\%modified,
                   3259:5):                                           $domain,$stuname,$tolog) eq 'ok') {
                   3260:5):                 $numchanged ++;
                   3261:5):             }
                   3262:5):         }
                   3263:5):     }
                   3264:5):     return $numchanged;
1.36      ng       3265: }
1.322     albertel 3266: 
1.380     albertel 3267: sub check_and_remove_from_queue {
                   3268:     my ($parts,$record,$newrecord,$symb,$cdom,$cnum,$domain,$stuname) = @_;
                   3269:     my @ungraded_parts;
                   3270:     foreach my $part (@{$parts}) {
                   3271: 	if (    $record->{   'resource.'.$part.'.awarded'} eq ''
                   3272: 	     && $record->{   'resource.'.$part.'.solved' } ne 'excused'
                   3273: 	     && $newrecord->{'resource.'.$part.'.awarded'} eq ''
                   3274: 	     && $newrecord->{'resource.'.$part.'.solved' } ne 'excused'
                   3275: 		) {
                   3276: 	    push(@ungraded_parts, $part);
                   3277: 	}
                   3278:     }
                   3279:     if ( !@ungraded_parts ) {
                   3280: 	&Apache::bridgetask::remove_from_queue('gradingqueue',$symb,$cdom,
                   3281: 					       $cnum,$domain,$stuname);
                   3282:     }
                   3283: }
                   3284: 
1.337     banghart 3285: sub handback_files {
                   3286:     my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;
1.517     raeburn  3287:     my $portfolio_root = '/userfiles/portfolio';
1.582     raeburn  3288:     my $res_error;
                   3289:     my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
                   3290:     if ($res_error) {
                   3291:         $request->print('<br />'.&navmap_errormsg().'<br />');
                   3292:         return;
                   3293:     }
1.596.2.4  raeburn  3294:     my @handedback;
                   3295:     my $file_msg;
1.375     albertel 3296:     my @part_response_id = &flatten_responseType($responseType);
                   3297:     foreach my $part_response_id (@part_response_id) {
                   3298:     	my ($part_id,$resp_id) = @{ $part_response_id };
                   3299: 	my $part_resp = join('_',@{ $part_response_id });
1.596.2.4  raeburn  3300:         if (($env{'form.'.$newflg.'_'.$part_resp.'_countreturndoc'} =~ /^\d+$/) & ($new_part eq $part_id)) {
                   3301:             for (my $counter=1; $counter<=$env{'form.'.$newflg.'_'.$part_resp.'_countreturndoc'}; $counter++) {
1.337     banghart 3302:                 # if multiple files are uploaded names will be 'returndoc2','returndoc3'
1.596.2.4  raeburn  3303: 		if ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$counter}) {
                   3304:                     my $fname=$env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$counter.'.filename'};
1.338     banghart 3305:                     my ($directory,$answer_file) = 
1.596.2.4  raeburn  3306:                         ($env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$counter} =~ /^(.*?)([^\/]*)$/);
1.338     banghart 3307:                     my ($answer_name,$answer_ver,$answer_ext) =
                   3308: 		        &file_name_version_ext($answer_file);
1.355     banghart 3309: 		    my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/);
1.517     raeburn  3310:                     my $getpropath = 1;
1.596.2.12.2.  (raeburn 3311:):                     my ($dir_list,$listerror) =
                   3312:):                         &Apache::lonnet::dirlist($portfolio_root.$portfolio_path,
                   3313:):                                                  $domain,$stuname,$getpropath);
                   3314:): 		    my $version = &get_next_version($answer_name,$answer_ext,$dir_list);
          3(raebur 3315:3):                     # fix filename
1.355     banghart 3316:                     my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);
                   3317:                     my $result=&Apache::lonnet::finishuserfileupload($stuname,$domain,
1.596.2.4  raeburn  3318:             	                                $newflg.'_'.$part_resp.'_returndoc'.$counter,
1.355     banghart 3319:             	                                $save_file_name);
1.337     banghart 3320:                     if ($result !~ m|^/uploaded/|) {
1.536     raeburn  3321:                         $request->print('<br /><span class="LC_error">'.
                   3322:                             &mt('An error occurred ([_1]) while trying to upload [_2].',
1.596.2.4  raeburn  3323:                                 $result,$newflg.'_'.$part_resp.'_returndoc'.$counter).
1.536     raeburn  3324:                                         '</span>');
1.356     banghart 3325:                     } else {
1.360     banghart 3326:                         # mark the file as read only
1.596.2.4  raeburn  3327:                         push(@handedback,$save_file_name);
1.367     albertel 3328: 			if (exists($$newrecord{"resource.$new_part.$resp_id.handback"})) {
                   3329: 			    $$newrecord{"resource.$new_part.$resp_id.handback"}.=',';
                   3330: 			}
                   3331:                         $$newrecord{"resource.$new_part.$resp_id.handback"} .= $save_file_name;
1.596.2.4  raeburn  3332: 			$file_msg.='<span class="LC_filename"><a href="/uploaded/'."$domain/$stuname/".$save_file_name.'">'.$save_file_name."</a></span> <br />";
1.367     albertel 3333: 
1.337     banghart 3334:                     }
1.596.2.12.2.  3(raebur 3335:3):                     $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>'));
1.337     banghart 3336:                 }
                   3337:             }
                   3338:         }
1.596.2.4  raeburn  3339:     }
                   3340:     if (@handedback > 0) {
                   3341:         $request->print('<br />');
                   3342:         my @what = ($symb,$env{'request.course.id'},'handback');
                   3343:         &Apache::lonnet::mark_as_readonly($domain,$stuname,\@handedback,\@what);
                   3344:         my $user_lh = &Apache::loncommon::user_lang($stuname,$domain,$env{'request.course.id'});
                   3345:         my ($subject,$message);
                   3346:         if (scalar(@handedback) == 1) {
                   3347:             $subject = &mt_user($user_lh,'File Handed Back by Instructor');
                   3348:         } else {
                   3349:             $subject = &mt_user($user_lh,'Files Handed Back by Instructor');
                   3350:             $message = &mt_user($user_lh,'Files have been returned that were originally submitted in response to: ');
                   3351:         }
                   3352:         $message .= "<p><strong>".&Apache::lonnet::gettitle($symb)." </strong></p>";
                   3353:         $message .= &mt_user($user_lh,'The returned file(s) are named: [_1]',"<br />$file_msg <br />").
                   3354:                     &mt_user($user_lh,'The file(s) can be found in your [_1]portfolio[_2].','<a href="/adm/portfolio">','</a>');
                   3355:         my ($feedurl,$showsymb) =
                   3356:             &get_feedurl_and_symb($symb,$domain,$stuname);
                   3357:         my $restitle = &Apache::lonnet::gettitle($symb);
                   3358:         $subject .= ' '.&mt_user($user_lh,'(File Returned)').' ['.$restitle.']';
                   3359:         my $msgstatus =
                   3360:              &Apache::lonmsg::user_normal_msg($stuname,$domain,$subject,
                   3361:                  $message,undef,$feedurl,undef,undef,undef,$showsymb,
                   3362:                  $restitle);
                   3363:         if ($msgstatus) {
                   3364:             $request->print(&mt('Notification message status: [_1]','<span class="LC_info">'.$msgstatus.'</span>').'<br />');
                   3365:         }
                   3366:     }
1.338     banghart 3367:     return;
1.337     banghart 3368: }
                   3369: 
1.418     albertel 3370: sub get_feedurl_and_symb {
                   3371:     my ($symb,$uname,$udom) = @_;
                   3372:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
                   3373:     $url = &Apache::lonnet::clutter($url);
                   3374:     my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl',
                   3375: 					$symb,$udom,$uname);
                   3376:     if ($encrypturl =~ /^yes$/i) {
                   3377: 	&Apache::lonenc::encrypted(\$url,1);
                   3378: 	&Apache::lonenc::encrypted(\$symb,1);
                   3379:     }
                   3380:     return ($url,$symb);
                   3381: }
                   3382: 
1.313     banghart 3383: sub get_submitted_files {
                   3384:     my ($udom,$uname,$partid,$respid,$record) = @_;
                   3385:     my @files;
                   3386:     if ($$record{"resource.$partid.$respid.portfiles"}) {
                   3387:         my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio';
                   3388:         foreach my $file (split(',',$$record{"resource.$partid.$respid.portfiles"})) {
                   3389:     	    push(@files,$file_url.$file);
                   3390:         }
                   3391:     }
                   3392:     if ($$record{"resource.$partid.$respid.uploadedurl"}) {
                   3393:         push(@files,$$record{"resource.$partid.$respid.uploadedurl"});
                   3394:     }
                   3395:     return (\@files);
                   3396: }
1.322     albertel 3397: 
1.269     raeburn  3398: # ----------- Provides number of tries since last reset.
                   3399: sub get_num_tries {
                   3400:     my ($record,$last_reset,$part) = @_;
                   3401:     my $timestamp = '';
                   3402:     my $num_tries = 0;
                   3403:     if ($$record{'version'}) {
                   3404:         for (my $version=$$record{'version'};$version>=1;$version--) {
                   3405:             if (exists($$record{$version.':resource.'.$part.'.solved'})) {
                   3406:                 $timestamp = $$record{$version.':timestamp'};
                   3407:                 if ($timestamp > $last_reset) {
                   3408:                     $num_tries ++;
                   3409:                 } else {
                   3410:                     last;
                   3411:                 }
                   3412:             }
                   3413:         }
                   3414:     }
                   3415:     return $num_tries;
                   3416: }
                   3417: 
                   3418: # ----------- Determine decrements required in aggregate totals 
                   3419: sub decrement_aggs {
                   3420:     my ($symb,$part,$aggregate,$aggtries,$totaltries,$solvedstatus) = @_;
                   3421:     my %decrement = (
                   3422:                         attempts => 0,
                   3423:                         users => 0,
                   3424:                         correct => 0
                   3425:                     );
                   3426:     $decrement{'attempts'} = $aggtries;
                   3427:     if ($solvedstatus =~ /^correct/) {
                   3428:         $decrement{'correct'} = 1;
                   3429:     }
                   3430:     if ($aggtries == $totaltries) {
                   3431:         $decrement{'users'} = 1;
                   3432:     }
1.524     raeburn  3433:     foreach my $type (keys(%decrement)) {
1.269     raeburn  3434:         $$aggregate{$symb."\0".$part."\0".$type} = -$decrement{$type};
                   3435:     }
                   3436:     return;
                   3437: }
                   3438: 
                   3439: # ----------- Determine timestamps for last reset of aggregate totals for parts  
                   3440: sub get_last_resets {
1.270     albertel 3441:     my ($symb,$courseid,$partids) =@_;
                   3442:     my %last_resets;
1.269     raeburn  3443:     my $cdom = $env{'course.'.$courseid.'.domain'};
                   3444:     my $cname = $env{'course.'.$courseid.'.num'};
1.271     albertel 3445:     my @keys;
                   3446:     foreach my $part (@{$partids}) {
                   3447: 	push(@keys,"$symb\0$part\0resettime");
                   3448:     }
                   3449:     my %results=&Apache::lonnet::get('nohist_resourcetracker',\@keys,
                   3450: 				     $cdom,$cname);
                   3451:     foreach my $part (@{$partids}) {
                   3452: 	$last_resets{$part}=$results{"$symb\0$part\0resettime"};
1.269     raeburn  3453:     }
1.270     albertel 3454:     return %last_resets;
1.269     raeburn  3455: }
                   3456: 
1.251     banghart 3457: # ----------- Handles creating versions for portfolio files as answers
                   3458: sub version_portfiles {
1.343     banghart 3459:     my ($record, $parts_graded, $courseid, $symb, $domain, $stu_name, $v_flag) = @_;
1.263     banghart 3460:     my $version_parts = join('|',@$v_flag);
1.343     banghart 3461:     my @returned_keys;
1.255     banghart 3462:     my $parts = join('|', @$parts_graded);
1.517     raeburn  3463:     my $portfolio_root = '/userfiles/portfolio';
1.277     albertel 3464:     foreach my $key (keys(%$record)) {
1.259     banghart 3465:         my $new_portfiles;
1.263     banghart 3466:         if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
1.342     banghart 3467:             my @versioned_portfiles;
1.367     albertel 3468:             my @portfiles = split(/\s*,\s*/,$$record{$key});
1.252     banghart 3469:             foreach my $file (@portfiles) {
1.306     banghart 3470:                 &Apache::lonnet::unmark_as_readonly($domain,$stu_name,[$symb,$env{'request.course.id'}],$file);
1.304     albertel 3471:                 my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/);
                   3472: 		my ($answer_name,$answer_ver,$answer_ext) =
                   3473: 		    &file_name_version_ext($answer_file);
1.596.2.12.2.  (raeburn 3474:):                 my $getpropath = 1;
                   3475:):                 my ($dir_list,$listerror) =
                   3476:):                     &Apache::lonnet::dirlist($portfolio_root.$directory,$domain,
                   3477:):                                              $stu_name,$getpropath);
                   3478:):                 my $version = &get_next_version($answer_name,$answer_ext,$dir_list);
1.306     banghart 3479:                 my $new_answer = &version_selected_portfile($domain, $stu_name, $directory, $answer_file, $version);
                   3480:                 if ($new_answer ne 'problem getting file') {
1.342     banghart 3481:                     push(@versioned_portfiles, $directory.$new_answer);
1.306     banghart 3482:                     &Apache::lonnet::mark_as_readonly($domain,$stu_name,
1.367     albertel 3483:                         [$directory.$new_answer],
1.306     banghart 3484:                         [$symb,$env{'request.course.id'},'graded']);
1.259     banghart 3485:                 }
1.252     banghart 3486:             }
1.343     banghart 3487:             $$record{$key} = join(',',@versioned_portfiles);
                   3488:             push(@returned_keys,$key);
1.251     banghart 3489:         }
                   3490:     } 
1.343     banghart 3491:     return (@returned_keys);   
1.305     banghart 3492: }
                   3493: 
1.307     banghart 3494: sub get_next_version {
1.341     banghart 3495:     my ($answer_name, $answer_ext, $dir_list) = @_;
1.307     banghart 3496:     my $version;
1.596.2.12.2.  (raeburn 3497:):     if (ref($dir_list) eq 'ARRAY') {
                   3498:):         foreach my $row (@{$dir_list}) {
                   3499:):             my ($file) = split(/\&/,$row,2);
                   3500:):             my ($file_name,$file_version,$file_ext) =
                   3501:): 	        &file_name_version_ext($file);
                   3502:):             if (($file_name eq $answer_name) && 
                   3503:): 	        ($file_ext eq $answer_ext)) {
                   3504:):                 # gets here if filename and extension match, 
                   3505:):                 # regardless of version
1.307     banghart 3506:                 if ($file_version ne '') {
1.596.2.12.2.  (raeburn 3507:):                     # a versioned file is found  so save it for later
                   3508:):                     if ($file_version > $version) {
                   3509:): 		        $version = $file_version;
                   3510:):                     }
1.307     banghart 3511: 	        }
                   3512:             }
                   3513:         }
1.596.2.12.2.  (raeburn 3514:):     }
1.307     banghart 3515:     $version ++;
                   3516:     return($version);
                   3517: }
                   3518: 
1.305     banghart 3519: sub version_selected_portfile {
1.306     banghart 3520:     my ($domain,$stu_name,$directory,$file_name,$version) = @_;
                   3521:     my ($answer_name,$answer_ver,$answer_ext) =
                   3522:         &file_name_version_ext($file_name);
                   3523:     my $new_answer;
                   3524:     $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name");
                   3525:     if($env{'form.copy'} eq '-1') {
                   3526:         $new_answer = 'problem getting file';
                   3527:     } else {
                   3528:         $new_answer = $answer_name.'.'.$version.'.'.$answer_ext;
                   3529:         my $copy_result = &Apache::lonnet::finishuserfileupload(
                   3530:                             $stu_name,$domain,'copy',
                   3531: 		        '/portfolio'.$directory.$new_answer);
                   3532:     }    
                   3533:     return ($new_answer);
1.251     banghart 3534: }
                   3535: 
1.304     albertel 3536: sub file_name_version_ext {
                   3537:     my ($file)=@_;
                   3538:     my @file_parts = split(/\./, $file);
                   3539:     my ($name,$version,$ext);
                   3540:     if (@file_parts > 1) {
                   3541: 	$ext=pop(@file_parts);
                   3542: 	if (@file_parts > 1 && $file_parts[-1] =~ /^\d+$/) {
                   3543: 	    $version=pop(@file_parts);
                   3544: 	}
                   3545: 	$name=join('.',@file_parts);
                   3546:     } else {
                   3547: 	$name=join('.',@file_parts);
                   3548:     }
                   3549:     return($name,$version,$ext);
                   3550: }
                   3551: 
1.44      ng       3552: #--------------------------------------------------------------------------------------
                   3553: #
                   3554: #-------------------------- Next few routines handles grading by section or whole class
                   3555: #
                   3556: #--- Javascript to handle grading by section or whole class
1.42      ng       3557: sub viewgrades_js {
                   3558:     my ($request) = shift;
                   3559: 
1.539     riegler  3560:     my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = ');
1.596.2.12.2.  6(raebur 3561:6):     &js_escape(\$alertmsg);
1.41      ng       3562:     $request->print(<<VIEWJAVASCRIPT);
                   3563: <script type="text/javascript" language="javascript">
1.45      ng       3564:    function writePoint(partid,weight,point) {
1.125     ng       3565: 	var radioButton = document.classgrade["RADVAL_"+partid];
                   3566: 	var textbox = document.classgrade["TEXTVAL_"+partid];
1.42      ng       3567: 	if (point == "textval") {
1.125     ng       3568: 	    point = document.classgrade["TEXTVAL_"+partid].value;
1.109     matthew  3569: 	    if (isNaN(point) || parseFloat(point) < 0) {
1.539     riegler  3570: 		alert("$alertmsg"+parseFloat(point));
1.42      ng       3571: 		var resetbox = false;
                   3572: 		for (var i=0; i<radioButton.length; i++) {
                   3573: 		    if (radioButton[i].checked) {
                   3574: 			textbox.value = i;
                   3575: 			resetbox = true;
                   3576: 		    }
                   3577: 		}
                   3578: 		if (!resetbox) {
                   3579: 		    textbox.value = "";
                   3580: 		}
                   3581: 		return;
                   3582: 	    }
1.109     matthew  3583: 	    if (parseFloat(point) > parseFloat(weight)) {
                   3584: 		var resp = confirm("You entered a value ("+parseFloat(point)+
1.44      ng       3585: 				   ") greater than the weight for the part. Accept?");
                   3586: 		if (resp == false) {
                   3587: 		    textbox.value = "";
                   3588: 		    return;
                   3589: 		}
                   3590: 	    }
1.42      ng       3591: 	    for (var i=0; i<radioButton.length; i++) {
                   3592: 		radioButton[i].checked=false;
1.109     matthew  3593: 		if (parseFloat(point) == i) {
1.42      ng       3594: 		    radioButton[i].checked=true;
                   3595: 		}
                   3596: 	    }
1.41      ng       3597: 
1.42      ng       3598: 	} else {
1.125     ng       3599: 	    textbox.value = parseFloat(point);
1.42      ng       3600: 	}
1.41      ng       3601: 	for (i=0;i<document.classgrade.total.value;i++) {
1.125     ng       3602: 	    var user = document.classgrade["ctr"+i].value;
1.289     albertel 3603: 	    user = user.replace(new RegExp(':', 'g'),"_");
1.125     ng       3604: 	    var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
                   3605: 	    var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
                   3606: 	    var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
1.42      ng       3607: 	    if (saveval != "correct") {
                   3608: 		scorename.value = point;
1.43      ng       3609: 		if (selname[0].selected != true) {
                   3610: 		    selname[0].selected = true;
                   3611: 		}
1.42      ng       3612: 	    }
                   3613: 	}
1.125     ng       3614: 	document.classgrade["SELVAL_"+partid][0].selected = true;
1.42      ng       3615:     }
                   3616: 
                   3617:     function writeRadText(partid,weight) {
1.125     ng       3618: 	var selval   = document.classgrade["SELVAL_"+partid];
                   3619: 	var radioButton = document.classgrade["RADVAL_"+partid];
1.265     www      3620:         var override = document.classgrade["FORCE_"+partid].checked;
1.125     ng       3621: 	var textbox = document.classgrade["TEXTVAL_"+partid];
                   3622: 	if (selval[1].selected || selval[2].selected) {
1.42      ng       3623: 	    for (var i=0; i<radioButton.length; i++) {
                   3624: 		radioButton[i].checked=false;
                   3625: 
                   3626: 	    }
                   3627: 	    textbox.value = "";
                   3628: 
                   3629: 	    for (i=0;i<document.classgrade.total.value;i++) {
1.125     ng       3630: 		var user = document.classgrade["ctr"+i].value;
1.289     albertel 3631: 		user = user.replace(new RegExp(':', 'g'),"_");
1.125     ng       3632: 		var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
                   3633: 		var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
                   3634: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
1.265     www      3635: 		if ((saveval != "correct") || override) {
1.42      ng       3636: 		    scorename.value = "";
1.125     ng       3637: 		    if (selval[1].selected) {
                   3638: 			selname[1].selected = true;
                   3639: 		    } else {
                   3640: 			selname[2].selected = true;
                   3641: 			if (Number(document.classgrade["GD_"+user+"_"+partid+"_tries"].value)) 
                   3642: 			{document.classgrade["GD_"+user+"_"+partid+"_tries"].value = '0';}
                   3643: 		    }
1.42      ng       3644: 		}
                   3645: 	    }
1.43      ng       3646: 	} else {
                   3647: 	    for (i=0;i<document.classgrade.total.value;i++) {
1.125     ng       3648: 		var user = document.classgrade["ctr"+i].value;
1.289     albertel 3649: 		user = user.replace(new RegExp(':', 'g'),"_");
1.125     ng       3650: 		var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
                   3651: 		var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
                   3652: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
1.265     www      3653: 		if ((saveval != "correct") || override) {
1.125     ng       3654: 		    scorename.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
1.43      ng       3655: 		    selname[0].selected = true;
                   3656: 		}
                   3657: 	    }
                   3658: 	}	    
1.42      ng       3659:     }
                   3660: 
                   3661:     function changeSelect(partid,user) {
1.125     ng       3662: 	var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
                   3663: 	var textbox = document.classgrade["GD_"+user+'_'+partid+"_awarded"];
1.44      ng       3664: 	var point  = textbox.value;
1.125     ng       3665: 	var weight = document.classgrade["weight_"+partid].value;
1.44      ng       3666: 
1.109     matthew  3667: 	if (isNaN(point) || parseFloat(point) < 0) {
1.539     riegler  3668: 	    alert("$alertmsg"+parseFloat(point));
1.44      ng       3669: 	    textbox.value = "";
                   3670: 	    return;
                   3671: 	}
1.109     matthew  3672: 	if (parseFloat(point) > parseFloat(weight)) {
                   3673: 	    var resp = confirm("You entered a value ("+parseFloat(point)+
1.44      ng       3674: 			       ") greater than the weight of the part. Accept?");
                   3675: 	    if (resp == false) {
                   3676: 		textbox.value = "";
                   3677: 		return;
                   3678: 	    }
                   3679: 	}
1.42      ng       3680: 	selval[0].selected = true;
                   3681:     }
                   3682: 
                   3683:     function changeOneScore(partid,user) {
1.125     ng       3684: 	var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
                   3685: 	if (selval[1].selected || selval[2].selected) {
                   3686: 	    document.classgrade["GD_"+user+'_'+partid+"_awarded"].value = "";
                   3687: 	    if (selval[2].selected) {
                   3688: 		document.classgrade["GD_"+user+'_'+partid+"_tries"].value = "0";
                   3689: 	    }
1.269     raeburn  3690:         }
1.42      ng       3691:     }
                   3692: 
                   3693:     function resetEntry(numpart) {
                   3694: 	for (ctpart=0;ctpart<numpart;ctpart++) {
1.125     ng       3695: 	    var partid = document.classgrade["partid_"+ctpart].value;
                   3696: 	    var radioButton = document.classgrade["RADVAL_"+partid];
                   3697: 	    var textbox = document.classgrade["TEXTVAL_"+partid];
                   3698: 	    var selval  = document.classgrade["SELVAL_"+partid];
1.42      ng       3699: 	    for (var i=0; i<radioButton.length; i++) {
                   3700: 		radioButton[i].checked=false;
                   3701: 
                   3702: 	    }
                   3703: 	    textbox.value = "";
                   3704: 	    selval[0].selected = true;
                   3705: 
                   3706: 	    for (i=0;i<document.classgrade.total.value;i++) {
1.125     ng       3707: 		var user = document.classgrade["ctr"+i].value;
1.289     albertel 3708: 		user = user.replace(new RegExp(':', 'g'),"_");
1.125     ng       3709: 		var resetscore = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
                   3710: 		resetscore.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
                   3711: 		var resettries = document.classgrade["GD_"+user+"_"+partid+"_tries"];
                   3712: 		resettries.value = document.classgrade["GD_"+user+"_"+partid+"_tries_s"].value;
                   3713: 		var saveselval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
                   3714: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
1.42      ng       3715: 		if (saveselval == "excused") {
1.43      ng       3716: 		    if (selname[1].selected == false) { selname[1].selected = true;}
1.42      ng       3717: 		} else {
1.43      ng       3718: 		    if (selname[0].selected == false) {selname[0].selected = true};
1.42      ng       3719: 		}
                   3720: 	    }
1.41      ng       3721: 	}
1.42      ng       3722:     }
                   3723: 
1.41      ng       3724: </script>
                   3725: VIEWJAVASCRIPT
1.42      ng       3726: }
                   3727: 
1.44      ng       3728: #--- show scores for a section or whole class w/ option to change/update a score
1.42      ng       3729: sub viewgrades {
                   3730:     my ($request) = shift;
                   3731:     &viewgrades_js($request);
1.41      ng       3732: 
1.324     albertel 3733:     my ($symb) = &get_symb($request);
1.168     albertel 3734:     #need to make sure we have the correct data for later EXT calls, 
                   3735:     #thus invalidate the cache
                   3736:     &Apache::lonnet::devalidatecourseresdata(
1.257     albertel 3737:                  $env{'course.'.$env{'request.course.id'}.'.num'},
                   3738:                  $env{'course.'.$env{'request.course.id'}.'.domain'});
1.168     albertel 3739:     &Apache::lonnet::clear_EXT_cache_status();
                   3740: 
1.398     albertel 3741:     my $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>';
1.596.2.12.2.  9(raebur 3742:3):     $result.='<h4><b>'.&mt('Current Resource').':</b> '.$env{'form.probTitle'}.'</h4>'."\n";
1.41      ng       3743: 
                   3744:     #view individual student submission form - called using Javascript viewOneStudent
1.324     albertel 3745:     $result.=&jscriptNform($symb);
1.41      ng       3746: 
1.44      ng       3747:     #beginning of class grading form
1.442     banghart 3748:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
1.41      ng       3749:     $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n".
1.418     albertel 3750: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1.38      ng       3751: 	'<input type="hidden" name="command" value="editgrades" />'."\n".
1.432     banghart 3752: 	&build_section_inputs().
1.257     albertel 3753: 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
1.442     banghart 3754: 	'<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n".
1.257     albertel 3755: 	'<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
1.72      ng       3756: 
1.596.2.12.2.  7(raebur 3757:6):     #retrieve selected groups
                   3758:6):     my (@groups,$group_display);
          8(raebur 3759:6):     @groups = &Apache::loncommon::get_env_multiple('form.group');
          7(raebur 3760:6):     if (grep(/^all$/,@groups)) {
                   3761:6):         @groups = ('all');
                   3762:6):     } elsif (grep(/^none$/,@groups)) {
                   3763:6):         @groups = ('none');
                   3764:6):     } elsif (@groups > 0) {
                   3765:6):         $group_display = join(', ',@groups);
                   3766:6):     }
                   3767:6): 
                   3768:6):     my ($common_header,$specific_header,@sections,$section_display);
                   3769:6):     @sections = &Apache::loncommon::get_env_multiple('form.section');
                   3770:6):     if (grep(/^all$/,@sections)) {
                   3771:6):         @sections = ('all');
                   3772:6):         if ($group_display) {
                   3773:6):             $common_header = &mt('Assign Common Grade to Students in Group(s) [_1]',$group_display);
                   3774:6):             $specific_header = &mt('Assign Grade to Specific Students in Group(s) [_1]',$group_display);
                   3775:6):         } elsif (grep(/^none$/,@groups)) {
                   3776:6):             $common_header = &mt('Assign Common Grade to Students not assigned to any groups');
                   3777:6):             $specific_header = &mt('Assign Grade to Specific Students not assigned to any groups');
                   3778:6):         } else {
                   3779:6):             $common_header = &mt('Assign Common Grade to Class');
                   3780:6):             $specific_header = &mt('Assign Grade to Specific Students in Class');
                   3781:6):         }
                   3782:6):     } elsif (grep(/^none$/,@sections)) {
                   3783:6):         @sections = ('none');
                   3784:6):         if ($group_display) {
                   3785:6):             $common_header = &mt('Assign Common Grade to Students in no Section and in Group(s) [_1]',$group_display);
                   3786:6):             $specific_header = &mt('Assign Grade to Specific Students in no Section and in Group(s)',$group_display);
                   3787:6):         } elsif (grep(/^none$/,@groups)) {
                   3788:6):             $common_header = &mt('Assign Common Grade to Students in no Section and in no Group');
                   3789:6):             $specific_header = &mt('Assign Grade to Specific Students in no Section and in no Group');
                   3790:6):         } else {
                   3791:6):             $common_header = &mt('Assign Common Grade to Students in no Section');
                   3792:6):             $specific_header = &mt('Assign Grade to Specific Students in no Section');
                   3793:6):         }
                   3794:6):     } else {
                   3795:6):         $section_display = join (", ",@sections);
                   3796:6):         if ($group_display) {
                   3797:6):             $common_header = &mt('Assign Common Grade to Students in Section(s) [_1], and in Group(s) [_2]',
                   3798:6):                                  $section_display,$group_display);
                   3799:6):             $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1], and in Group(s) [_2]',
                   3800:6):                                    $section_display,$group_display);
                   3801:6):         } elsif (grep(/^none$/,@groups)) {
                   3802:6):             $common_header = &mt('Assign Common Grade to Students in Section(s) [_1] and no Group',$section_display);
                   3803:6):             $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1] and no Group',$section_display);
                   3804:6):         } else {
                   3805:6):             $common_header = &mt('Assign Common Grade to Students in Section(s) [_1]',$section_display);
                   3806:6):             $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1]',$section_display);
                   3807:6):         }
1.52      albertel 3808:     }
1.596.2.12.2.  7(raebur 3809:6):     my %submit_types = &substatus_options();
                   3810:6):     my $submission_status = $submit_types{$env{'form.submitonly'}};
                   3811:6): 
                   3812:6):     if ($env{'form.submitonly'} eq 'all') {
                   3813:6):         $result.= '<h3>'.$common_header.'</h3>';
                   3814:6):     } else {
                   3815:6):         $result.= '<h3>'.$common_header.'&nbsp;'.&mt('(submission status: "[_1]")',$submission_status).'</h3>'; 
                   3816:6):     }
                   3817:6):     $result .= &Apache::loncommon::start_data_table();
1.44      ng       3818:     #radio buttons/text box for assigning points for a section or class.
                   3819:     #handles different parts of a problem
1.582     raeburn  3820:     my $res_error;
                   3821:     my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
                   3822:     if ($res_error) {
                   3823:         return &navmap_errormsg();
                   3824:     }
1.42      ng       3825:     my %weight = ();
                   3826:     my $ctsparts = 0;
1.45      ng       3827:     my %seen = ();
1.375     albertel 3828:     my @part_response_id = &flatten_responseType($responseType);
                   3829:     foreach my $part_response_id (@part_response_id) {
                   3830:     	my ($partid,$respid) = @{ $part_response_id };
                   3831: 	my $part_resp = join('_',@{ $part_response_id });
1.45      ng       3832: 	next if $seen{$partid};
                   3833: 	$seen{$partid}++;
1.375     albertel 3834: 	my $handgrade=$$handgrade{$part_resp};
1.42      ng       3835: 	my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);
                   3836: 	$weight{$partid} = $wgt eq '' ? '1' : $wgt;
                   3837: 
1.324     albertel 3838: 	my $display_part=&get_display_part($partid,$symb);
1.485     albertel 3839: 	my $radio.='<table border="0"><tr>';  
1.41      ng       3840: 	my $ctr = 0;
1.42      ng       3841: 	while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across
1.485     albertel 3842: 	    $radio.= '<td><label><input type="radio" name="RADVAL_'.$partid.'" '.
1.54      albertel 3843: 		'onclick="javascript:writePoint(\''.$partid.'\','.$weight{$partid}.
1.288     albertel 3844: 		','.$ctr.')" />'.$ctr."</label></td>\n";
1.41      ng       3845: 	    $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
                   3846: 	    $ctr++;
                   3847: 	}
1.485     albertel 3848: 	$radio.='</tr></table>';
                   3849: 	my $line = '<input type="text" name="TEXTVAL_'.
1.589     bisitz   3850: 	    $partid.'" size="4" '.'onchange="javascript:writePoint(\''.
1.54      albertel 3851: 		$partid.'\','.$weight{$partid}.',\'textval\')" /> /'.
1.539     riegler  3852: 	    $weight{$partid}.' '.&mt('(problem weight)').'</td>'."\n";
1.596.2.12.2.  9(raebur 3853:3): 	$line.= '<td><b>'.&mt('Grade Status').':</b>'.
                   3854:3):                 '<select name="SELVAL_'.$partid.'" '.
                   3855:3): 	        'onchange="javascript:writeRadText(\''.$partid.'\','.
1.59      albertel 3856: 		$weight{$partid}.')"> '.
1.401     albertel 3857: 	    '<option selected="selected"> </option>'.
1.485     albertel 3858: 	    '<option value="excused">'.&mt('excused').'</option>'.
                   3859: 	    '<option value="reset status">'.&mt('reset status').'</option>'.
                   3860: 	    '</select></td>'.
                   3861:             '<td><label><input type="checkbox" name="FORCE_'.$partid.'" />'.&mt('Override "Correct"').'</label>';
                   3862: 	$line.='<input type="hidden" name="partid_'.
                   3863: 	    $ctsparts.'" value="'.$partid.'" />'."\n";
                   3864: 	$line.='<input type="hidden" name="weight_'.
                   3865: 	    $partid.'" value="'.$weight{$partid}.'" />'."\n";
                   3866: 
                   3867: 	$result.=
                   3868: 	    &Apache::loncommon::start_data_table_row()."\n".
1.577     bisitz   3869: 	    '<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>'.
1.485     albertel 3870: 	    &Apache::loncommon::end_data_table_row()."\n";
1.42      ng       3871: 	$ctsparts++;
1.41      ng       3872:     }
1.474     albertel 3873:     $result.=&Apache::loncommon::end_data_table()."\n".
1.52      albertel 3874: 	'<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
1.485     albertel 3875:     $result.='<input type="button" value="'.&mt('Revert to Default').'" '.
1.589     bisitz   3876: 	'onclick="javascript:resetEntry('.$ctsparts.');" />';
1.41      ng       3877: 
1.44      ng       3878:     #table listing all the students in a section/class
                   3879:     #header of table
1.596.2.12.2.  7(raebur 3880:6):     if ($env{'form.submitonly'} eq 'all') { 
                   3881:6):         $result.= '<h3>'.$specific_header.'</h3>';
                   3882:6):     } else {
                   3883:6):         $result.= '<h3>'.$specific_header.'&nbsp;'.&mt('(submission status: "[_1]")',$submission_status).'</h3>';
                   3884:6):     }
                   3885:6):     $result.= &Apache::loncommon::start_data_table().
1.560     raeburn  3886: 	      &Apache::loncommon::start_data_table_header_row().
                   3887: 	      '<th>'.&mt('No.').'</th>'.
                   3888: 	      '<th>'.&nameUserString('header')."</th>\n";
1.582     raeburn  3889:     my $partserror;
                   3890:     my (@parts) = sort(&getpartlist($symb,\$partserror));
                   3891:     if ($partserror) {
                   3892:         return &navmap_errormsg();
                   3893:     }
1.324     albertel 3894:     my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
1.269     raeburn  3895:     my @partids = ();
1.41      ng       3896:     foreach my $part (@parts) {
                   3897: 	my $display=&Apache::lonnet::metadata($url,$part.'.display');
1.539     riegler  3898:         my $narrowtext = &mt('Tries');
                   3899: 	$display =~ s|^Number of Attempts|$narrowtext <br />|; # makes the column narrower
1.41      ng       3900: 	if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }
1.207     albertel 3901: 	my ($partid) = &split_part_type($part);
1.524     raeburn  3902:         push(@partids,$partid);
1.324     albertel 3903: 	my $display_part=&get_display_part($partid,$symb);
1.41      ng       3904: 	if ($display =~ /^Partial Credit Factor/) {
1.485     albertel 3905: 	    $result.='<th>'.
1.596.2.12.2.  8(raebur 3906:3):                 &mt('Score Part: [_1][_2](weight = [_3])',
                   3907:3):                     $display_part,'<br />',$weight{$partid}).'</th>'."\n";
1.41      ng       3908: 	    next;
1.485     albertel 3909: 	    
1.207     albertel 3910: 	} else {
1.485     albertel 3911: 	    if ($display =~ /Problem Status/) {
                   3912: 		my $grade_status_mt = &mt('Grade Status');
                   3913: 		$display =~ s{Problem Status}{$grade_status_mt<br />};
                   3914: 	    }
                   3915: 	    my $part_mt = &mt('Part:');
                   3916: 	    $display =~s{\[Part: \Q$partid\E\]}{$part_mt $display_part};
1.41      ng       3917: 	}
1.485     albertel 3918: 
1.474     albertel 3919: 	$result.='<th>'.$display.'</th>'."\n";
1.41      ng       3920:     }
1.474     albertel 3921:     $result.=&Apache::loncommon::end_data_table_header_row();
1.44      ng       3922: 
1.270     albertel 3923:     my %last_resets = 
                   3924: 	&get_last_resets($symb,$env{'request.course.id'},\@partids);
1.269     raeburn  3925: 
1.41      ng       3926:     #get info for each student
1.44      ng       3927:     #list all the students - with points and grade status
1.596.2.12.2.  7(raebur 3928:6):     my (undef,undef,$fullname) = &getclasslist(\@sections,'1',\@groups);
1.41      ng       3929:     my $ctr = 0;
1.294     albertel 3930:     foreach (sort 
                   3931: 	     {
                   3932: 		 if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
                   3933: 		     return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
                   3934: 		 }
                   3935: 		 return $a cmp $b;
                   3936: 	     } (keys(%$fullname))) {
1.324     albertel 3937: 	$result.=&viewstudentgrade($symb,$env{'request.course.id'},
1.596.2.12.2.  7(raebur 3938:6): 				   $_,$$fullname{$_},\@parts,\%weight,\$ctr,\%last_resets);
1.41      ng       3939:     }
1.474     albertel 3940:     $result.=&Apache::loncommon::end_data_table();
1.41      ng       3941:     $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
1.485     albertel 3942:     $result.='<input type="button" value="'.&mt('Save').'" '.
1.589     bisitz   3943: 	'onclick="javascript:submit();" target="_self" /></form>'."\n";
1.596.2.12.2.  7(raebur 3944:6):     if ($ctr == 0) {
1.442     banghart 3945:         my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status'));
1.596.2.12.2.  7(raebur 3946:6):         $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>'.
                   3947:6):                 '<span class="LC_warning">';
                   3948:6):         if ($env{'form.submitonly'} eq 'all') {
                   3949:6):             if (grep(/^all$/,@sections)) {
                   3950:6):                 if (grep(/^all$/,@groups)) {
                   3951:6):                     $result .= &mt('There are no students with enrollment status [_1] to modify or grade.',
                   3952:6):                                    $stu_status);
                   3953:6):                 } elsif (grep(/^none$/,@groups)) {
                   3954:6):                     $result .= &mt('There are no students with no group assigned and with enrollment status [_1] to modify or grade.',
                   3955:6):                                    $stu_status);
                   3956:6):                 } else {
                   3957:6):                     $result .= &mt('There are no students in group(s) [_1] with enrollment status [_2] to modify or grade.',
                   3958:6):                                    $group_display,$stu_status);
                   3959:6):                 }
                   3960:6):             } elsif (grep(/^none$/,@sections)) {
                   3961:6):                 if (grep(/^all$/,@groups)) {
                   3962:6):                     $result .= &mt('There are no students in no section with enrollment status [_1] to modify or grade.',
                   3963:6):                                    $stu_status);
                   3964:6):                 } elsif (grep(/^none$/,@groups)) {
                   3965:6):                     $result .= &mt('There are no students in no section and no group with enrollment status [_1] to modify or grade.',
                   3966:6):                                    $stu_status);
                   3967:6):                 } else {
                   3968:6):                     $result .= &mt('There are no students in no section in group(s) [_1] with enrollment status [_2] to modify or grade.',
                   3969:6):                                    $group_display,$stu_status);
                   3970:6):                 }
                   3971:6):             } else {
                   3972:6):                 if (grep(/^all$/,@groups)) {
                   3973:6):                     $result .= &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade.',
                   3974:6):                                    $section_display,$stu_status);
                   3975:6):                 } elsif (grep(/^none$/,@groups)) {
                   3976:6):                     $result .= &mt('There are no students in section(s) [_1] nd no group with enrollment status [_2] to modify or grade.',
                   3977:6):                                    $section_display,$stu_status);
                   3978:6):                 } else {
                   3979:6):                     $result .= &mt('There are no students in section(s) [_1] and group(s) [_2] with enrollment status [_3] to modify or grade.',
                   3980:6):                                    $section_display,$group_display,$stu_status);
                   3981:6):                 }
                   3982:6):             }
                   3983:6):         } else {
                   3984:6):             if (grep(/^all$/,@sections)) {
                   3985:6):                 if (grep(/^all$/,@groups)) {
                   3986:6):                     $result .= &mt('There are no students with enrollment status [_1] and submission status "[_2]" to modify or grade.',
                   3987:6):                                    $stu_status,$submission_status);
                   3988:6):                 } elsif (grep(/^none$/,@groups)) {
                   3989:6):                     $result .= &mt('There are no students with no group assigned with enrollment status [_1] and submission status "[_2]" to modify or grade.',
                   3990:6):                                    $stu_status,$submission_status);
                   3991:6):                 } else {
                   3992:6):                     $result .= &mt('There are no students in group(s) [_1] with enrollment status [_2] and submission status "[_3]" to modify or grade.',
                   3993:6):                                    $group_display,$stu_status,$submission_status);
                   3994:6):                 }
                   3995:6):             } elsif (grep(/^none$/,@sections)) {
                   3996:6):                 if (grep(/^all$/,@groups)) {
                   3997:6):                     $result .= &mt('There are no students in no section with enrollment status [_1] and submission status "[_2]" to modify or grade.',
                   3998:6):                                    $stu_status,$submission_status);
                   3999:6):                 } elsif (grep(/^none$/,@groups)) {
                   4000:6):                     $result .= &mt('There are no students in no section and no group with enrollment status [_1] and submission status "[_2]" to modify or grade.',
                   4001:6):                                    $stu_status,$submission_status);
                   4002:6):                 } else {
                   4003:6):                     $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.',
                   4004:6):                                    $group_display,$stu_status,$submission_status);
                   4005:6):                 }
                   4006:6):             } else {
                   4007:6):                 if (grep(/^all$/,@groups)) {
                   4008:6):                     $result .= &mt('There are no students in section(s) [_1] with enrollment status [_2] and submission status "[_3]" to modify or grade.',
                   4009:6):                                    $section_display,$stu_status,$submission_status);
                   4010:6):                 } elsif (grep(/^none$/,@groups)) {
                   4011:6):                     $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.',
                   4012:6):                                    $section_display,$stu_status,$submission_status);
                   4013:6):                 } else {
                   4014:6):                     $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.',
                   4015:6):                                    $section_display,$group_display,$stu_status,$submission_status);
                   4016:6):                 }
                   4017:6):             }
                   4018:6): 	}
                   4019:6): 	$result .= '</span><br />';
1.96      albertel 4020:     }
1.324     albertel 4021:     $result.=&show_grading_menu_form($symb);
1.41      ng       4022:     return $result;
                   4023: }
                   4024: 
1.596.2.12.2.  7(raebur 4025:6): #--- call by previous routine to display each student who satisfies submission filter.
1.41      ng       4026: sub viewstudentgrade {
1.324     albertel 4027:     my ($symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets) = @_;
1.44      ng       4028:     my ($uname,$udom) = split(/:/,$student);
                   4029:     my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
1.596.2.12.2.  7(raebur 4030:6):     my $submitonly = $env{'form.submitonly'};
                   4031:6):     unless (($submitonly eq 'all') || ($submitonly eq 'queued')) {
                   4032:6):         my %partstatus = ();
                   4033:6):         if (ref($parts) eq 'ARRAY') {
                   4034:6):             foreach my $apart (@{$parts}) {
                   4035:6):                 my ($part,$type) = &split_part_type($apart);
                   4036:6):                 my ($status,undef) = split(/_/,$record{"resource.$part.solved"},2);
                   4037:6):                 $status = 'nothing' if ($status eq '');
                   4038:6):                 $partstatus{$part}      = $status;
                   4039:6):                 my $subkey = "resource.$part.submitted_by";
                   4040:6):                 $partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
                   4041:6):             }
                   4042:6):             my $submitted = 0;
                   4043:6):             my $graded = 0;
                   4044:6):             my $incorrect = 0;
                   4045:6):             foreach my $key (keys(%partstatus)) {
                   4046:6):                 $submitted = 1 if ($partstatus{$key} ne 'nothing');
                   4047:6):                 $graded = 1 if ($partstatus{$key} =~ /^ungraded/);
                   4048:6):                 $incorrect = 1 if ($partstatus{$key} =~ /^incorrect/);
                   4049:6): 
                   4050:6):                 my $partid = (split(/\./,$key))[1];
                   4051:6):                 if ($partstatus{'resource.'.$partid.'.'.$key.'.submitted_by'} ne '') {
                   4052:6):                     $submitted = 0;
                   4053:6):                 }
                   4054:6):             }
                   4055:6):             return if (!$submitted && ($submitonly eq 'yes' ||
                   4056:6):                                        $submitonly eq 'incorrect' ||
                   4057:6):                                        $submitonly eq 'graded'));
                   4058:6):             return if (!$graded && ($submitonly eq 'graded'));
                   4059:6):             return if (!$incorrect && $submitonly eq 'incorrect');
                   4060:6):         }
                   4061:6):     }
                   4062:6):     if ($submitonly eq 'queued') {
                   4063:6):         my ($cdom,$cnum) = split(/_/,$courseid);
                   4064:6):         my %queue_status =
                   4065:6):             &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
                   4066:6):                                                     $udom,$uname);
                   4067:6):         return if (!defined($queue_status{'gradingqueue'}));
                   4068:6):     }
                   4069:6):     $$ctr++;
                   4070:6):     my %aggregates = ();
1.474     albertel 4071:     my $result=&Apache::loncommon::start_data_table_row().'<td align="right">'.
1.596.2.12.2.  7(raebur 4072:6): 	'<input type="hidden" name="ctr'.($$ctr-1).'" value="'.$student.'" />'.
                   4073:6): 	"\n".$$ctr.'&nbsp;</td><td>&nbsp;'.
1.44      ng       4074: 	'<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
1.417     albertel 4075: 	'\');" target="_self">'.$fullname.'</a> '.
1.398     albertel 4076: 	'<span class="LC_internal_info">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</span></td>'."\n";
1.281     albertel 4077:     $student=~s/:/_/; # colon doen't work in javascript for names
1.63      albertel 4078:     foreach my $apart (@$parts) {
                   4079: 	my ($part,$type) = &split_part_type($apart);
1.41      ng       4080: 	my $score=$record{"resource.$part.$type"};
1.276     albertel 4081:         $result.='<td align="center">';
1.269     raeburn  4082:         my ($aggtries,$totaltries);
                   4083:         unless (exists($aggregates{$part})) {
1.270     albertel 4084: 	    $totaltries = $record{'resource.'.$part.'.tries'};
                   4085: 
                   4086: 	    $aggtries = $totaltries;
1.269     raeburn  4087:             if ($$last_resets{$part}) {  
1.270     albertel 4088:                 $aggtries = &get_num_tries(\%record,$$last_resets{$part},
                   4089: 					   $part);
                   4090:             }
1.269     raeburn  4091:             $result.='<input type="hidden" name="'.
                   4092:                 'GD_'.$student.'_'.$part.'_aggtries" value="'.$aggtries.'" />'."\n";
                   4093:             $result.='<input type="hidden" name="'.
                   4094:                 'GD_'.$student.'_'.$part.'_totaltries" value="'.$totaltries.'" />'."\n";
                   4095:             $aggregates{$part} = 1;
                   4096:         }
1.41      ng       4097: 	if ($type eq 'awarded') {
1.320     albertel 4098: 	    my $pts = $score eq '' ? '' : &compute_points($score,$$weight{$part});
1.42      ng       4099: 	    $result.='<input type="hidden" name="'.
1.89      albertel 4100: 		'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
1.233     albertel 4101: 	    $result.='<input type="text" name="'.
1.89      albertel 4102: 		'GD_'.$student.'_'.$part.'_awarded" '.
1.589     bisitz   4103:                 'onchange="javascript:changeSelect(\''.$part.'\',\''.$student.
1.44      ng       4104: 		'\')" value="'.$pts.'" size="4" /></td>'."\n";
1.41      ng       4105: 	} elsif ($type eq 'solved') {
                   4106: 	    my ($status,$foo)=split(/_/,$score,2);
                   4107: 	    $status = 'nothing' if ($status eq '');
1.89      albertel 4108: 	    $result.='<input type="hidden" name="'.'GD_'.$student.'_'.
1.54      albertel 4109: 		$part.'_solved_s" value="'.$status.'" />'."\n";
1.233     albertel 4110: 	    $result.='&nbsp;<select name="'.
1.89      albertel 4111: 		'GD_'.$student.'_'.$part.'_solved" '.
1.589     bisitz   4112:                 'onchange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";
1.485     albertel 4113: 	    $result.= (($status eq 'excused') ? '<option> </option><option selected="selected" value="excused">'.&mt('excused').'</option>' 
                   4114: 		: '<option selected="selected"> </option><option value="excused">'.&mt('excused').'</option>')."\n";
                   4115: 	    $result.='<option value="reset status">'.&mt('reset status').'</option>';
1.126     ng       4116: 	    $result.="</select>&nbsp;</td>\n";
1.122     ng       4117: 	} else {
                   4118: 	    $result.='<input type="hidden" name="'.
                   4119: 		'GD_'.$student.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'.
                   4120: 		    "\n";
1.233     albertel 4121: 	    $result.='<input type="text" name="'.
1.122     ng       4122: 		'GD_'.$student.'_'.$part.'_'.$type.'" '.
                   4123: 		'value="'.$score.'" size="4" /></td>'."\n";
1.41      ng       4124: 	}
                   4125:     }
1.474     albertel 4126:     $result.=&Apache::loncommon::end_data_table_row();
1.41      ng       4127:     return $result;
1.38      ng       4128: }
                   4129: 
1.44      ng       4130: #--- change scores for all the students in a section/class
                   4131: #    record does not get update if unchanged
1.38      ng       4132: sub editgrades {
1.41      ng       4133:     my ($request) = @_;
                   4134: 
1.596.2.12.2.  (raeburn 4135:):     my ($symb)=&get_symb($request);
1.433     banghart 4136:     my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
1.477     albertel 4137:     my $title='<h2>'.&mt('Current Grade Status').'</h2>';
1.596.2.12.2.  9(raebur 4138:3):     $title.='<h4><b>'.&mt('Current Resource').':</b> '.$env{'form.probTitle'}.'</h4>'."\n";
                   4139:3):     $title.='<h4><b>'.&mt('Section:').'</b> '.$section_display.'</h4>'."\n";
1.126     ng       4140: 
1.477     albertel 4141:     my $result= &Apache::loncommon::start_data_table().
                   4142: 	&Apache::loncommon::start_data_table_header_row().
                   4143: 	'<th rowspan="2" valign="middle">'.&mt('No.').'</th>'.
                   4144: 	'<th rowspan="2" valign="middle">'.&nameUserString('header')."</th>\n";
1.43      ng       4145:     my %scoreptr = (
                   4146: 		    'correct'  =>'correct_by_override',
                   4147: 		    'incorrect'=>'incorrect_by_override',
                   4148: 		    'excused'  =>'excused',
                   4149: 		    'ungraded' =>'ungraded_attempted',
1.596     raeburn  4150:                     'credited' =>'credit_attempted',
1.43      ng       4151: 		    'nothing'  => '',
                   4152: 		    );
1.257     albertel 4153:     my ($classlist,undef,$fullname) = &getclasslist($env{'form.section'},'0');
1.34      ng       4154: 
1.44      ng       4155:     my (@partid);
                   4156:     my %weight = ();
1.54      albertel 4157:     my %columns = ();
1.44      ng       4158:     my ($i,$ctr,$count,$rec_update) = (0,0,0,0);
1.54      albertel 4159: 
1.582     raeburn  4160:     my $partserror;
                   4161:     my (@parts) = sort(&getpartlist($symb,\$partserror));
                   4162:     if ($partserror) {
                   4163:         return &navmap_errormsg();
                   4164:     }
1.54      albertel 4165:     my $header;
1.257     albertel 4166:     while ($ctr < $env{'form.totalparts'}) {
                   4167: 	my $partid = $env{'form.partid_'.$ctr};
1.524     raeburn  4168: 	push(@partid,$partid);
1.257     albertel 4169: 	$weight{$partid} = $env{'form.weight_'.$partid};
1.44      ng       4170: 	$ctr++;
1.54      albertel 4171:     }
1.324     albertel 4172:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
1.54      albertel 4173:     foreach my $partid (@partid) {
1.478     albertel 4174: 	$header .= '<th align="center">'.&mt('Old Score').'</th>'.
                   4175: 	    '<th align="center">'.&mt('New Score').'</th>';
1.54      albertel 4176: 	$columns{$partid}=2;
                   4177: 	foreach my $stores (@parts) {
                   4178: 	    my ($part,$type) = &split_part_type($stores);
                   4179: 	    if ($part !~ m/^\Q$partid\E/) { next;}
                   4180: 	    if ($type eq 'awarded' || $type eq 'solved') { next; }
                   4181: 	    my $display=&Apache::lonnet::metadata($url,$stores.'.display');
1.551     raeburn  4182: 	    $display =~ s/\[Part: \Q$part\E\]//;
1.539     riegler  4183:             my $narrowtext = &mt('Tries');
                   4184: 	    $display =~ s/Number of Attempts/$narrowtext/;
                   4185: 	    $header .= '<th align="center">'.&mt('Old').' '.$display.'</th>'.
                   4186: 		'<th align="center">'.&mt('New').' '.$display.'</th>';
1.54      albertel 4187: 	    $columns{$partid}+=2;
                   4188: 	}
                   4189:     }
                   4190:     foreach my $partid (@partid) {
1.324     albertel 4191: 	my $display_part=&get_display_part($partid,$symb);
1.478     albertel 4192: 	$result .= '<th colspan="'.$columns{$partid}.'" align="center">'.
                   4193: 	    &mt('Part: [_1] (Weight = [_2])',$display_part,$weight{$partid}).
                   4194: 	    '</th>';
1.54      albertel 4195: 
1.44      ng       4196:     }
1.477     albertel 4197:     $result .= &Apache::loncommon::end_data_table_header_row().
                   4198: 	&Apache::loncommon::start_data_table_header_row().
                   4199: 	$header.
                   4200: 	&Apache::loncommon::end_data_table_header_row();
                   4201:     my @noupdate;
1.126     ng       4202:     my ($updateCtr,$noupdateCtr) = (1,1);
1.257     albertel 4203:     for ($i=0; $i<$env{'form.total'}; $i++) {
1.93      albertel 4204: 	my $line;
1.257     albertel 4205: 	my $user = $env{'form.ctr'.$i};
1.281     albertel 4206: 	my ($uname,$udom)=split(/:/,$user);
1.44      ng       4207: 	my %newrecord;
                   4208: 	my $updateflag = 0;
1.281     albertel 4209: 	$line .= '<td>'.&nameUserString(undef,$$fullname{$user},$uname,$udom).'</td>';
1.108     albertel 4210: 	my $usec=$classlist->{"$uname:$udom"}[5];
1.105     albertel 4211: 	if (!&canmodify($usec)) {
1.126     ng       4212: 	    my $numcols=scalar(@partid)*4+2;
1.477     albertel 4213: 	    push(@noupdate,
1.478     albertel 4214: 		 $line."<td colspan=\"$numcols\"><span class=\"LC_warning\">".
                   4215: 		 &mt('Not allowed to modify student')."</span></td></tr>");
1.105     albertel 4216: 	    next;
                   4217: 	}
1.269     raeburn  4218:         my %aggregate = ();
                   4219:         my $aggregateflag = 0;
1.281     albertel 4220: 	$user=~s/:/_/; # colon doen't work in javascript for names
1.44      ng       4221: 	foreach (@partid) {
1.257     albertel 4222: 	    my $old_aw    = $env{'form.GD_'.$user.'_'.$_.'_awarded_s'};
1.54      albertel 4223: 	    my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1);
                   4224: 	    my $old_part  = $old_aw eq '' ? '' : $old_part_pcr;
1.257     albertel 4225: 	    my $old_score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
                   4226: 	    my $awarded   = $env{'form.GD_'.$user.'_'.$_.'_awarded'};
1.54      albertel 4227: 	    my $pcr       = $awarded/($weight{$_} ne '0' ? $weight{$_} : 1);
                   4228: 	    my $partial   = $awarded eq '' ? '' : $pcr;
1.44      ng       4229: 	    my $score;
                   4230: 	    if ($partial eq '') {
1.257     albertel 4231: 		$score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
1.44      ng       4232: 	    } elsif ($partial > 0) {
                   4233: 		$score = 'correct_by_override';
                   4234: 	    } elsif ($partial == 0) {
                   4235: 		$score = 'incorrect_by_override';
                   4236: 	    }
1.257     albertel 4237: 	    my $dropMenu = $env{'form.GD_'.$user.'_'.$_.'_solved'};
1.125     ng       4238: 	    $score = 'excused' if (($dropMenu eq 'excused') && ($score ne 'excused'));
                   4239: 
1.292     albertel 4240: 	    $newrecord{'resource.'.$_.'.regrader'}=
                   4241: 		"$env{'user.name'}:$env{'user.domain'}";
1.125     ng       4242: 	    if ($dropMenu eq 'reset status' &&
                   4243: 		$old_score ne '') { # ignore if no previous attempts => nothing to reset
1.299     albertel 4244: 		$newrecord{'resource.'.$_.'.tries'} = '';
1.125     ng       4245: 		$newrecord{'resource.'.$_.'.solved'} = '';
                   4246: 		$newrecord{'resource.'.$_.'.award'} = '';
1.299     albertel 4247: 		$newrecord{'resource.'.$_.'.awarded'} = '';
1.125     ng       4248: 		$updateflag = 1;
1.269     raeburn  4249:                 if ($env{'form.GD_'.$user.'_'.$_.'_aggtries'} > 0) {
                   4250:                     my $aggtries = $env{'form.GD_'.$user.'_'.$_.'_aggtries'};
                   4251:                     my $totaltries = $env{'form.GD_'.$user.'_'.$_.'_totaltries'};
                   4252:                     my $solvedstatus = $env{'form.GD_'.$user.'_'.$_.'_solved_s'};
                   4253:                     &decrement_aggs($symb,$_,\%aggregate,$aggtries,$totaltries,$solvedstatus);
                   4254:                     $aggregateflag = 1;
                   4255:                 }
1.139     albertel 4256: 	    } elsif (!($old_part eq $partial && $old_score eq $score)) {
                   4257: 		$updateflag = 1;
                   4258: 		$newrecord{'resource.'.$_.'.awarded'}  = $partial if $partial ne '';
                   4259: 		$newrecord{'resource.'.$_.'.solved'}   = $score;
                   4260: 		$rec_update++;
1.125     ng       4261: 	    }
                   4262: 
1.93      albertel 4263: 	    $line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
1.44      ng       4264: 		'<td align="center">'.$awarded.
                   4265: 		($score eq 'excused' ? $score : '').'&nbsp;</td>';
1.5       albertel 4266: 
1.54      albertel 4267: 
                   4268: 	    my $partid=$_;
                   4269: 	    foreach my $stores (@parts) {
                   4270: 		my ($part,$type) = &split_part_type($stores);
                   4271: 		if ($part !~ m/^\Q$partid\E/) { next;}
                   4272: 		if ($type eq 'awarded' || $type eq 'solved') { next; }
1.257     albertel 4273: 		my $old_aw    = $env{'form.GD_'.$user.'_'.$part.'_'.$type.'_s'};
                   4274: 		my $awarded   = $env{'form.GD_'.$user.'_'.$part.'_'.$type};
1.54      albertel 4275: 		if ($awarded ne '' && $awarded ne $old_aw) {
                   4276: 		    $newrecord{'resource.'.$part.'.'.$type}= $awarded;
1.257     albertel 4277: 		    $newrecord{'resource.'.$part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
1.54      albertel 4278: 		    $updateflag=1;
                   4279: 		}
1.93      albertel 4280: 		$line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
1.54      albertel 4281: 		    '<td align="center">'.$awarded.'&nbsp;</td>';
                   4282: 	    }
1.44      ng       4283: 	}
1.477     albertel 4284: 	$line.="\n";
1.301     albertel 4285: 
                   4286: 	my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
                   4287: 	my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
                   4288: 
1.44      ng       4289: 	if ($updateflag) {
                   4290: 	    $count++;
1.257     albertel 4291: 	    &Apache::lonnet::cstore(\%newrecord,$symb,$env{'request.course.id'},
1.89      albertel 4292: 				    $udom,$uname);
1.301     albertel 4293: 
                   4294: 	    if (&Apache::bridgetask::in_queue('gradingqueue',$symb,$cdom,
                   4295: 					      $cnum,$udom,$uname)) {
                   4296: 		# need to figure out if should be in queue.
                   4297: 		my %record =  
                   4298: 		    &Apache::lonnet::restore($symb,$env{'request.course.id'},
                   4299: 					     $udom,$uname);
                   4300: 		my $all_graded = 1;
                   4301: 		my $none_graded = 1;
                   4302: 		foreach my $part (@parts) {
                   4303: 		    if ( $record{'resource.'.$part.'.awarded'} eq '' ) {
                   4304: 			$all_graded = 0;
                   4305: 		    } else {
                   4306: 			$none_graded = 0;
                   4307: 		    }
                   4308: 		}
                   4309: 
                   4310: 		if ($all_graded || $none_graded) {
                   4311: 		    &Apache::bridgetask::remove_from_queue('gradingqueue',
                   4312: 							   $symb,$cdom,$cnum,
                   4313: 							   $udom,$uname);
                   4314: 		}
                   4315: 	    }
                   4316: 
1.477     albertel 4317: 	    $result.=&Apache::loncommon::start_data_table_row().
                   4318: 		'<td align="right">&nbsp;'.$updateCtr.'&nbsp;</td>'.$line.
                   4319: 		&Apache::loncommon::end_data_table_row();
1.126     ng       4320: 	    $updateCtr++;
1.93      albertel 4321: 	} else {
1.477     albertel 4322: 	    push(@noupdate,
                   4323: 		 '<td align="right">&nbsp;'.$noupdateCtr.'&nbsp;</td>'.$line);
1.126     ng       4324: 	    $noupdateCtr++;
1.44      ng       4325: 	}
1.269     raeburn  4326:         if ($aggregateflag) {
                   4327:             &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
1.301     albertel 4328: 				  $cdom,$cnum);
1.269     raeburn  4329:         }
1.93      albertel 4330:     }
1.477     albertel 4331:     if (@noupdate) {
1.126     ng       4332: #	my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3;
                   4333: 	my $numcols=scalar(@partid)*4+2;
1.477     albertel 4334: 	$result .= &Apache::loncommon::start_data_table_row('LC_empty_row').
1.478     albertel 4335: 	    '<td align="center" colspan="'.$numcols.'">'.
                   4336: 	    &mt('No Changes Occurred For the Students Below').
                   4337: 	    '</td>'.
1.477     albertel 4338: 	    &Apache::loncommon::end_data_table_row();
                   4339: 	foreach my $line (@noupdate) {
                   4340: 	    $result.=
                   4341: 		&Apache::loncommon::start_data_table_row().
                   4342: 		$line.
                   4343: 		&Apache::loncommon::end_data_table_row();
                   4344: 	}
1.44      ng       4345:     }
1.477     albertel 4346:     $result .= &Apache::loncommon::end_data_table().
                   4347: 	&show_grading_menu_form($symb);
1.478     albertel 4348:     my $msg = '<p><b>'.
                   4349: 	&mt('Number of records updated = [_1] for [quant,_2,student].',
                   4350: 	    $rec_update,$count).'</b><br />'.
                   4351: 	'<b>'.&mt('Total number of students = [_1]',$env{'form.total'}).
                   4352: 	'</b></p>';
1.44      ng       4353:     return $title.$msg.$result;
1.5       albertel 4354: }
1.54      albertel 4355: 
                   4356: sub split_part_type {
                   4357:     my ($partstr) = @_;
                   4358:     my ($temp,@allparts)=split(/_/,$partstr);
                   4359:     my $type=pop(@allparts);
1.439     albertel 4360:     my $part=join('_',@allparts);
1.54      albertel 4361:     return ($part,$type);
                   4362: }
                   4363: 
1.44      ng       4364: #------------- end of section for handling grading by section/class ---------
                   4365: #
                   4366: #----------------------------------------------------------------------------
                   4367: 
1.5       albertel 4368: 
1.44      ng       4369: #----------------------------------------------------------------------------
                   4370: #
                   4371: #-------------------------- Next few routines handles grading by csv upload
                   4372: #
                   4373: #--- Javascript to handle csv upload
1.27      albertel 4374: sub csvupload_javascript_reverse_associate {
1.573     bisitz   4375:     my $error1=&mt('You need to specify the username or the student/employee ID');
1.246     albertel 4376:     my $error2=&mt('You need to specify at least one grading field');
1.596.2.12.2.  6(raebur 4377:6):   &js_escape(\$error1);
                   4378:6):   &js_escape(\$error2);
1.27      albertel 4379:   return(<<ENDPICK);
                   4380:   function verify(vf) {
                   4381:     var foundsomething=0;
                   4382:     var founduname=0;
1.243     albertel 4383:     var foundID=0;
1.27      albertel 4384:     for (i=0;i<=vf.nfields.value;i++) {
                   4385:       tw=eval('vf.f'+i+'.selectedIndex');
1.243     albertel 4386:       if (i==0 && tw!=0) { foundID=1; }
                   4387:       if (i==1 && tw!=0) { founduname=1; }
                   4388:       if (i!=0 && i!=1 && i!=2 && tw!=0) { foundsomething=1; }
1.27      albertel 4389:     }
1.246     albertel 4390:     if (founduname==0 && foundID==0) {
                   4391: 	alert('$error1');
                   4392: 	return;
1.27      albertel 4393:     }
                   4394:     if (foundsomething==0) {
1.246     albertel 4395: 	alert('$error2');
                   4396: 	return;
1.27      albertel 4397:     }
                   4398:     vf.submit();
                   4399:   }
                   4400:   function flip(vf,tf) {
                   4401:     var nw=eval('vf.f'+tf+'.selectedIndex');
                   4402:     var i;
                   4403:     for (i=0;i<=vf.nfields.value;i++) {
                   4404:       //can not pick the same destination field for both name and domain
                   4405:       if (((i ==0)||(i ==1)) && 
                   4406:           ((tf==0)||(tf==1)) && 
                   4407:           (i!=tf) &&
                   4408:           (eval('vf.f'+i+'.selectedIndex')==nw)) {
                   4409:         eval('vf.f'+i+'.selectedIndex=0;')
                   4410:       }
                   4411:     }
                   4412:   }
                   4413: ENDPICK
                   4414: }
                   4415: 
                   4416: sub csvupload_javascript_forward_associate {
1.573     bisitz   4417:     my $error1=&mt('You need to specify the username or the student/employee ID');
1.246     albertel 4418:     my $error2=&mt('You need to specify at least one grading field');
1.596.2.12.2.  6(raebur 4419:6):   &js_escape(\$error1);
                   4420:6):   &js_escape(\$error2);
1.27      albertel 4421:   return(<<ENDPICK);
                   4422:   function verify(vf) {
                   4423:     var foundsomething=0;
                   4424:     var founduname=0;
1.243     albertel 4425:     var foundID=0;
1.27      albertel 4426:     for (i=0;i<=vf.nfields.value;i++) {
                   4427:       tw=eval('vf.f'+i+'.selectedIndex');
1.243     albertel 4428:       if (tw==1) { foundID=1; }
                   4429:       if (tw==2) { founduname=1; }
                   4430:       if (tw>3) { foundsomething=1; }
1.27      albertel 4431:     }
1.246     albertel 4432:     if (founduname==0 && foundID==0) {
                   4433: 	alert('$error1');
                   4434: 	return;
1.27      albertel 4435:     }
                   4436:     if (foundsomething==0) {
1.246     albertel 4437: 	alert('$error2');
                   4438: 	return;
1.27      albertel 4439:     }
                   4440:     vf.submit();
                   4441:   }
                   4442:   function flip(vf,tf) {
                   4443:     var nw=eval('vf.f'+tf+'.selectedIndex');
                   4444:     var i;
                   4445:     //can not pick the same destination field twice
                   4446:     for (i=0;i<=vf.nfields.value;i++) {
                   4447:       if ((i!=tf) && (eval('vf.f'+i+'.selectedIndex')==nw)) {
                   4448:         eval('vf.f'+i+'.selectedIndex=0;')
                   4449:       }
                   4450:     }
                   4451:   }
                   4452: ENDPICK
                   4453: }
                   4454: 
1.26      albertel 4455: sub csvuploadmap_header {
1.324     albertel 4456:     my ($request,$symb,$datatoken,$distotal)= @_;
1.41      ng       4457:     my $javascript;
1.257     albertel 4458:     if ($env{'form.upfile_associate'} eq 'reverse') {
1.41      ng       4459: 	$javascript=&csvupload_javascript_reverse_associate();
                   4460:     } else {
                   4461: 	$javascript=&csvupload_javascript_forward_associate();
                   4462:     }
1.45      ng       4463: 
1.324     albertel 4464:     my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
1.257     albertel 4465:     my $checked=(($env{'form.noFirstLine'})?' checked="checked"':'');
1.245     albertel 4466:     my $ignore=&mt('Ignore First Line');
1.418     albertel 4467:     $symb = &Apache::lonenc::check_encrypt($symb);
1.41      ng       4468:     $request->print(<<ENDPICK);
1.26      albertel 4469: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
1.398     albertel 4470: <h3><span class="LC_info">Uploading Class Grades</span></h3>
1.45      ng       4471: $result
1.326     albertel 4472: <hr />
1.26      albertel 4473: <h3>Identify fields</h3>
                   4474: Total number of records found in file: $distotal <hr />
                   4475: Enter as many fields as you can. The system will inform you and bring you back
                   4476: to this page if the data selected is insufficient to run your class.<hr />
1.589     bisitz   4477: <input type="button" value="Reverse Association" onclick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" />
1.245     albertel 4478: <label><input type="checkbox" name="noFirstLine" $checked />$ignore</label>
1.26      albertel 4479: <input type="hidden" name="associate"  value="" />
                   4480: <input type="hidden" name="phase"      value="three" />
                   4481: <input type="hidden" name="datatoken"  value="$datatoken" />
1.257     albertel 4482: <input type="hidden" name="fileupload" value="$env{'form.fileupload'}" />
                   4483: <input type="hidden" name="upfiletype" value="$env{'form.upfiletype'}" />
1.26      albertel 4484: <input type="hidden" name="upfile_associate" 
1.257     albertel 4485:                                        value="$env{'form.upfile_associate'}" />
1.26      albertel 4486: <input type="hidden" name="symb"       value="$symb" />
1.257     albertel 4487: <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
                   4488: <input type="hidden" name="probTitle"  value="$env{'form.probTitle'}" />
1.246     albertel 4489: <input type="hidden" name="command"    value="csvuploadoptions" />
1.26      albertel 4490: <hr />
                   4491: <script type="text/javascript" language="Javascript">
                   4492: $javascript
                   4493: </script>
                   4494: ENDPICK
1.118     ng       4495:     return '';
1.26      albertel 4496: 
                   4497: }
                   4498: 
                   4499: sub csvupload_fields {
1.582     raeburn  4500:     my ($symb,$errorref) = @_;
                   4501:     my (@parts) = &getpartlist($symb,$errorref);
                   4502:     if (ref($errorref)) {
                   4503:         if ($$errorref) {
                   4504:             return;
                   4505:         }
                   4506:     }
                   4507: 
1.556     weissno  4508:     my @fields=(['ID','Student/Employee ID'],
1.243     albertel 4509: 		['username','Student Username'],
                   4510: 		['domain','Student Domain']);
1.324     albertel 4511:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
1.41      ng       4512:     foreach my $part (sort(@parts)) {
                   4513: 	my @datum;
                   4514: 	my $display=&Apache::lonnet::metadata($url,$part.'.display');
                   4515: 	my $name=$part;
                   4516: 	if  (!$display) { $display = $name; }
                   4517: 	@datum=($name,$display);
1.244     albertel 4518: 	if ($name=~/^stores_(.*)_awarded/) {
                   4519: 	    push(@fields,['stores_'.$1.'_points',"Points [Part: $1]"]);
                   4520: 	}
1.41      ng       4521: 	push(@fields,\@datum);
                   4522:     }
                   4523:     return (@fields);
1.26      albertel 4524: }
                   4525: 
                   4526: sub csvuploadmap_footer {
1.41      ng       4527:     my ($request,$i,$keyfields) =@_;
1.596.2.12.2.  0(raebur 4528:3):     my $buttontext = &mt('Assign Grades');
1.41      ng       4529:     $request->print(<<ENDPICK);
1.26      albertel 4530: </table>
                   4531: <input type="hidden" name="nfields" value="$i" />
                   4532: <input type="hidden" name="keyfields" value="$keyfields" />
1.596.2.12.2.  0(raebur 4533:3): <input type="button" onclick="javascript:verify(this.form)" value="$buttontext" /><br />
1.26      albertel 4534: </form>
                   4535: ENDPICK
                   4536: }
                   4537: 
1.283     albertel 4538: sub checkforfile_js {
1.539     riegler  4539:     my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
1.596.2.12.2.  6(raebur 4540:6):     &js_escape(\$alertmsg);
1.86      ng       4541:     my $result =<<CSVFORMJS;
                   4542: <script type="text/javascript" language="javascript">
                   4543:     function checkUpload(formname) {
                   4544: 	if (formname.upfile.value == "") {
1.539     riegler  4545: 	    alert("$alertmsg");
1.86      ng       4546: 	    return false;
                   4547: 	}
                   4548: 	formname.submit();
                   4549:     }
                   4550:     </script>
                   4551: CSVFORMJS
1.283     albertel 4552:     return $result;
                   4553: }
                   4554: 
                   4555: sub upcsvScores_form {
                   4556:     my ($request) = shift;
1.324     albertel 4557:     my ($symb)=&get_symb($request);
1.283     albertel 4558:     if (!$symb) {return '';}
                   4559:     my $result=&checkforfile_js();
1.257     albertel 4560:     $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
1.324     albertel 4561:     my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});
1.118     ng       4562:     $result.=$table;
1.326     albertel 4563:     $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";
                   4564:     $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";
1.538     schulted 4565:     $result.='&nbsp;<b>'.&mt('Specify a file containing the class scores for current resource.').
                   4566: 	'</b></td></tr>'."\n";
1.596.2.4  raeburn  4567:     $result.='<tr bgcolor="#ffffe6"><td>'."\n";
1.370     www      4568:     my $upload=&mt("Upload Scores");
1.86      ng       4569:     my $upfile_select=&Apache::loncommon::upfile_select_html();
1.245     albertel 4570:     my $ignore=&mt('Ignore First Line');
1.418     albertel 4571:     $symb = &Apache::lonenc::check_encrypt($symb);
1.86      ng       4572:     $result.=<<ENDUPFORM;
1.106     albertel 4573: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
1.86      ng       4574: <input type="hidden" name="symb" value="$symb" />
                   4575: <input type="hidden" name="command" value="csvuploadmap" />
1.257     albertel 4576: <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
                   4577: <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
1.86      ng       4578: $upfile_select
1.589     bisitz   4579: <br /><input type="button" onclick="javascript:checkUpload(this.form);" value="$upload" />
1.283     albertel 4580: <label><input type="checkbox" name="noFirstLine" />$ignore</label>
1.86      ng       4581: </form>
                   4582: ENDUPFORM
1.370     www      4583:     $result.=&Apache::loncommon::help_open_topic("Course_Convert_To_CSV",
                   4584:                            &mt("How do I create a CSV file from a spreadsheet"))
                   4585:     .'</td></tr></table>'."\n";
1.86      ng       4586:     $result.='</td></tr></table><br /><br />'."\n";
1.324     albertel 4587:     $result.=&show_grading_menu_form($symb);
1.86      ng       4588:     return $result;
                   4589: }
                   4590: 
                   4591: 
1.26      albertel 4592: sub csvuploadmap {
1.41      ng       4593:     my ($request)= @_;
1.324     albertel 4594:     my ($symb)=&get_symb($request);
1.41      ng       4595:     if (!$symb) {return '';}
1.72      ng       4596: 
1.41      ng       4597:     my $datatoken;
1.257     albertel 4598:     if (!$env{'form.datatoken'}) {
1.41      ng       4599: 	$datatoken=&Apache::loncommon::upfile_store($request);
1.26      albertel 4600:     } else {
1.257     albertel 4601: 	$datatoken=$env{'form.datatoken'};
1.41      ng       4602: 	&Apache::loncommon::load_tmp_file($request);
1.26      albertel 4603:     }
1.41      ng       4604:     my @records=&Apache::loncommon::upfile_record_sep();
1.257     albertel 4605:     if ($env{'form.noFirstLine'}) { shift(@records); }
1.324     albertel 4606:     &csvuploadmap_header($request,$symb,$datatoken,$#records+1);
1.41      ng       4607:     my ($i,$keyfields);
                   4608:     if (@records) {
1.582     raeburn  4609:         my $fieldserror;
                   4610: 	my @fields=&csvupload_fields($symb,\$fieldserror);
                   4611:         if ($fieldserror) {
                   4612:             $request->print(&navmap_errormsg());
                   4613:             return;
                   4614:         }
1.257     albertel 4615: 	if ($env{'form.upfile_associate'} eq 'reverse') {	
1.41      ng       4616: 	    &Apache::loncommon::csv_print_samples($request,\@records);
                   4617: 	    $i=&Apache::loncommon::csv_print_select_table($request,\@records,
                   4618: 							  \@fields);
                   4619: 	    foreach (@fields) { $keyfields.=$_->[0].','; }
                   4620: 	    chop($keyfields);
                   4621: 	} else {
                   4622: 	    unshift(@fields,['none','']);
                   4623: 	    $i=&Apache::loncommon::csv_samples_select_table($request,\@records,
                   4624: 							    \@fields);
1.311     banghart 4625:             foreach my $rec (@records) {
                   4626:                 my %temp = &Apache::loncommon::record_sep($rec);
                   4627:                 if (%temp) {
                   4628:                     $keyfields=join(',',sort(keys(%temp)));
                   4629:                     last;
                   4630:                 }
                   4631:             }
1.41      ng       4632: 	}
                   4633:     }
                   4634:     &csvuploadmap_footer($request,$i,$keyfields);
1.324     albertel 4635:     $request->print(&show_grading_menu_form($symb));
1.72      ng       4636: 
1.41      ng       4637:     return '';
1.27      albertel 4638: }
                   4639: 
1.246     albertel 4640: sub csvuploadoptions {
1.41      ng       4641:     my ($request)= @_;
1.324     albertel 4642:     my ($symb)=&get_symb($request);
1.257     albertel 4643:     my $checked=(($env{'form.noFirstLine'})?'1':'0');
1.246     albertel 4644:     my $ignore=&mt('Ignore First Line');
                   4645:     $request->print(<<ENDPICK);
                   4646: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
1.398     albertel 4647: <h3><span class="LC_info">Uploading Class Grade Options</span></h3>
1.246     albertel 4648: <input type="hidden" name="command"    value="csvuploadassign" />
1.302     albertel 4649: <!--
1.246     albertel 4650: <p>
                   4651: <label>
                   4652:    <input type="checkbox" name="show_full_results" />
                   4653:    Show a table of all changes
                   4654: </label>
                   4655: </p>
1.302     albertel 4656: -->
1.246     albertel 4657: <p>
                   4658: <label>
                   4659:    <input type="checkbox" name="overwite_scores" checked="checked" />
                   4660:    Overwrite any existing score
                   4661: </label>
                   4662: </p>
                   4663: ENDPICK
                   4664:     my %fields=&get_fields();
                   4665:     if (!defined($fields{'domain'})) {
1.257     albertel 4666: 	my $domform = &Apache::loncommon::select_dom_form($env{'request.role.domain'},'default_domain');
1.246     albertel 4667: 	$request->print("\n<p> Users are in domain: ".$domform."</p>\n");
                   4668:     }
1.257     albertel 4669:     foreach my $key (sort(keys(%env))) {
1.246     albertel 4670: 	if ($key !~ /^form\.(.*)$/) { next; }
                   4671: 	my $cleankey=$1;
                   4672: 	if ($cleankey eq 'command') { next; }
                   4673: 	$request->print('<input type="hidden" name="'.$cleankey.
1.257     albertel 4674: 			'"  value="'.$env{$key}.'" />'."\n");
1.246     albertel 4675:     }
                   4676:     # FIXME do a check for any duplicated user ids...
                   4677:     # FIXME do a check for any invalid user ids?...
1.596.2.12.2.  0(raebur 4678:3):     $request->print('<input type="submit" value="'.&mt('Assign Grades').'" /><br />
1.290     albertel 4679: <hr /></form>'."\n");
1.324     albertel 4680:     $request->print(&show_grading_menu_form($symb));
1.246     albertel 4681:     return '';
                   4682: }
                   4683: 
                   4684: sub get_fields {
                   4685:     my %fields;
1.257     albertel 4686:     my @keyfields = split(/\,/,$env{'form.keyfields'});
                   4687:     for (my $i=0; $i<=$env{'form.nfields'}; $i++) {
                   4688: 	if ($env{'form.upfile_associate'} eq 'reverse') {
                   4689: 	    if ($env{'form.f'.$i} ne 'none') {
                   4690: 		$fields{$keyfields[$i]}=$env{'form.f'.$i};
1.41      ng       4691: 	    }
                   4692: 	} else {
1.257     albertel 4693: 	    if ($env{'form.f'.$i} ne 'none') {
                   4694: 		$fields{$env{'form.f'.$i}}=$keyfields[$i];
1.41      ng       4695: 	    }
                   4696: 	}
1.27      albertel 4697:     }
1.246     albertel 4698:     return %fields;
                   4699: }
                   4700: 
                   4701: sub csvuploadassign {
                   4702:     my ($request)= @_;
1.324     albertel 4703:     my ($symb)=&get_symb($request);
1.246     albertel 4704:     if (!$symb) {return '';}
1.345     bowersj2 4705:     my $error_msg = '';
1.246     albertel 4706:     &Apache::loncommon::load_tmp_file($request);
                   4707:     my @gradedata = &Apache::loncommon::upfile_record_sep();
1.257     albertel 4708:     if ($env{'form.noFirstLine'}) { shift(@gradedata); }
1.246     albertel 4709:     my %fields=&get_fields();
1.41      ng       4710:     $request->print('<h3>Assigning Grades</h3>');
1.257     albertel 4711:     my $courseid=$env{'request.course.id'};
1.97      albertel 4712:     my ($classlist) = &getclasslist('all',0);
1.106     albertel 4713:     my @notallowed;
1.41      ng       4714:     my @skipped;
1.596.2.4  raeburn  4715:     my @warnings;
1.41      ng       4716:     my $countdone=0;
                   4717:     foreach my $grade (@gradedata) {
                   4718: 	my %entries=&Apache::loncommon::record_sep($grade);
1.246     albertel 4719: 	my $domain;
                   4720: 	if ($entries{$fields{'domain'}}) {
                   4721: 	    $domain=$entries{$fields{'domain'}};
                   4722: 	} else {
1.257     albertel 4723: 	    $domain=$env{'form.default_domain'};
1.246     albertel 4724: 	}
1.243     albertel 4725: 	$domain=~s/\s//g;
1.41      ng       4726: 	my $username=$entries{$fields{'username'}};
1.160     albertel 4727: 	$username=~s/\s//g;
1.243     albertel 4728: 	if (!$username) {
                   4729: 	    my $id=$entries{$fields{'ID'}};
1.247     albertel 4730: 	    $id=~s/\s//g;
1.243     albertel 4731: 	    my %ids=&Apache::lonnet::idget($domain,$id);
                   4732: 	    $username=$ids{$id};
                   4733: 	}
1.41      ng       4734: 	if (!exists($$classlist{"$username:$domain"})) {
1.247     albertel 4735: 	    my $id=$entries{$fields{'ID'}};
                   4736: 	    $id=~s/\s//g;
                   4737: 	    if ($id) {
                   4738: 		push(@skipped,"$id:$domain");
                   4739: 	    } else {
                   4740: 		push(@skipped,"$username:$domain");
                   4741: 	    }
1.41      ng       4742: 	    next;
                   4743: 	}
1.108     albertel 4744: 	my $usec=$classlist->{"$username:$domain"}[5];
1.106     albertel 4745: 	if (!&canmodify($usec)) {
                   4746: 	    push(@notallowed,"$username:$domain");
                   4747: 	    next;
                   4748: 	}
1.244     albertel 4749: 	my %points;
1.41      ng       4750: 	my %grades;
                   4751: 	foreach my $dest (keys(%fields)) {
1.244     albertel 4752: 	    if ($dest eq 'ID' || $dest eq 'username' ||
                   4753: 		$dest eq 'domain') { next; }
                   4754: 	    if ($entries{$fields{$dest}} =~ /^\s*$/) { next; }
                   4755: 	    if ($dest=~/stores_(.*)_points/) {
                   4756: 		my $part=$1;
                   4757: 		my $wgt =&Apache::lonnet::EXT('resource.'.$part.'.weight',
                   4758: 					      $symb,$domain,$username);
1.345     bowersj2 4759:                 if ($wgt) {
                   4760:                     $entries{$fields{$dest}}=~s/\s//g;
                   4761:                     my $pcr=$entries{$fields{$dest}} / $wgt;
1.463     albertel 4762:                     my $award=($pcr == 0) ? 'incorrect_by_override'
                   4763:                                           : 'correct_by_override';
1.596.2.4  raeburn  4764:                     if ($pcr>1) {
                   4765:                         push(@warnings,&mt("[_1]: point value larger than weight","$username:$domain"));
                   4766:                     }
1.345     bowersj2 4767:                     $grades{"resource.$part.awarded"}=$pcr;
                   4768:                     $grades{"resource.$part.solved"}=$award;
                   4769:                     $points{$part}=1;
                   4770:                 } else {
                   4771:                     $error_msg = "<br />" .
                   4772:                         &mt("Some point values were assigned"
                   4773:                             ." for problems with a weight "
                   4774:                             ."of zero. These values were "
                   4775:                             ."ignored.");
                   4776:                 }
1.244     albertel 4777: 	    } else {
                   4778: 		if ($dest=~/stores_(.*)_awarded/) { if ($points{$1}) {next;} }
                   4779: 		if ($dest=~/stores_(.*)_solved/)  { if ($points{$1}) {next;} }
                   4780: 		my $store_key=$dest;
                   4781: 		$store_key=~s/^stores/resource/;
                   4782: 		$store_key=~s/_/\./g;
                   4783: 		$grades{$store_key}=$entries{$fields{$dest}};
                   4784: 	    }
1.41      ng       4785: 	}
1.508     www      4786: 	if (! %grades) { 
                   4787:            push(@skipped,&mt("[_1]: no data to save","$username:$domain")); 
                   4788:         } else {
                   4789: 	   $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
                   4790: 	   my $result=&Apache::lonnet::cstore(\%grades,$symb,
1.302     albertel 4791: 					   $env{'request.course.id'},
                   4792: 					   $domain,$username);
1.508     www      4793: 	   if ($result eq 'ok') {
                   4794: 	      $request->print('.');
1.596.2.4  raeburn  4795: # Remove from grading queue
                   4796:               &Apache::bridgetask::remove_from_queue('gradingqueue',$symb,
                   4797:                                              $env{'course.'.$env{'request.course.id'}.'.domain'},
                   4798:                                              $env{'course.'.$env{'request.course.id'}.'.num'},
                   4799:                                              $domain,$username);
1.508     www      4800: 	   } else {
                   4801: 	      $request->print("<p><span class=\"LC_error\">".
                   4802:                               &mt("Failed to save data for student [_1]. Message when trying to save was: [_2]",
                   4803:                                   "$username:$domain",$result)."</span></p>");
                   4804: 	   }
                   4805: 	   $request->rflush();
                   4806: 	   $countdone++;
                   4807:         }
1.41      ng       4808:     }
1.570     www      4809:     $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt("Saved scores for [quant,_1,student]",$countdone),$countdone==0));
1.596.2.4  raeburn  4810:     if (@warnings) {
                   4811:         $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('Warnings generated for the following saved scores:'),1).'<br />');
                   4812:         $request->print(join(', ',@warnings));
                   4813:     }
1.41      ng       4814:     if (@skipped) {
1.571     www      4815: 	$request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('No scores stored for the following username(s):'),1).'<br />');
                   4816:         $request->print(join(', ',@skipped));
1.106     albertel 4817:     }
                   4818:     if (@notallowed) {
1.571     www      4819: 	$request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('Modification of scores not allowed for the following username(s):'),1).'<br />');
                   4820: 	$request->print(join(', ',@notallowed));
1.41      ng       4821:     }
1.106     albertel 4822:     $request->print("<br />\n");
1.324     albertel 4823:     $request->print(&show_grading_menu_form($symb));
1.345     bowersj2 4824:     return $error_msg;
1.26      albertel 4825: }
1.44      ng       4826: #------------- end of section for handling csv file upload ---------
                   4827: #
                   4828: #-------------------------------------------------------------------
                   4829: #
1.122     ng       4830: #-------------- Next few routines handle grading by page/sequence
1.72      ng       4831: #
                   4832: #--- Select a page/sequence and a student to grade
1.68      ng       4833: sub pickStudentPage {
                   4834:     my ($request) = shift;
                   4835: 
1.539     riegler  4836:     my $alertmsg = &mt('Please select the student you wish to grade.');
1.596.2.12.2.  6(raebur 4837:6):     &js_escape(\$alertmsg);
1.68      ng       4838:     $request->print(<<LISTJAVASCRIPT);
                   4839: <script type="text/javascript" language="javascript">
                   4840: 
                   4841: function checkPickOne(formname) {
1.76      ng       4842:     if (radioSelection(formname.student) == null) {
1.539     riegler  4843: 	alert("$alertmsg");
1.68      ng       4844: 	return;
                   4845:     }
1.125     ng       4846:     ptr = pullDownSelection(formname.selectpage);
                   4847:     formname.page.value = formname["page"+ptr].value;
                   4848:     formname.title.value = formname["title"+ptr].value;
1.68      ng       4849:     formname.submit();
                   4850: }
                   4851: 
                   4852: </script>
                   4853: LISTJAVASCRIPT
1.118     ng       4854:     &commonJSfunctions($request);
1.324     albertel 4855:     my ($symb) = &get_symb($request);
1.257     albertel 4856:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
                   4857:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
                   4858:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
1.68      ng       4859: 
1.398     albertel 4860:     my $result='<h3><span class="LC_info">&nbsp;'.
1.485     albertel 4861: 	&mt('Manual Grading by Page or Sequence').'</span></h3>';
1.68      ng       4862: 
1.80      ng       4863:     $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";
1.582     raeburn  4864:     my $map_error;
                   4865:     my ($titles,$symbx) = &getSymbMap($map_error);
                   4866:     if ($map_error) {
                   4867:         $request->print(&navmap_errormsg());
                   4868:         return; 
                   4869:     }
1.137     albertel 4870:     my ($curpage) =&Apache::lonnet::decode_symb($symb); 
                   4871: #    my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); 
                   4872: #    my $type=($curpage =~ /\.(page|sequence)/);
1.485     albertel 4873:     my $select = '<select name="selectpage">'."\n";
1.70      ng       4874:     my $ctr=0;
1.68      ng       4875:     foreach (@$titles) {
                   4876: 	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
1.485     albertel 4877: 	$select.='<option value="'.$ctr.'" '.
1.401     albertel 4878: 	    ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
1.71      ng       4879: 	    '>'.$showtitle.'</option>'."\n";
1.70      ng       4880: 	$ctr++;
1.68      ng       4881:     }
1.485     albertel 4882:     $select.= '</select>';
1.539     riegler  4883:     $result.='&nbsp;<b>'.&mt('Problems from').':</b> '.$select."<br />\n";
1.485     albertel 4884: 
1.70      ng       4885:     $ctr=0;
                   4886:     foreach (@$titles) {
                   4887: 	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
                   4888: 	$result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n";
                   4889: 	$result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n";
                   4890: 	$ctr++;
                   4891:     }
1.72      ng       4892:     $result.='<input type="hidden" name="page" />'."\n".
                   4893: 	'<input type="hidden" name="title" />'."\n";
1.68      ng       4894: 
1.485     albertel 4895:     my $options =
                   4896: 	'<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n".
                   4897: 	'<label><input type="radio" name="vProb" value="yes" /> '.&mt('yes').' </label>'."<br />\n";
1.539     riegler  4898:     $result.='&nbsp;<b>'.&mt('View Problem Text').': </b>'.$options;
1.485     albertel 4899: 
                   4900:     $options =
                   4901: 	'<label><input type="radio" name="lastSub" value="none" /> '.&mt('none').' </label>'."\n".
                   4902: 	'<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> '.&mt('by dates and submissions').'</label>'."\n".
                   4903: 	'<label><input type="radio" name="lastSub" value="all" /> '.&mt('all details').' </label>'."\n";
1.539     riegler  4904:     $result.='&nbsp;<b>'.&mt('Submissions').': </b>'.$options;
1.432     banghart 4905:     
                   4906:     $result.=&build_section_inputs();
1.442     banghart 4907:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
                   4908:     $result.='<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
1.72      ng       4909: 	'<input type="hidden" name="command" value="displayPage" />'."\n".
1.418     albertel 4910: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1.257     albertel 4911: 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n";
1.72      ng       4912: 
1.539     riegler  4913:     $result.='&nbsp;<b>'.&mt('Use CODE').': </b> <input type="text" name="CODE" value="" /> <br />'."\n";
1.382     albertel 4914: 
1.80      ng       4915:     $result.='&nbsp;<input type="button" '.
1.589     bisitz   4916:              'onclick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /><br />'."\n";
1.72      ng       4917: 
1.68      ng       4918:     $request->print($result);
                   4919: 
1.485     albertel 4920:     my $studentTable.='&nbsp;<b>'.&mt('Select a student you wish to grade and then click on the Next button.').'</b><br />'.
1.484     albertel 4921: 	&Apache::loncommon::start_data_table().
                   4922: 	&Apache::loncommon::start_data_table_header_row().
1.485     albertel 4923: 	'<th align="right">&nbsp;'.&mt('No.').'</th>'.
1.484     albertel 4924: 	'<th>'.&nameUserString('header').'</th>'.
1.485     albertel 4925: 	'<th align="right">&nbsp;'.&mt('No.').'</th>'.
1.484     albertel 4926: 	'<th>'.&nameUserString('header').'</th>'.
                   4927: 	&Apache::loncommon::end_data_table_header_row();
1.68      ng       4928:  
1.76      ng       4929:     my (undef,undef,$fullname) = &getclasslist($getsec,'1');
1.68      ng       4930:     my $ptr = 1;
1.294     albertel 4931:     foreach my $student (sort 
                   4932: 			 {
                   4933: 			     if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
                   4934: 				 return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
                   4935: 			     }
                   4936: 			     return $a cmp $b;
                   4937: 			 } (keys(%$fullname))) {
1.68      ng       4938: 	my ($uname,$udom) = split(/:/,$student);
1.484     albertel 4939: 	$studentTable.=($ptr%2==1 ? &Apache::loncommon::start_data_table_row()
                   4940:                                   : '</td>');
1.126     ng       4941: 	$studentTable.='<td align="right">'.$ptr.'&nbsp;</td>';
1.288     albertel 4942: 	$studentTable.='<td>&nbsp;<label><input type="radio" name="student" value="'.$student.'" /> '
                   4943: 	    .&nameUserString(undef,$$fullname{$student},$uname,$udom)."</label>\n";
1.484     albertel 4944: 	$studentTable.=
                   4945: 	    ($ptr%2 == 0 ? '</td>'.&Apache::loncommon::end_data_table_row() 
                   4946:                          : '');
1.68      ng       4947: 	$ptr++;
                   4948:     }
1.484     albertel 4949:     if ($ptr%2 == 0) {
                   4950: 	$studentTable.='</td><td>&nbsp;</td><td>&nbsp;</td>'.
                   4951: 	    &Apache::loncommon::end_data_table_row();
                   4952:     }
                   4953:     $studentTable.=&Apache::loncommon::end_data_table()."\n";
1.126     ng       4954:     $studentTable.='<input type="button" '.
1.589     bisitz   4955:                    'onclick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /></form>'."\n";
1.68      ng       4956: 
1.324     albertel 4957:     $studentTable.=&show_grading_menu_form($symb);
1.68      ng       4958:     $request->print($studentTable);
                   4959: 
                   4960:     return '';
                   4961: }
                   4962: 
                   4963: sub getSymbMap {
1.582     raeburn  4964:     my ($map_error) = @_;
1.132     bowersj2 4965:     my $navmap = Apache::lonnavmaps::navmap->new();
1.582     raeburn  4966:     unless (ref($navmap)) {
                   4967:         if (ref($map_error)) {
                   4968:             $$map_error = 'navmap';
                   4969:         }
                   4970:         return;
                   4971:     }
1.68      ng       4972:     my %symbx = ();
                   4973:     my @titles = ();
1.117     bowersj2 4974:     my $minder = 0;
                   4975: 
                   4976:     # Gather every sequence that has problems.
1.240     albertel 4977:     my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); },
                   4978: 					       1,0,1);
1.117     bowersj2 4979:     for my $sequence ($navmap->getById('0.0'), @sequences) {
1.241     albertel 4980: 	if ($navmap->hasResource($sequence, sub { shift->is_problem(); }, 0) ) {
1.381     albertel 4981: 	    my $title = $minder.'.'.
                   4982: 		&HTML::Entities::encode($sequence->compTitle(),'"\'&');
                   4983: 	    push(@titles, $title); # minder in case two titles are identical
                   4984: 	    $symbx{$title} = &HTML::Entities::encode($sequence->symb(),'"\'&');
1.117     bowersj2 4985: 	    $minder++;
1.241     albertel 4986: 	}
1.68      ng       4987:     }
                   4988:     return \@titles,\%symbx;
                   4989: }
                   4990: 
1.72      ng       4991: #
                   4992: #--- Displays a page/sequence w/wo problems, w/wo submissions
1.68      ng       4993: sub displayPage {
                   4994:     my ($request) = shift;
                   4995: 
1.324     albertel 4996:     my ($symb) = &get_symb($request);
1.257     albertel 4997:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
                   4998:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
                   4999:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
                   5000:     my $pageTitle = $env{'form.page'};
1.103     albertel 5001:     my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
1.257     albertel 5002:     my ($uname,$udom) = split(/:/,$env{'form.student'});
                   5003:     my $usec=$classlist->{$env{'form.student'}}[5];
1.168     albertel 5004: 
                   5005:     #need to make sure we have the correct data for later EXT calls, 
                   5006:     #thus invalidate the cache
                   5007:     &Apache::lonnet::devalidatecourseresdata(
1.257     albertel 5008:                  $env{'course.'.$env{'request.course.id'}.'.num'},
                   5009:                  $env{'course.'.$env{'request.course.id'}.'.domain'});
1.168     albertel 5010:     &Apache::lonnet::clear_EXT_cache_status();
                   5011: 
1.103     albertel 5012:     if (!&canview($usec)) {
1.596.2.12.2.  8(raebur 5013:4): 	$request->print('<span class="LC_warning">'.
                   5014:4):                         &mt('Unable to view requested student. ([_1])',
                   5015:4):                             $env{'form.student'}).
                   5016:4):                         '</span>');
                   5017:4):         $request->print(&show_grading_menu_form($symb));
                   5018:4):         return;
1.103     albertel 5019:     }
1.398     albertel 5020:     my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
1.485     albertel 5021:     $result.='<h3>&nbsp;'.&mt('Student: [_1]',&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom)).
1.129     ng       5022: 	'</h3>'."\n";
1.500     albertel 5023:     $env{'form.CODE'} = uc($env{'form.CODE'});
1.501     foxr     5024:     if (&Apache::lonnet::validCODE(uc($env{'form.CODE'}))) {
1.485     albertel 5025: 	$result.='<h3>&nbsp;'.&mt('CODE: [_1]',$env{'form.CODE'}).'</h3>'."\n";
1.382     albertel 5026:     } else {
                   5027: 	delete($env{'form.CODE'});
                   5028:     }
1.71      ng       5029:     &sub_page_js($request);
                   5030:     $request->print($result);
                   5031: 
1.132     bowersj2 5032:     my $navmap = Apache::lonnavmaps::navmap->new();
1.582     raeburn  5033:     unless (ref($navmap)) {
                   5034:         $request->print(&navmap_errormsg());
                   5035:         $request->print(&show_grading_menu_form($symb));
                   5036:         return;
                   5037:     }
1.257     albertel 5038:     my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});
1.68      ng       5039:     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
1.288     albertel 5040:     if (!$map) {
1.485     albertel 5041: 	$request->print('<span class="LC_warning">'.&mt('Unable to view requested sequence. ([_1])',$resUrl).'</span>');
1.324     albertel 5042: 	$request->print(&show_grading_menu_form($symb));
1.288     albertel 5043: 	return; 
                   5044:     }
1.68      ng       5045:     my $iterator = $navmap->getIterator($map->map_start(),
                   5046: 					$map->map_finish());
                   5047: 
1.71      ng       5048:     my $studentTable='<form action="/adm/grades" method="post" name="gradePage">'."\n".
1.72      ng       5049: 	'<input type="hidden" name="command" value="gradeByPage" />'."\n".
1.257     albertel 5050: 	'<input type="hidden" name="fullname" value="'.$$fullname{$env{'form.student'}}.'" />'."\n".
                   5051: 	'<input type="hidden" name="student" value="'.$env{'form.student'}.'" />'."\n".
1.72      ng       5052: 	'<input type="hidden" name="page"    value="'.$pageTitle.'" />'."\n".
1.257     albertel 5053: 	'<input type="hidden" name="title"   value="'.$env{'form.title'}.'" />'."\n".
1.418     albertel 5054: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1.125     ng       5055: 	'<input type="hidden" name="overRideScore" value="no" />'."\n".
1.257     albertel 5056: 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n";
1.71      ng       5057: 
1.382     albertel 5058:     if (defined($env{'form.CODE'})) {
                   5059: 	$studentTable.=
                   5060: 	    '<input type="hidden" name="CODE" value="'.$env{'form.CODE'}.'" />'."\n";
                   5061:     }
1.381     albertel 5062:     my $checkIcon = '<img alt="'.&mt('Check Mark').
1.485     albertel 5063: 	'" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
1.71      ng       5064: 
1.594     bisitz   5065:     $studentTable.='&nbsp;<span class="LC_info">'.
                   5066:         &mt('Problems graded correct by the computer are marked with a [_1] symbol.',$checkIcon).
                   5067:         '</span>'."\n".
1.484     albertel 5068: 	&Apache::loncommon::start_data_table().
                   5069: 	&Apache::loncommon::start_data_table_header_row().
                   5070: 	'<th align="center">&nbsp;Prob.&nbsp;</th>'.
1.485     albertel 5071: 	'<th>&nbsp;'.($env{'form.vProb'} eq 'no' ? &mt('Title') : &mt('Problem Text')).'/'.&mt('Grade').'</th>'.
1.484     albertel 5072: 	&Apache::loncommon::end_data_table_header_row();
1.71      ng       5073: 
1.329     albertel 5074:     &Apache::lonxml::clear_problem_counter();
1.196     albertel 5075:     my ($depth,$question,$prob) = (1,1,1);
1.68      ng       5076:     $iterator->next(); # skip the first BEGIN_MAP
                   5077:     my $curRes = $iterator->next(); # for "current resource"
1.101     albertel 5078:     while ($depth > 0) {
1.68      ng       5079:         if($curRes == $iterator->BEGIN_MAP) { $depth++; }
1.100     bowersj2 5080:         if($curRes == $iterator->END_MAP) { $depth--; }
1.68      ng       5081: 
1.385     albertel 5082:         if (ref($curRes) && $curRes->is_problem()) {
1.91      albertel 5083: 	    my $parts = $curRes->parts();
1.68      ng       5084:             my $title = $curRes->compTitle();
1.71      ng       5085: 	    my $symbx = $curRes->symb();
1.484     albertel 5086: 	    $studentTable.=
                   5087: 		&Apache::loncommon::start_data_table_row().
                   5088: 		'<td align="center" valign="top" >'.$prob.
1.485     albertel 5089: 		(scalar(@{$parts}) == 1 ? '' 
1.596.2.12.2.  2(raebur 5090:2): 		                        : '<br />('.&mt('[_1]parts',
                   5091:2): 							scalar(@{$parts}).'&nbsp;').')'
1.485     albertel 5092: 		 ).
                   5093: 		 '</td>';
1.71      ng       5094: 	    $studentTable.='<td valign="top">';
1.382     albertel 5095: 	    my %form = ('CODE' => $env{'form.CODE'},);
1.257     albertel 5096: 	    if ($env{'form.vProb'} eq 'yes' ) {
1.144     albertel 5097: 		$studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
1.383     albertel 5098: 					     undef,'both',\%form);
1.71      ng       5099: 	    } else {
1.382     albertel 5100: 		my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'},%form);
1.80      ng       5101: 		$companswer =~ s|<form(.*?)>||g;
                   5102: 		$companswer =~ s|</form>||g;
1.71      ng       5103: #		while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a>
1.116     ng       5104: #		    $companswer =~ s/$1/ /ms;
1.326     albertel 5105: #		    $request->print('match='.$1."<br />\n");
1.71      ng       5106: #		}
1.116     ng       5107: #		$companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;
1.539     riegler  5108: 		$studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br />&nbsp;<b>'.&mt('Correct answer').':</b><br />'.$companswer;
1.71      ng       5109: 	    }
                   5110: 
1.257     albertel 5111: 	    my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
1.125     ng       5112: 
1.257     albertel 5113: 	    if ($env{'form.lastSub'} eq 'datesub') {
1.71      ng       5114: 		if ($record{'version'} eq '') {
1.485     albertel 5115: 		    $studentTable.='<br />&nbsp;<span class="LC_warning">'.&mt('No recorded submission for this problem.').'</span><br />';
1.71      ng       5116: 		} else {
1.116     ng       5117: 		    my %responseType = ();
                   5118: 		    foreach my $partid (@{$parts}) {
1.147     albertel 5119: 			my @responseIds =$curRes->responseIds($partid);
                   5120: 			my @responseType =$curRes->responseType($partid);
                   5121: 			my %responseIds;
                   5122: 			for (my $i=0;$i<=$#responseIds;$i++) {
                   5123: 			    $responseIds{$responseIds[$i]}=$responseType[$i];
                   5124: 			}
                   5125: 			$responseType{$partid} = \%responseIds;
1.116     ng       5126: 		    }
1.148     albertel 5127: 		    $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom);
1.147     albertel 5128: 
1.71      ng       5129: 		}
1.257     albertel 5130: 	    } elsif ($env{'form.lastSub'} eq 'all') {
                   5131: 		my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
1.596.2.12.2.  1(raebur 5132:5):                 my $identifier = (&canmodify($usec)? $prob : '');
1.71      ng       5133: 		$studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom,
1.257     albertel 5134: 									$env{'request.course.id'},
1.596.2.12.2.  1(raebur 5135:5): 									'','.submission',undef,
                   5136:5):                                                                         $usec,$identifier);
1.71      ng       5137:  
                   5138: 	    }
1.103     albertel 5139: 	    if (&canmodify($usec)) {
1.585     bisitz   5140:             $studentTable.=&gradeBox_start();
1.103     albertel 5141: 		foreach my $partid (@{$parts}) {
                   5142: 		    $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record);
                   5143: 		    $studentTable.='<input type="hidden" name="q_'.$question.'" value="'.$partid.'" />'."\n";
                   5144: 		    $question++;
                   5145: 		}
1.585     bisitz   5146:             $studentTable.=&gradeBox_end();
1.196     albertel 5147: 		$prob++;
1.71      ng       5148: 	    }
                   5149: 	    $studentTable.='</td></tr>';
1.68      ng       5150: 
1.103     albertel 5151: 	}
1.68      ng       5152:         $curRes = $iterator->next();
                   5153:     }
                   5154: 
1.589     bisitz   5155:     $studentTable.=
                   5156:         '</table>'."\n".
                   5157:         '<input type="button" value="'.&mt('Save').'" '.
                   5158:         'onclick="javascript:checkSubmitPage(this.form,'.$question.');" />'.
                   5159:         '</form>'."\n";
1.324     albertel 5160:     $studentTable.=&show_grading_menu_form($symb);
1.71      ng       5161:     $request->print($studentTable);
                   5162: 
                   5163:     return '';
1.119     ng       5164: }
                   5165: 
                   5166: sub displaySubByDates {
1.148     albertel 5167:     my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;
1.224     albertel 5168:     my $isCODE=0;
1.335     albertel 5169:     my $isTask = ($symb =~/\.task$/);
1.224     albertel 5170:     if (exists($record->{'resource.CODE'})) { $isCODE=1; }
1.467     albertel 5171:     my $studentTable=&Apache::loncommon::start_data_table().
                   5172: 	&Apache::loncommon::start_data_table_header_row().
                   5173: 	'<th>'.&mt('Date/Time').'</th>'.
                   5174: 	($isCODE?'<th>'.&mt('CODE').'</th>':'').
1.596.2.12.2.  (raeburn 5175:):         ($isTask?'<th>'.&mt('Version').'</th>':'').
1.467     albertel 5176: 	'<th>'.&mt('Submission').'</th>'.
                   5177: 	'<th>'.&mt('Status').'</th>'.
                   5178: 	&Apache::loncommon::end_data_table_header_row();
1.119     ng       5179:     my ($version);
                   5180:     my %mark;
1.148     albertel 5181:     my %orders;
1.119     ng       5182:     $mark{'correct_by_student'} = $checkIcon;
1.147     albertel 5183:     if (!exists($$record{'1:timestamp'})) {
1.539     riegler  5184: 	return '<br />&nbsp;<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br />';
1.147     albertel 5185:     }
1.335     albertel 5186: 
                   5187:     my $interaction;
1.525     raeburn  5188:     my $no_increment = 1;
1.596.2.12.2.  5(raebur 5189:5):     my (%lastrndseed,%lasttype);
1.119     ng       5190:     for ($version=1;$version<=$$record{'version'};$version++) {
1.467     albertel 5191: 	my $timestamp = 
                   5192: 	    &Apache::lonlocal::locallocaltime($$record{$version.':timestamp'});
1.335     albertel 5193: 	if (exists($$record{$version.':resource.0.version'})) {
                   5194: 	    $interaction = $$record{$version.':resource.0.version'};
                   5195: 	}
1.596.2.12.2.  (raeburn 5196:):         if ($isTask && $env{'form.previousversion'}) {
                   5197:):             next unless ($interaction == $env{'form.previousversion'});
                   5198:):         }
1.335     albertel 5199: 	my $where = ($isTask ? "$version:resource.$interaction"
                   5200: 		             : "$version:resource");
1.467     albertel 5201: 	$studentTable.=&Apache::loncommon::start_data_table_row().
                   5202: 	    '<td>'.$timestamp.'</td>';
1.224     albertel 5203: 	if ($isCODE) {
                   5204: 	    $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';
                   5205: 	}
1.596.2.12.2.  (raeburn 5206:):         if ($isTask) {
                   5207:):             $studentTable.='<td>'.$interaction.'</td>';
                   5208:):         }
1.119     ng       5209: 	my @versionKeys = split(/\:/,$$record{$version.':keys'});
                   5210: 	my @displaySub = ();
                   5211: 	foreach my $partid (@{$parts}) {
1.596.2.2  raeburn  5212:             my ($hidden,$type);
                   5213:             $type = $$record{$version.':resource.'.$partid.'.type'};
                   5214:             if (($type eq 'anonsurvey') || ($type eq 'anonsurveycred')) {
1.596     raeburn  5215:                 $hidden = 1;
                   5216:             }
1.335     albertel 5217: 	    my @matchKey = ($isTask ? sort(grep /^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys)
                   5218: 			            : sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys));
                   5219: 	    
1.122     ng       5220: #	    next if ($$record{"$version:resource.$partid.solved"} eq '');
1.324     albertel 5221: 	    my $display_part=&get_display_part($partid,$symb);
1.147     albertel 5222: 	    foreach my $matchKey (@matchKey) {
1.198     albertel 5223: 		if (exists($$record{$version.':'.$matchKey}) &&
                   5224: 		    $$record{$version.':'.$matchKey} ne '') {
1.596     raeburn  5225:                     
1.335     albertel 5226: 		    my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
                   5227: 				               : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
1.596.2.12.2.  (raeburn 5228:):                     $displaySub[0].='<span class="LC_nobreak">';
1.577     bisitz   5229:                     $displaySub[0].='<b>'.&mt('Part: [_1]',$display_part).'</b>'
                   5230:                                    .' <span class="LC_internal_info">'
1.596.2.4  raeburn  5231:                                    .'('.&mt('Response ID: [_1]',$responseId).')'
1.577     bisitz   5232:                                    .'</span>'
                   5233:                                    .' <b>';
1.596     raeburn  5234:                     if ($hidden) {
                   5235:                         $displaySub[0].= &mt('Anonymous Survey').'</b>';
                   5236:                     } else {
1.596.2.2  raeburn  5237:                         my ($trial,$rndseed,$newvariation);
                   5238:                         if ($type eq 'randomizetry') {
                   5239:                             $trial = $$record{"$where.$partid.tries"};
                   5240:                             $rndseed = $$record{"$where.$partid.rndseed"};
                   5241:                         }
1.596     raeburn  5242: 		        if ($$record{"$where.$partid.tries"} eq '') {
                   5243: 			    $displaySub[0].=&mt('Trial not counted');
                   5244: 		        } else {
                   5245: 			    $displaySub[0].=&mt('Trial: [_1]',
1.467     albertel 5246: 					    $$record{"$where.$partid.tries"});
1.596.2.12.2.  4(raebur 5247:5):                             if (($rndseed ne '')  && ($lastrndseed{$partid} ne '')) {
          5(raebur 5248:5):                                 if (($rndseed ne $lastrndseed{$partid}) &&
                   5249:5):                                     (($type eq 'randomizetry') || ($lasttype{$partid} eq 'randomizetry'))) {
1.596.2.2  raeburn  5250:                                     $newvariation = '&nbsp;('.&mt('New variation this try').')';
                   5251:                                 }
                   5252:                             }
1.596.2.12.2.  4(raebur 5253:5):                             $lastrndseed{$partid} = $rndseed;
          5(raebur 5254:5):                             $lasttype{$partid} = $type;
1.596     raeburn  5255: 		        }
                   5256: 		        my $responseType=($isTask ? 'Task'
1.335     albertel 5257:                                               : $responseType->{$partid}->{$responseId});
1.596     raeburn  5258: 		        if (!exists($orders{$partid})) { $orders{$partid}={}; }
1.596.2.2  raeburn  5259: 		        if ((!exists($orders{$partid}->{$responseId})) || ($trial)) {
1.596     raeburn  5260: 			    $orders{$partid}->{$responseId}=
                   5261: 			        &get_order($partid,$responseId,$symb,$uname,$udom,
1.596.2.2  raeburn  5262:                                            $no_increment,$type,$trial,$rndseed);
1.596     raeburn  5263: 		        }
1.596.2.2  raeburn  5264: 		        $displaySub[0].='</b>'.$newvariation.'</span>'; # /nobreak
1.596     raeburn  5265: 		        $displaySub[0].='&nbsp; '.
1.596.2.2  raeburn  5266: 			    &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom,$type,$trial,$rndseed).'<br />';
1.596     raeburn  5267:                     }
1.147     albertel 5268: 		}
                   5269: 	    }
1.335     albertel 5270: 	    if (exists($$record{"$where.$partid.checkedin"})) {
1.485     albertel 5271: 		$displaySub[1].=&mt('Checked in by [_1] into slot [_2]',
                   5272: 				    $$record{"$where.$partid.checkedin"},
                   5273: 				    $$record{"$where.$partid.checkedin.slot"}).
                   5274: 					'<br />';
1.335     albertel 5275: 	    }
                   5276: 	    if (exists $$record{"$where.$partid.award"}) {
1.485     albertel 5277: 		$displaySub[1].='<b>'.&mt('Part:').'</b>&nbsp;'.$display_part.' &nbsp;'.
1.335     albertel 5278: 		    lc($$record{"$where.$partid.award"}).' '.
                   5279: 		    $mark{$$record{"$where.$partid.solved"}}.
1.147     albertel 5280: 		    '<br />';
                   5281: 	    }
1.335     albertel 5282: 	    if (exists $$record{"$where.$partid.regrader"}) {
                   5283: 		$displaySub[2].=$$record{"$where.$partid.regrader"}.
                   5284: 		    ' (<b>'.&mt('Part').':</b> '.$display_part.')';
                   5285: 	    } elsif ($$record{"$version:resource.$partid.regrader"} =~ /\S/) {
                   5286: 		$displaySub[2].=
                   5287: 		    $$record{"$version:resource.$partid.regrader"}.
1.207     albertel 5288: 		    ' (<b>'.&mt('Part').':</b> '.$display_part.')';
1.147     albertel 5289: 	    }
                   5290: 	}
                   5291: 	# needed because old essay regrader has not parts info
                   5292: 	if (exists $$record{"$version:resource.regrader"}) {
                   5293: 	    $displaySub[2].=$$record{"$version:resource.regrader"};
                   5294: 	}
                   5295: 	$studentTable.='<td>'.$displaySub[0].'&nbsp;</td><td>'.$displaySub[1];
                   5296: 	if ($displaySub[2]) {
1.467     albertel 5297: 	    $studentTable.=&mt('Manually graded by [_1]',$displaySub[2]);
1.147     albertel 5298: 	}
1.467     albertel 5299: 	$studentTable.='&nbsp;</td>'.
                   5300: 	    &Apache::loncommon::end_data_table_row();
1.119     ng       5301:     }
1.467     albertel 5302:     $studentTable.=&Apache::loncommon::end_data_table();
1.119     ng       5303:     return $studentTable;
1.71      ng       5304: }
                   5305: 
                   5306: sub updateGradeByPage {
                   5307:     my ($request) = shift;
                   5308: 
1.257     albertel 5309:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
                   5310:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
                   5311:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
                   5312:     my $pageTitle = $env{'form.page'};
1.103     albertel 5313:     my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
1.257     albertel 5314:     my ($uname,$udom) = split(/:/,$env{'form.student'});
                   5315:     my $usec=$classlist->{$env{'form.student'}}[5];
1.103     albertel 5316:     if (!&canmodify($usec)) {
1.526     raeburn  5317: 	$request->print('<span class="LC_warning">'.&mt('Unable to modify requested student ([_1])',$env{'form.student'}).'</span>');
1.324     albertel 5318: 	$request->print(&show_grading_menu_form($env{'form.symb'}));
1.103     albertel 5319: 	return;
                   5320:     }
1.398     albertel 5321:     my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
1.526     raeburn  5322:     $result.='<h3>&nbsp;'.&mt('Student: ').&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
1.129     ng       5323: 	'</h3>'."\n";
1.70      ng       5324: 
1.68      ng       5325:     $request->print($result);
                   5326: 
1.582     raeburn  5327: 
1.132     bowersj2 5328:     my $navmap = Apache::lonnavmaps::navmap->new();
1.582     raeburn  5329:     unless (ref($navmap)) {
                   5330:         $request->print(&navmap_errormsg());
                   5331:         return;
                   5332:     }
1.257     albertel 5333:     my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});
1.71      ng       5334:     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
1.288     albertel 5335:     if (!$map) {
1.527     raeburn  5336: 	$request->print('<span class="LC_warning">'.&mt('Unable to grade requested sequence ([_1]).',$resUrl).'</span>');
1.324     albertel 5337: 	my ($symb)=&get_symb($request);
                   5338: 	$request->print(&show_grading_menu_form($symb));
1.288     albertel 5339: 	return; 
                   5340:     }
1.71      ng       5341:     my $iterator = $navmap->getIterator($map->map_start(),
                   5342: 					$map->map_finish());
1.70      ng       5343: 
1.484     albertel 5344:     my $studentTable=
                   5345: 	&Apache::loncommon::start_data_table().
                   5346: 	&Apache::loncommon::start_data_table_header_row().
1.485     albertel 5347: 	'<th align="center">&nbsp;'.&mt('Prob.').'&nbsp;</th>'.
                   5348: 	'<th>&nbsp;'.&mt('Title').'&nbsp;</th>'.
                   5349: 	'<th>&nbsp;'.&mt('Previous Score').'&nbsp;</th>'.
                   5350: 	'<th>&nbsp;'.&mt('New Score').'&nbsp;</th>'.
1.484     albertel 5351: 	&Apache::loncommon::end_data_table_header_row();
1.71      ng       5352: 
                   5353:     $iterator->next(); # skip the first BEGIN_MAP
                   5354:     my $curRes = $iterator->next(); # for "current resource"
1.596.2.12.2.  1(raebur 5355:5):     my ($depth,$question,$prob,$changeflag,$hideflag)= (1,1,1,0,0);
1.101     albertel 5356:     while ($depth > 0) {
1.71      ng       5357:         if($curRes == $iterator->BEGIN_MAP) { $depth++; }
1.100     bowersj2 5358:         if($curRes == $iterator->END_MAP) { $depth--; }
1.71      ng       5359: 
1.385     albertel 5360:         if (ref($curRes) && $curRes->is_problem()) {
1.91      albertel 5361: 	    my $parts = $curRes->parts();
1.71      ng       5362:             my $title = $curRes->compTitle();
                   5363: 	    my $symbx = $curRes->symb();
1.484     albertel 5364: 	    $studentTable.=
                   5365: 		&Apache::loncommon::start_data_table_row().
                   5366: 		'<td align="center" valign="top" >'.$prob.
1.485     albertel 5367: 		(scalar(@{$parts}) == 1 ? '' 
1.596.2.2  raeburn  5368:                                         : '<br />('.&mt('[quant,_1,part]',scalar(@{$parts}))
1.526     raeburn  5369: 		.')').'</td>';
1.71      ng       5370: 	    $studentTable.='<td valign="top">&nbsp;<b>'.$title.'</b>&nbsp;</td>';
                   5371: 
                   5372: 	    my %newrecord=();
                   5373: 	    my @displayPts=();
1.269     raeburn  5374:             my %aggregate = ();
                   5375:             my $aggregateflag = 0;
1.596.2.12.2.  1(raebur 5376:5):             if ($env{'form.HIDE'.$prob}) {
                   5377:5):                 my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
                   5378:5):                 my ($version,$parts) = split(/:/,$env{'form.HIDE'.$prob},2);
                   5379:5):                 my $numchgs = &makehidden($version,$parts,\%record,$symbx,$udom,$uname,1);
                   5380:5):                 $hideflag += $numchgs;
                   5381:5):             }
1.71      ng       5382: 	    foreach my $partid (@{$parts}) {
1.257     albertel 5383: 		my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
                   5384: 		my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
1.71      ng       5385: 
1.257     albertel 5386: 		my $wgt = $env{'form.WGT'.$question.'_'.$partid} != 0 ? 
                   5387: 		    $env{'form.WGT'.$question.'_'.$partid} : 1;
1.71      ng       5388: 		my $partial = $newpts/$wgt;
                   5389: 		my $score;
                   5390: 		if ($partial > 0) {
                   5391: 		    $score = 'correct_by_override';
1.125     ng       5392: 		} elsif ($newpts ne '') { #empty is taken as 0
1.71      ng       5393: 		    $score = 'incorrect_by_override';
                   5394: 		}
1.257     albertel 5395: 		my $dropMenu = $env{'form.GD_SEL'.$question.'_'.$partid};
1.125     ng       5396: 		if ($dropMenu eq 'excused') {
1.71      ng       5397: 		    $partial = '';
                   5398: 		    $score = 'excused';
1.125     ng       5399: 		} elsif ($dropMenu eq 'reset status'
1.257     albertel 5400: 			 && $env{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists
1.125     ng       5401: 		    $newrecord{'resource.'.$partid.'.tries'} = 0;
                   5402: 		    $newrecord{'resource.'.$partid.'.solved'} = '';
                   5403: 		    $newrecord{'resource.'.$partid.'.award'} = '';
                   5404: 		    $newrecord{'resource.'.$partid.'.awarded'} = 0;
1.257     albertel 5405: 		    $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}";
1.125     ng       5406: 		    $changeflag++;
                   5407: 		    $newpts = '';
1.269     raeburn  5408:                     
                   5409:                     my $aggtries =  $env{'form.aggtries'.$question.'_'.$partid};
                   5410:                     my $totaltries = $env{'form.totaltries'.$question.'_'.$partid};
                   5411:                     my $solvedstatus = $env{'form.solved'.$question.'_'.$partid};
                   5412:                     if ($aggtries > 0) {
                   5413:                         &decrement_aggs($symbx,$partid,\%aggregate,$aggtries,$totaltries,$solvedstatus);
                   5414:                         $aggregateflag = 1;
                   5415:                     }
1.71      ng       5416: 		}
1.324     albertel 5417: 		my $display_part=&get_display_part($partid,$curRes->symb());
1.257     albertel 5418: 		my $oldstatus = $env{'form.solved'.$question.'_'.$partid};
1.526     raeburn  5419: 		$displayPts[0].='&nbsp;<b>'.&mt('Part').':</b> '.$display_part.' = '.
1.71      ng       5420: 		    (($oldstatus eq 'excused') ? 'excused' : $oldpts).
1.326     albertel 5421: 		    '&nbsp;<br />';
1.526     raeburn  5422: 		$displayPts[1].='&nbsp;<b>'.&mt('Part').':</b> '.$display_part.' = '.
1.125     ng       5423: 		     (($score eq 'excused') ? 'excused' : $newpts).
1.326     albertel 5424: 		    '&nbsp;<br />';
1.71      ng       5425: 		$question++;
1.380     albertel 5426: 		next if ($dropMenu eq 'reset status' || ($newpts eq $oldpts && $score ne 'excused'));
1.125     ng       5427: 
1.71      ng       5428: 		$newrecord{'resource.'.$partid.'.awarded'}  = $partial if $partial ne '';
1.125     ng       5429: 		$newrecord{'resource.'.$partid.'.solved'}   = $score if $score ne '';
1.257     albertel 5430: 		$newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}"
1.125     ng       5431: 		    if (scalar(keys(%newrecord)) > 0);
1.71      ng       5432: 
                   5433: 		$changeflag++;
                   5434: 	    }
                   5435: 	    if (scalar(keys(%newrecord)) > 0) {
1.382     albertel 5436: 		my %record = 
                   5437: 		    &Apache::lonnet::restore($symbx,$env{'request.course.id'},
                   5438: 					     $udom,$uname);
                   5439: 
                   5440: 		if (&Apache::lonnet::validCODE($env{'form.CODE'})) {
                   5441: 		    $newrecord{'resource.CODE'} = $env{'form.CODE'};
                   5442: 		} elsif (&Apache::lonnet::validCODE($record{'resource.CODE'})) {
                   5443: 		    $newrecord{'resource.CODE'} = '';
                   5444: 		}
1.257     albertel 5445: 		&Apache::lonnet::cstore(\%newrecord,$symbx,$env{'request.course.id'},
1.71      ng       5446: 					$udom,$uname);
1.382     albertel 5447: 		%record = &Apache::lonnet::restore($symbx,
                   5448: 						   $env{'request.course.id'},
                   5449: 						   $udom,$uname);
1.380     albertel 5450: 		&check_and_remove_from_queue($parts,\%record,undef,$symbx,
                   5451: 					     $cdom,$cnum,$udom,$uname);
1.71      ng       5452: 	    }
1.380     albertel 5453: 	    
1.269     raeburn  5454:             if ($aggregateflag) {
                   5455:                 &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
                   5456:                       $env{'course.'.$env{'request.course.id'}.'.domain'},
                   5457:                       $env{'course.'.$env{'request.course.id'}.'.num'});
                   5458:             }
1.125     ng       5459: 
1.71      ng       5460: 	    $studentTable.='<td valign="top">'.$displayPts[0].'</td>'.
                   5461: 		'<td valign="top">'.$displayPts[1].'</td>'.
1.484     albertel 5462: 		&Apache::loncommon::end_data_table_row();
1.68      ng       5463: 
1.196     albertel 5464: 	    $prob++;
1.68      ng       5465: 	}
1.71      ng       5466:         $curRes = $iterator->next();
1.68      ng       5467:     }
1.98      albertel 5468: 
1.484     albertel 5469:     $studentTable.=&Apache::loncommon::end_data_table();
1.324     albertel 5470:     $studentTable.=&show_grading_menu_form($env{'form.symb'});
1.526     raeburn  5471:     my $grademsg=($changeflag == 0 ? &mt('No score was changed or updated.') :
                   5472: 		  &mt('The scores were changed for [quant,_1,problem].',
1.596.2.12.2.  1(raebur 5473:5): 		  $changeflag).'<br />');
                   5474:5):     my $hidemsg=($hideflag == 0 ? '' :
                   5475:5):                  &mt('Submissions were marked "hidden" for [quant,_1,transaction].',
                   5476:5):                      $hideflag).'<br />');
                   5477:5):     $request->print($hidemsg.$grademsg.$studentTable);
1.68      ng       5478: 
1.70      ng       5479:     return '';
                   5480: }
                   5481: 
1.72      ng       5482: #-------- end of section for handling grading by page/sequence ---------
                   5483: #
                   5484: #-------------------------------------------------------------------
                   5485: 
1.581     www      5486: #-------------------- Bubblesheet (Scantron) Grading -------------------
1.75      albertel 5487: #
                   5488: #------ start of section for handling grading by page/sequence ---------
                   5489: 
1.423     albertel 5490: =pod
                   5491: 
                   5492: =head1 Bubble sheet grading routines
                   5493: 
1.424     albertel 5494:   For this documentation:
                   5495: 
                   5496:    'scanline' refers to the full line of characters
                   5497:    from the file that we are parsing that represents one entire sheet
                   5498: 
                   5499:    'bubble line' refers to the data
1.596.2.6  raeburn  5500:    representing the line of bubbles that are on the physical bubblesheet
1.424     albertel 5501: 
                   5502: 
1.596.2.6  raeburn  5503: The overall process is that a scanned in bubblesheet data is uploaded
1.424     albertel 5504: into a course. When a user wants to grade, they select a
1.596.2.6  raeburn  5505: sequence/folder of resources, a file of bubblesheet info, and pick
1.424     albertel 5506: one of the predefined configurations for what each scanline looks
                   5507: like.
                   5508: 
                   5509: Next each scanline is checked for any errors of either 'missing
1.435     foxr     5510: bubbles' (it's an error because it may have been mis-scanned
1.424     albertel 5511: because too light bubbling), 'double bubble' (each bubble line should
1.596.2.12.2.  0(raebur 5512:3): have no more than one letter picked), invalid or duplicated CODE,
1.556     weissno  5513: invalid student/employee ID
1.424     albertel 5514: 
                   5515: If the CODE option is used that determines the randomization of the
1.556     weissno  5516: homework problems, either way the student/employee ID is looked up into a
1.424     albertel 5517: username:domain.
                   5518: 
                   5519: During the validation phase the instructor can choose to skip scanlines. 
                   5520: 
1.596.2.6  raeburn  5521: After the validation phase, there are now 3 bubblesheet files
1.424     albertel 5522: 
                   5523:   scantron_original_filename (unmodified original file)
                   5524:   scantron_corrected_filename (file where the corrected information has replaced the original information)
                   5525:   scantron_skipped_filename (contains the exact text of scanlines that where skipped)
                   5526: 
                   5527: Also there is a separate hash nohist_scantrondata that contains extra
1.596.2.6  raeburn  5528: correction information that isn't representable in the bubblesheet
1.424     albertel 5529: file (see &scantron_getfile() for more information)
                   5530: 
                   5531: After all scanlines are either valid, marked as valid or skipped, then
                   5532: foreach line foreach problem in the picked sequence, an ssi request is
                   5533: made that simulates a user submitting their selected letter(s) against
                   5534: the homework problem.
1.423     albertel 5535: 
                   5536: =over 4
                   5537: 
                   5538: 
                   5539: 
                   5540: =item defaultFormData
                   5541: 
                   5542:   Returns html hidden inputs used to hold context/default values.
                   5543: 
                   5544:  Arguments:
                   5545:   $symb - $symb of the current resource 
                   5546: 
                   5547: =cut
1.422     foxr     5548: 
1.81      albertel 5549: sub defaultFormData {
1.324     albertel 5550:     my ($symb)=@_;
1.447     foxr     5551:     return '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1.257     albertel 5552:      '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
                   5553:      '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
1.81      albertel 5554: }
                   5555: 
1.447     foxr     5556: 
1.423     albertel 5557: =pod 
                   5558: 
                   5559: =item getSequenceDropDown
                   5560: 
                   5561:    Return html dropdown of possible sequences to grade
                   5562:  
                   5563:  Arguments:
1.582     raeburn  5564:    $symb - $symb of the current resource
                   5565:    $map_error - ref to scalar which will container error if
                   5566:                 $navmap object is unavailable in &getSymbMap().
1.423     albertel 5567: 
                   5568: =cut
1.422     foxr     5569: 
1.75      albertel 5570: sub getSequenceDropDown {
1.582     raeburn  5571:     my ($symb,$map_error)=@_;
1.75      albertel 5572:     my $result='<select name="selectpage">'."\n";
1.582     raeburn  5573:     my ($titles,$symbx) = &getSymbMap($map_error);
                   5574:     if (ref($map_error)) {
                   5575:         return if ($$map_error);
                   5576:     }
1.137     albertel 5577:     my ($curpage)=&Apache::lonnet::decode_symb($symb); 
1.75      albertel 5578:     my $ctr=0;
                   5579:     foreach (@$titles) {
                   5580: 	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
                   5581: 	$result.='<option value="'.$$symbx{$_}.'" '.
1.401     albertel 5582: 	    ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
1.75      albertel 5583: 	    '>'.$showtitle.'</option>'."\n";
                   5584: 	$ctr++;
                   5585:     }
                   5586:     $result.= '</select>';
                   5587:     return $result;
                   5588: }
                   5589: 
1.495     albertel 5590: my %bubble_lines_per_response;     # no. bubble lines for each response.
1.554     raeburn  5591:                                    # key is zero-based index - 0, 1, 2 ...
1.495     albertel 5592: 
                   5593: my %first_bubble_line;             # First bubble line no. for each bubble.
                   5594: 
1.509     raeburn  5595: my %subdivided_bubble_lines;       # no. bubble lines for optionresponse, 
                   5596:                                    # matchresponse or rankresponse, where 
                   5597:                                    # an individual response can have multiple 
                   5598:                                    # lines
1.503     raeburn  5599: 
                   5600: my %responsetype_per_response;     # responsetype for each response
                   5601: 
1.596.2.12.2.  6(raebur 5602:3): my %masterseq_id_responsenum;      # src_id (e.g., 12.3_0.11 etc.) for each
                   5603:3):                                    # numbered response. Needed when randomorder
                   5604:3):                                    # or randompick are in use. Key is ID, value 
                   5605:3):                                    # is response number.
                   5606:3): 
1.495     albertel 5607: # Save and restore the bubble lines array to the form env.
                   5608: 
                   5609: 
                   5610: sub save_bubble_lines {
                   5611:     foreach my $line (keys(%bubble_lines_per_response)) {
                   5612: 	$env{"form.scantron.bubblelines.$line"}  = $bubble_lines_per_response{$line};
                   5613: 	$env{"form.scantron.first_bubble_line.$line"} =
                   5614: 	    $first_bubble_line{$line};
1.503     raeburn  5615:         $env{"form.scantron.sub_bubblelines.$line"} = 
                   5616:             $subdivided_bubble_lines{$line};
                   5617:         $env{"form.scantron.responsetype.$line"} =
                   5618:             $responsetype_per_response{$line};
1.495     albertel 5619:     }
1.596.2.12.2.  6(raebur 5620:3):     foreach my $resid (keys(%masterseq_id_responsenum)) {
                   5621:3):         my $line = $masterseq_id_responsenum{$resid};
                   5622:3):         $env{"form.scantron.residpart.$line"} = $resid;
                   5623:3):     }
1.495     albertel 5624: }
                   5625: 
                   5626: 
                   5627: sub restore_bubble_lines {
                   5628:     my $line = 0;
                   5629:     %bubble_lines_per_response = ();
1.596.2.12.2.  6(raebur 5630:3):     %masterseq_id_responsenum = ();
1.495     albertel 5631:     while ($env{"form.scantron.bubblelines.$line"}) {
                   5632: 	my $value = $env{"form.scantron.bubblelines.$line"};
                   5633: 	$bubble_lines_per_response{$line} = $value;
                   5634: 	$first_bubble_line{$line}  =
                   5635: 	    $env{"form.scantron.first_bubble_line.$line"};
1.503     raeburn  5636:         $subdivided_bubble_lines{$line} =
                   5637:             $env{"form.scantron.sub_bubblelines.$line"};
                   5638:         $responsetype_per_response{$line} =
                   5639:             $env{"form.scantron.responsetype.$line"};
1.596.2.12.2.  6(raebur 5640:3):         my $id = $env{"form.scantron.residpart.$line"};
                   5641:3):         $masterseq_id_responsenum{$id} = $line;
1.495     albertel 5642: 	$line++;
                   5643:     }
                   5644: }
                   5645: 
1.423     albertel 5646: =pod 
                   5647: 
                   5648: =item scantron_filenames
                   5649: 
                   5650:    Returns a list of the scantron files in the current course 
                   5651: 
                   5652: =cut
1.422     foxr     5653: 
1.202     albertel 5654: sub scantron_filenames {
1.257     albertel 5655:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
                   5656:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
1.517     raeburn  5657:     my $getpropath = 1;
1.596.2.12.2.  (raeburn 5658:):     my ($dirlist,$listerror) = &Apache::lonnet::dirlist('userfiles',$cdom,
                   5659:):                                                         $cname,$getpropath);
1.202     albertel 5660:     my @possiblenames;
1.596.2.12.2.  (raeburn 5661:):     if (ref($dirlist) eq 'ARRAY') {
                   5662:):         foreach my $filename (sort(@{$dirlist})) {
                   5663:): 	    ($filename)=split(/&/,$filename);
                   5664:): 	    if ($filename!~/^scantron_orig_/) { next ; }
                   5665:): 	    $filename=~s/^scantron_orig_//;
                   5666:): 	    push(@possiblenames,$filename);
                   5667:):         }
1.202     albertel 5668:     }
                   5669:     return @possiblenames;
                   5670: }
                   5671: 
1.423     albertel 5672: =pod 
                   5673: 
                   5674: =item scantron_uploads
                   5675: 
                   5676:    Returns  html drop-down list of scantron files in current course.
                   5677: 
                   5678:  Arguments:
                   5679:    $file2grade - filename to set as selected in the dropdown
                   5680: 
                   5681: =cut
1.422     foxr     5682: 
1.202     albertel 5683: sub scantron_uploads {
1.209     ng       5684:     my ($file2grade) = @_;
1.202     albertel 5685:     my $result=	'<select name="scantron_selectfile">';
                   5686:     $result.="<option></option>";
                   5687:     foreach my $filename (sort(&scantron_filenames())) {
1.401     albertel 5688: 	$result.="<option".($filename eq $file2grade ? ' selected="selected"':'').">$filename</option>\n";
1.81      albertel 5689:     }
                   5690:     $result.="</select>";
                   5691:     return $result;
                   5692: }
                   5693: 
1.423     albertel 5694: =pod 
                   5695: 
                   5696: =item scantron_scantab
                   5697: 
                   5698:   Returns html drop down of the scantron formats in the scantronformat.tab
                   5699:   file.
                   5700: 
                   5701: =cut
1.422     foxr     5702: 
1.82      albertel 5703: sub scantron_scantab {
                   5704:     my $result='<select name="scantron_format">'."\n";
1.191     albertel 5705:     $result.='<option></option>'."\n";
1.518     raeburn  5706:     my @lines = &get_scantronformat_file();
                   5707:     if (@lines > 0) {
                   5708:         foreach my $line (@lines) {
                   5709:             next if (($line =~ /^\#/) || ($line eq ''));
                   5710: 	    my ($name,$descrip)=split(/:/,$line);
                   5711: 	    $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";
                   5712:         }
1.82      albertel 5713:     }
                   5714:     $result.='</select>'."\n";
1.518     raeburn  5715:     return $result;
                   5716: }
                   5717: 
                   5718: =pod
                   5719: 
                   5720: =item get_scantronformat_file
                   5721: 
                   5722:   Returns an array containing lines from the scantron format file for
                   5723:   the domain of the course.
                   5724: 
                   5725:   If a url for a custom.tab file is listed in domain's configuration.db, 
                   5726:   lines are from this file.
                   5727: 
                   5728:   Otherwise, if a default.tab has been published in RES space by the 
                   5729:   domainconfig user, lines are from this file.
                   5730: 
                   5731:   Otherwise, fall back to getting lines from the legacy file on the
1.519     raeburn  5732:   local server:  /home/httpd/lonTabs/default_scantronformat.tab    
1.82      albertel 5733: 
1.518     raeburn  5734: =cut
                   5735: 
                   5736: sub get_scantronformat_file {
                   5737:     my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
                   5738:     my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$cdom);
                   5739:     my $gottab = 0;
                   5740:     my @lines;
                   5741:     if (ref($domconfig{'scantron'}) eq 'HASH') {
                   5742:         if ($domconfig{'scantron'}{'scantronformat'} ne '') {
                   5743:             my $formatfile = &Apache::lonnet::getfile($Apache::lonnet::perlvar{'lonDocRoot'}.$domconfig{'scantron'}{'scantronformat'});
                   5744:             if ($formatfile ne '-1') {
                   5745:                 @lines = split("\n",$formatfile,-1);
                   5746:                 $gottab = 1;
                   5747:             }
                   5748:         }
                   5749:     }
                   5750:     if (!$gottab) {
                   5751:         my $confname = $cdom.'-domainconfig';
                   5752:         my $default = $Apache::lonnet::perlvar{'lonDocRoot'}.'/res/'.$cdom.'/'.$confname.'/default.tab';
                   5753:         my $formatfile =  &Apache::lonnet::getfile($default);
                   5754:         if ($formatfile ne '-1') {
                   5755:             @lines = split("\n",$formatfile,-1);
                   5756:             $gottab = 1;
                   5757:         }
                   5758:     }
                   5759:     if (!$gottab) {
1.519     raeburn  5760:         my @domains = &Apache::lonnet::current_machine_domains();
                   5761:         if (grep(/^\Q$cdom\E$/,@domains)) {
                   5762:             my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
                   5763:             @lines = <$fh>;
                   5764:             close($fh);
                   5765:         } else {
                   5766:             my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/default_scantronformat.tab');
                   5767:             @lines = <$fh>;
                   5768:             close($fh);
                   5769:         }
1.518     raeburn  5770:     }
                   5771:     return @lines;
1.82      albertel 5772: }
                   5773: 
1.423     albertel 5774: =pod 
                   5775: 
                   5776: =item scantron_CODElist
                   5777: 
                   5778:   Returns html drop down of the saved CODE lists from current course,
                   5779:   generated from earlier printings.
                   5780: 
                   5781: =cut
1.422     foxr     5782: 
1.186     albertel 5783: sub scantron_CODElist {
1.257     albertel 5784:     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
                   5785:     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
1.186     albertel 5786:     my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum);
                   5787:     my $namechoice='<option></option>';
1.225     albertel 5788:     foreach my $name (sort {uc($a) cmp uc($b)} @names) {
1.191     albertel 5789: 	if ($name =~ /^error: 2 /) { next; }
1.278     albertel 5790: 	if ($name =~ /^type\0/) { next; }
1.186     albertel 5791: 	$namechoice.='<option value="'.$name.'">'.$name.'</option>';
                   5792:     }
                   5793:     $namechoice='<select name="scantron_CODElist">'.$namechoice.'</select>';
                   5794:     return $namechoice;
                   5795: }
                   5796: 
1.423     albertel 5797: =pod 
                   5798: 
                   5799: =item scantron_CODEunique
                   5800: 
                   5801:   Returns the html for "Each CODE to be used once" radio.
                   5802: 
                   5803: =cut
1.422     foxr     5804: 
1.186     albertel 5805: sub scantron_CODEunique {
1.532     bisitz   5806:     my $result='<span class="LC_nobreak">
1.272     albertel 5807:                  <label><input type="radio" name="scantron_CODEunique"
1.423     albertel 5808:                         value="yes" checked="checked" />'.&mt('Yes').' </label>
1.381     albertel 5809:                 </span>
1.532     bisitz   5810:                 <span class="LC_nobreak">
1.272     albertel 5811:                  <label><input type="radio" name="scantron_CODEunique"
1.423     albertel 5812:                         value="no" />'.&mt('No').' </label>
1.381     albertel 5813:                 </span>';
1.186     albertel 5814:     return $result;
                   5815: }
1.423     albertel 5816: 
                   5817: =pod 
                   5818: 
                   5819: =item scantron_selectphase
                   5820: 
1.596.2.6  raeburn  5821:   Generates the initial screen to start the bubblesheet process.
1.423     albertel 5822:   Allows for - starting a grading run.
1.424     albertel 5823:              - downloading existing scan data (original, corrected
1.423     albertel 5824:                                                 or skipped info)
                   5825: 
                   5826:              - uploading new scan data
                   5827: 
                   5828:  Arguments:
                   5829:   $r          - The Apache request object
                   5830:   $file2grade - name of the file that contain the scanned data to score
                   5831: 
                   5832: =cut
1.186     albertel 5833: 
1.75      albertel 5834: sub scantron_selectphase {
1.209     ng       5835:     my ($r,$file2grade) = @_;
1.324     albertel 5836:     my ($symb)=&get_symb($r);
1.75      albertel 5837:     if (!$symb) {return '';}
1.582     raeburn  5838:     my $map_error;
                   5839:     my $sequence_selector=&getSequenceDropDown($symb,\$map_error);
                   5840:     if ($map_error) {
                   5841:         $r->print('<br />'.&navmap_errormsg().'<br />');
                   5842:         return;
                   5843:     }
1.324     albertel 5844:     my $default_form_data=&defaultFormData($symb);
                   5845:     my $grading_menu_button=&show_grading_menu_form($symb);
1.209     ng       5846:     my $file_selector=&scantron_uploads($file2grade);
1.82      albertel 5847:     my $format_selector=&scantron_scantab();
1.186     albertel 5848:     my $CODE_selector=&scantron_CODElist();
                   5849:     my $CODE_unique=&scantron_CODEunique();
1.75      albertel 5850:     my $result;
1.422     foxr     5851: 
1.513     foxr     5852:     $ssi_error = 0;
                   5853: 
1.596.2.4  raeburn  5854:     if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
                   5855:         &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
                   5856: 
                   5857:         # Chunk of form to prompt for a scantron file upload.
                   5858: 
                   5859:         $r->print('
                   5860:     <br />
                   5861:     '.&Apache::loncommon::start_data_table('LC_scantron_action').'
                   5862:        '.&Apache::loncommon::start_data_table_header_row().'
                   5863:             <th>
                   5864:               &nbsp;'.&mt('Specify a bubblesheet data file to upload.').'
                   5865:             </th>
                   5866:        '.&Apache::loncommon::end_data_table_header_row().'
                   5867:        '.&Apache::loncommon::start_data_table_row().'
                   5868:             <td>
                   5869: ');
                   5870:     my $default_form_data=&defaultFormData(&get_symb($r,1));
                   5871:     my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
                   5872:     my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};
1.596.2.12.2.  6(raebur 5873:6):     my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
                   5874:6):     &js_escape(\$alertmsg);
1.596.2.4  raeburn  5875:     $r->print('
                   5876:               <script type="text/javascript" language="javascript">
                   5877:     function checkUpload(formname) {
                   5878:         if (formname.upfile.value == "") {
1.596.2.12.2.  6(raebur 5879:6):             alert("'.$alertmsg.'");
1.596.2.4  raeburn  5880:             return false;
                   5881:         }
                   5882:         formname.submit();
                   5883:     }
                   5884:               </script>
                   5885: 
                   5886:               <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
                   5887:                 '.$default_form_data.'
                   5888:                 <input name="courseid" type="hidden" value="'.$cnum.'" />
                   5889:                 <input name="domainid" type="hidden" value="'.$cdom.'" />
                   5890:                 <input name="command" value="scantronupload_save" type="hidden" />
                   5891:                 '.&mt('File to upload: [_1]','<input type="file" name="upfile" size="50" />').'
                   5892:                 <br />
                   5893:                 <input type="button" onclick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
                   5894:               </form>
                   5895: ');
                   5896: 
                   5897:         $r->print('
                   5898:             </td>
                   5899:        '.&Apache::loncommon::end_data_table_row().'
                   5900:        '.&Apache::loncommon::end_data_table().'
                   5901: ');
                   5902:     }
                   5903: 
1.422     foxr     5904:     # Chunk of form to prompt for a file to grade and how:
                   5905: 
1.489     albertel 5906:     $result.= '
                   5907:     <br />
                   5908:     <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
                   5909:     <input type="hidden" name="command" value="scantron_warning" />
                   5910:     '.$default_form_data.'
                   5911:     '.&Apache::loncommon::start_data_table('LC_scantron_action').'
                   5912:        '.&Apache::loncommon::start_data_table_header_row().'
                   5913:             <th colspan="2">
1.492     albertel 5914:               &nbsp;'.&mt('Specify file and which Folder/Sequence to grade').'
1.489     albertel 5915:             </th>
                   5916:        '.&Apache::loncommon::end_data_table_header_row().'
                   5917:        '.&Apache::loncommon::start_data_table_row().'
1.492     albertel 5918:             <td> '.&mt('Sequence to grade:').' </td><td> '.$sequence_selector.' </td>
1.489     albertel 5919:        '.&Apache::loncommon::end_data_table_row().'
                   5920:        '.&Apache::loncommon::start_data_table_row().'
1.572     www      5921:             <td> '.&mt('Filename of bubblesheet data file:').' </td><td> '.$file_selector.' </td>
1.489     albertel 5922:        '.&Apache::loncommon::end_data_table_row().'
                   5923:        '.&Apache::loncommon::start_data_table_row().'
1.572     www      5924:             <td> '.&mt('Format of bubblesheet data file:').' </td><td> '.$format_selector.' </td>
1.489     albertel 5925:        '.&Apache::loncommon::end_data_table_row().'
                   5926:        '.&Apache::loncommon::start_data_table_row().'
1.492     albertel 5927:             <td> '.&mt('Saved CODEs to validate against:').' </td><td> '.$CODE_selector.' </td>
1.489     albertel 5928:        '.&Apache::loncommon::end_data_table_row().'
                   5929:        '.&Apache::loncommon::start_data_table_row().'
1.492     albertel 5930:             <td> '.&mt('Each CODE is only to be used once:').'</td><td> '.$CODE_unique.' </td>
1.489     albertel 5931:        '.&Apache::loncommon::end_data_table_row().'
                   5932:        '.&Apache::loncommon::start_data_table_row().'
1.492     albertel 5933: 	    <td> '.&mt('Options:').' </td>
1.187     albertel 5934:             <td>
1.492     albertel 5935: 	       <label><input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> '.&mt('Do only previously skipped records').'</label> <br />
                   5936:                <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> '.&mt('Remove all existing corrections').'</label> <br />
                   5937:                <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> '.&mt('Skip hidden resources when grading').'</label>
1.187     albertel 5938: 	    </td>
1.489     albertel 5939:        '.&Apache::loncommon::end_data_table_row().'
                   5940:        '.&Apache::loncommon::start_data_table_row().'
1.174     albertel 5941:             <td colspan="2">
1.572     www      5942:               <input type="submit" value="'.&mt('Grading: Validate Bubblesheet Records').'" />
1.162     albertel 5943:             </td>
1.489     albertel 5944:        '.&Apache::loncommon::end_data_table_row().'
                   5945:     '.&Apache::loncommon::end_data_table().'
                   5946:     </form>
                   5947: ';
1.162     albertel 5948:    
                   5949:     $r->print($result);
                   5950: 
1.422     foxr     5951:     # Chunk of the form that prompts to view a scoring office file,
                   5952:     # corrected file, skipped records in a file.
                   5953: 
1.489     albertel 5954:     $r->print('
                   5955:    <br />
                   5956:    <form action="/adm/grades" name="scantron_download">
                   5957:      '.$default_form_data.'
                   5958:      <input type="hidden" name="command" value="scantron_download" />
                   5959:      '.&Apache::loncommon::start_data_table('LC_scantron_action').'
                   5960:        '.&Apache::loncommon::start_data_table_header_row().'
                   5961:               <th>
1.492     albertel 5962:                 &nbsp;'.&mt('Download a scoring office file').'
1.489     albertel 5963:               </th>
                   5964:        '.&Apache::loncommon::end_data_table_header_row().'
                   5965:        '.&Apache::loncommon::start_data_table_row().'
1.492     albertel 5966:               <td> '.&mt('Filename of scoring office file: [_1]',$file_selector).' 
1.489     albertel 5967:                 <br />
1.492     albertel 5968:                 <input type="submit" value="'.&mt('Download: Show List of Associated Files').'" />
1.489     albertel 5969:        '.&Apache::loncommon::end_data_table_row().'
                   5970:      '.&Apache::loncommon::end_data_table().'
                   5971:    </form>
                   5972:    <br />
                   5973: ');
1.162     albertel 5974: 
1.457     banghart 5975:     &Apache::lonpickcode::code_list($r,2);
1.523     raeburn  5976: 
1.596.2.12.2.  8(raebur 5977:3):     $r->print('<br /><form method="post" name="checkscantron" action="">'.
1.523     raeburn  5978:              $default_form_data."\n".
                   5979:              &Apache::loncommon::start_data_table('LC_scantron_action')."\n".
                   5980:              &Apache::loncommon::start_data_table_header_row()."\n".
                   5981:              '<th colspan="2">
1.572     www      5982:               &nbsp;'.&mt('Review bubblesheet data and submissions for a previously graded folder/sequence')."\n".
1.523     raeburn  5983:              '</th>'."\n".
                   5984:               &Apache::loncommon::end_data_table_header_row()."\n".
                   5985:               &Apache::loncommon::start_data_table_row()."\n".
                   5986:               '<td> '.&mt('Graded folder/sequence:').' </td>'."\n".
                   5987:               '<td> '.$sequence_selector.' </td>'.
                   5988:               &Apache::loncommon::end_data_table_row()."\n".
                   5989:               &Apache::loncommon::start_data_table_row()."\n".
                   5990:               '<td> '.&mt('Filename of scoring office file:').' </td>'."\n".
                   5991:               '<td> '.$file_selector.' </td>'."\n".
                   5992:               &Apache::loncommon::end_data_table_row()."\n".
                   5993:               &Apache::loncommon::start_data_table_row()."\n".
                   5994:               '<td> '.&mt('Format of data file:').' </td>'."\n".
                   5995:               '<td> '.$format_selector.' </td>'."\n".
                   5996:               &Apache::loncommon::end_data_table_row()."\n".
                   5997:               &Apache::loncommon::start_data_table_row()."\n".
1.557     raeburn  5998:               '<td> '.&mt('Options').' </td>'."\n".
                   5999:               '<td> <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> '.&mt('Skip hidden resources').'</label></td>'.
                   6000:               &Apache::loncommon::end_data_table_row()."\n".
                   6001:               &Apache::loncommon::start_data_table_row()."\n".
1.523     raeburn  6002:               '<td colspan="2">'."\n".
                   6003:               '<input type="hidden" name="command" value="checksubmissions" />'."\n".
1.575     www      6004:               '<input type="submit" value="'.&mt('Review Bubblesheet Data and Submission Records').'" />'."\n".
1.523     raeburn  6005:               '</td>'."\n".
                   6006:               &Apache::loncommon::end_data_table_row()."\n".
                   6007:               &Apache::loncommon::end_data_table()."\n".
                   6008:               '</form><br />');
1.457     banghart 6009:     $r->print($grading_menu_button);
1.523     raeburn  6010:     return;
1.75      albertel 6011: }
                   6012: 
1.423     albertel 6013: =pod
                   6014: 
                   6015: =item get_scantron_config
                   6016: 
                   6017:    Parse and return the scantron configuration line selected as a
                   6018:    hash of configuration file fields.
                   6019: 
                   6020:  Arguments:
                   6021:     which - the name of the configuration to parse from the file.
                   6022: 
                   6023: 
                   6024:  Returns:
                   6025:             If the named configuration is not in the file, an empty
                   6026:             hash is returned.
                   6027:     a hash with the fields
                   6028:       name         - internal name for the this configuration setup
                   6029:       description  - text to display to operator that describes this config
                   6030:       CODElocation - if 0 or the string 'none'
                   6031:                           - no CODE exists for this config
                   6032:                      if -1 || the string 'letter'
                   6033:                           - a CODE exists for this config and is
                   6034:                             a string of letters
                   6035:                      Unsupported value (but planned for future support)
                   6036:                           if a positive integer
                   6037:                                - The CODE exists as the first n items from
                   6038:                                  the question section of the form
                   6039:                           if the string 'number'
                   6040:                                - The CODE exists for this config and is
                   6041:                                  a string of numbers
                   6042:       CODEstart   - (only matter if a CODE exists) column in the line where
                   6043:                      the CODE starts
                   6044:       CODElength  - length of the CODE
1.573     bisitz   6045:       IDstart     - column where the student/employee ID starts
1.556     weissno  6046:       IDlength    - length of the student/employee ID info
1.423     albertel 6047:       Qstart      - column where the information from the bubbled
                   6048:                     'questions' start
                   6049:       Qlength     - number of columns comprising a single bubble line from
                   6050:                     the sheet. (usually either 1 or 10)
1.424     albertel 6051:       Qon         - either a single character representing the character used
1.423     albertel 6052:                     to signal a bubble was chosen in the positional setup, or
                   6053:                     the string 'letter' if the letter of the chosen bubble is
                   6054:                     in the final, or 'number' if a number representing the
                   6055:                     chosen bubble is in the file (1->A 0->J)
1.424     albertel 6056:       Qoff        - the character used to represent that a bubble was
                   6057:                     left blank
1.423     albertel 6058:       PaperID     - if the scanning process generates a unique number for each
                   6059:                     sheet scanned the column that this ID number starts in
                   6060:       PaperIDlength - number of columns that comprise the unique ID number
                   6061:                       for the sheet of paper
1.424     albertel 6062:       FirstName   - column that the first name starts in
1.423     albertel 6063:       FirstNameLength - number of columns that the first name spans
                   6064:  
                   6065:       LastName    - column that the last name starts in
                   6066:       LastNameLength - number of columns that the last name spans
1.596.2.12.2.  (raeburn 6067:):       BubblesPerRow - number of bubbles available in each row used to
                   6068:):                       bubble an answer. (If not specified, 10 assumed).
1.423     albertel 6069: 
                   6070: =cut
1.422     foxr     6071: 
1.82      albertel 6072: sub get_scantron_config {
                   6073:     my ($which) = @_;
1.518     raeburn  6074:     my @lines = &get_scantronformat_file();
1.82      albertel 6075:     my %config;
1.157     albertel 6076:     #FIXME probably should move to XML it has already gotten a bit much now
1.518     raeburn  6077:     foreach my $line (@lines) {
1.82      albertel 6078: 	my ($name,$descrip)=split(/:/,$line);
                   6079: 	if ($name ne $which ) { next; }
                   6080: 	chomp($line);
                   6081: 	my @config=split(/:/,$line);
                   6082: 	$config{'name'}=$config[0];
                   6083: 	$config{'description'}=$config[1];
                   6084: 	$config{'CODElocation'}=$config[2];
                   6085: 	$config{'CODEstart'}=$config[3];
                   6086: 	$config{'CODElength'}=$config[4];
                   6087: 	$config{'IDstart'}=$config[5];
                   6088: 	$config{'IDlength'}=$config[6];
                   6089: 	$config{'Qstart'}=$config[7];
1.497     foxr     6090:  	$config{'Qlength'}=$config[8];
1.82      albertel 6091: 	$config{'Qoff'}=$config[9];
                   6092: 	$config{'Qon'}=$config[10];
1.157     albertel 6093: 	$config{'PaperID'}=$config[11];
                   6094: 	$config{'PaperIDlength'}=$config[12];
                   6095: 	$config{'FirstName'}=$config[13];
                   6096: 	$config{'FirstNamelength'}=$config[14];
                   6097: 	$config{'LastName'}=$config[15];
                   6098: 	$config{'LastNamelength'}=$config[16];
1.596.2.12.2.  (raeburn 6099:):         $config{'BubblesPerRow'}=$config[17];
1.82      albertel 6100: 	last;
                   6101:     }
                   6102:     return %config;
                   6103: }
                   6104: 
1.423     albertel 6105: =pod 
                   6106: 
                   6107: =item username_to_idmap
                   6108: 
1.556     weissno  6109:     creates a hash keyed by student/employee ID with values of the corresponding
1.423     albertel 6110:     student username:domain.
                   6111: 
                   6112:   Arguments:
                   6113: 
                   6114:     $classlist - reference to the class list hash. This is a hash
                   6115:                  keyed by student name:domain  whose elements are references
1.424     albertel 6116:                  to arrays containing various chunks of information
1.423     albertel 6117:                  about the student. (See loncoursedata for more info).
                   6118: 
                   6119:   Returns
                   6120:     %idmap - the constructed hash
                   6121: 
                   6122: =cut
                   6123: 
1.82      albertel 6124: sub username_to_idmap {
                   6125:     my ($classlist)= @_;
                   6126:     my %idmap;
                   6127:     foreach my $student (keys(%$classlist)) {
1.596.2.12.2.  3(raebur 6128:5):         my $id = $classlist->{$student}->[&Apache::loncoursedata::CL_ID];
                   6129:5):         unless ($id eq '') {
                   6130:5):             if (!exists($idmap{$id})) {
                   6131:5):                 $idmap{$id} = $student;
                   6132:5):             } else {
                   6133:5):                 my $status = $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS];
                   6134:5):                 if ($status eq 'Active') {
                   6135:5):                     $idmap{$id} = $student;
                   6136:5):                 }
                   6137:5):             }
                   6138:5):         }
1.82      albertel 6139:     }
                   6140:     return %idmap;
                   6141: }
1.423     albertel 6142: 
                   6143: =pod
                   6144: 
1.424     albertel 6145: =item scantron_fixup_scanline
1.423     albertel 6146: 
                   6147:    Process a requested correction to a scanline.
                   6148: 
                   6149:   Arguments:
                   6150:     $scantron_config   - hash from &get_scantron_config()
                   6151:     $scan_data         - hash of correction information 
                   6152:                           (see &scantron_getfile())
                   6153:     $line              - existing scanline
                   6154:     $whichline         - line number of the passed in scanline
                   6155:     $field             - type of change to process 
                   6156:                          (either 
1.573     bisitz   6157:                           'ID'     -> correct the student/employee ID
1.423     albertel 6158:                           'CODE'   -> correct the CODE
                   6159:                           'answer' -> fixup the submitted answers)
                   6160:     
                   6161:    $args               - hash of additional info,
                   6162:                           - 'ID' 
                   6163:                                'newid' -> studentID to use in replacement
1.424     albertel 6164:                                           of existing one
1.423     albertel 6165:                           - 'CODE' 
                   6166:                                'CODE_ignore_dup' - set to true if duplicates
                   6167:                                                    should be ignored.
                   6168: 	                       'CODE' - is new code or 'use_unfound'
1.424     albertel 6169:                                         if the existing unfound code should
1.423     albertel 6170:                                         be used as is
                   6171:                           - 'answer'
                   6172:                                'response' - new answer or 'none' if blank
                   6173:                                'question' - the bubble line to change
1.503     raeburn  6174:                                'questionnum' - the question identifier,
                   6175:                                                may include subquestion. 
1.423     albertel 6176: 
                   6177:   Returns:
                   6178:     $line - the modified scanline
                   6179: 
                   6180:   Side effects: 
                   6181:     $scan_data - may be updated
                   6182: 
                   6183: =cut
                   6184: 
1.82      albertel 6185: 
1.157     albertel 6186: sub scantron_fixup_scanline {
                   6187:     my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
                   6188:     if ($field eq 'ID') {
                   6189: 	if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
1.186     albertel 6190: 	    return ($line,1,'New value too large');
1.157     albertel 6191: 	}
                   6192: 	if (length($args->{'newid'}) < $$scantron_config{'IDlength'}) {
                   6193: 	    $args->{'newid'}=sprintf('%-'.$$scantron_config{'IDlength'}.'s',
                   6194: 				     $args->{'newid'});
                   6195: 	}
                   6196: 	substr($line,$$scantron_config{'IDstart'}-1,
                   6197: 	       $$scantron_config{'IDlength'})=$args->{'newid'};
                   6198: 	if ($args->{'newid'}=~/^\s*$/) {
                   6199: 	    &scan_data($scan_data,"$whichline.user",
                   6200: 		       $args->{'username'}.':'.$args->{'domain'});
                   6201: 	}
1.186     albertel 6202:     } elsif ($field eq 'CODE') {
1.192     albertel 6203: 	if ($args->{'CODE_ignore_dup'}) {
                   6204: 	    &scan_data($scan_data,"$whichline.CODE_ignore_dup",'1');
                   6205: 	}
                   6206: 	&scan_data($scan_data,"$whichline.useCODE",'1');
                   6207: 	if ($args->{'CODE'} ne 'use_unfound') {
1.191     albertel 6208: 	    if (length($args->{'CODE'}) > $$scantron_config{'CODElength'}) {
                   6209: 		return ($line,1,'New CODE value too large');
                   6210: 	    }
                   6211: 	    if (length($args->{'CODE'}) < $$scantron_config{'CODElength'}) {
                   6212: 		$args->{'CODE'}=sprintf('%-'.$$scantron_config{'CODElength'}.'s',$args->{'CODE'});
                   6213: 	    }
                   6214: 	    substr($line,$$scantron_config{'CODEstart'}-1,
                   6215: 		   $$scantron_config{'CODElength'})=$args->{'CODE'};
1.186     albertel 6216: 	}
1.157     albertel 6217:     } elsif ($field eq 'answer') {
1.497     foxr     6218: 	my $length=$scantron_config->{'Qlength'};
1.157     albertel 6219: 	my $off=$scantron_config->{'Qoff'};
                   6220: 	my $on=$scantron_config->{'Qon'};
1.497     foxr     6221: 	my $answer=${off}x$length;
                   6222: 	if ($args->{'response'} eq 'none') {
                   6223: 	    &scan_data($scan_data,
1.503     raeburn  6224: 		       "$whichline.no_bubble.".$args->{'questionnum'},'1');
1.497     foxr     6225: 	} else {
                   6226: 	    if ($on eq 'letter') {
                   6227: 		my @alphabet=('A'..'Z');
                   6228: 		$answer=$alphabet[$args->{'response'}];
                   6229: 	    } elsif ($on eq 'number') {
                   6230: 		$answer=$args->{'response'}+1;
                   6231: 		if ($answer == 10) { $answer = '0'; }
1.274     albertel 6232: 	    } else {
1.497     foxr     6233: 		substr($answer,$args->{'response'},1)=$on;
1.274     albertel 6234: 	    }
1.497     foxr     6235: 	    &scan_data($scan_data,
1.503     raeburn  6236: 		       "$whichline.no_bubble.".$args->{'questionnum'},undef,'1');
1.157     albertel 6237: 	}
1.497     foxr     6238: 	my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};
                   6239: 	substr($line,$where-1,$length)=$answer;
1.157     albertel 6240:     }
                   6241:     return $line;
                   6242: }
1.423     albertel 6243: 
                   6244: =pod
                   6245: 
                   6246: =item scan_data
                   6247: 
                   6248:     Edit or look up  an item in the scan_data hash.
                   6249: 
                   6250:   Arguments:
                   6251:     $scan_data  - The hash (see scantron_getfile)
                   6252:     $key        - shorthand of the key to edit (actual key is
1.424     albertel 6253:                   scantronfilename_key).
1.423     albertel 6254:     $data        - New value of the hash entry.
                   6255:     $delete      - If true, the entry is removed from the hash.
                   6256: 
                   6257:   Returns:
                   6258:     The new value of the hash table field (undefined if deleted).
                   6259: 
                   6260: =cut
                   6261: 
                   6262: 
1.157     albertel 6263: sub scan_data {
                   6264:     my ($scan_data,$key,$value,$delete)=@_;
1.257     albertel 6265:     my $filename=$env{'form.scantron_selectfile'};
1.157     albertel 6266:     if (defined($value)) {
                   6267: 	$scan_data->{$filename.'_'.$key} = $value;
                   6268:     }
                   6269:     if ($delete) { delete($scan_data->{$filename.'_'.$key}); }
                   6270:     return $scan_data->{$filename.'_'.$key};
                   6271: }
1.423     albertel 6272: 
1.495     albertel 6273: # ----- These first few routines are general use routines.----
                   6274: 
                   6275: # Return the number of occurences of a pattern in a string.
                   6276: 
                   6277: sub occurence_count {
                   6278:     my ($string, $pattern) = @_;
                   6279: 
                   6280:     my @matches = ($string =~ /$pattern/g);
                   6281: 
                   6282:     return scalar(@matches);
                   6283: }
                   6284: 
                   6285: 
                   6286: # Take a string known to have digits and convert all the
                   6287: # digits into letters in the range J,A..I.
                   6288: 
                   6289: sub digits_to_letters {
                   6290:     my ($input) = @_;
                   6291: 
                   6292:     my @alphabet = ('J', 'A'..'I');
                   6293: 
                   6294:     my @input    = split(//, $input);
                   6295:     my $output ='';
                   6296:     for (my $i = 0; $i < scalar(@input); $i++) {
                   6297: 	if ($input[$i] =~ /\d/) {
                   6298: 	    $output .= $alphabet[$input[$i]];
                   6299: 	} else {
                   6300: 	    $output .= $input[$i];
                   6301: 	}
                   6302:     }
                   6303:     return $output;
                   6304: }
                   6305: 
1.423     albertel 6306: =pod 
                   6307: 
                   6308: =item scantron_parse_scanline
                   6309: 
                   6310:   Decodes a scanline from the selected scantron file
                   6311: 
                   6312:  Arguments:
                   6313:     line             - The text of the scantron file line to process
                   6314:     whichline        - Line number
                   6315:     scantron_config  - Hash describing the format of the scantron lines.
                   6316:     scan_data        - Hash of extra information about the scanline
                   6317:                        (see scantron_getfile for more information)
                   6318:     just_header      - True if should not process question answers but only
                   6319:                        the stuff to the left of the answers.
1.596.2.12.2.  6(raebur 6320:3):     randomorder      - True if randomorder in use
                   6321:3):     randompick       - True if randompick in use
                   6322:3):     sequence         - Exam folder URL
                   6323:3):     master_seq       - Ref to array containing symbs in exam folder
                   6324:3):     symb_to_resource - Ref to hash of symbs for resources in exam folder
                   6325:3):                        (corresponding values are resource objects)
                   6326:3):     partids_by_symb  - Ref to hash of symb -> array ref of partIDs
                   6327:3):     orderedforcode   - Ref to hash of arrays. keys are CODEs and values
                   6328:3):                        are refs to an array of resource objects, ordered
                   6329:3):                        according to order used for CODE, when randomorder
                   6330:3):                        and or randompick are in use.
                   6331:3):     respnumlookup    - Ref to hash mapping question numbers in bubble lines
                   6332:3):                        for current line to question number used for same question
                   6333:3):                         in "Master Sequence" (as seen by Course Coordinator).
                   6334:3):     startline        - Ref to hash where key is question number (0 is first)
                   6335:3):                        and value is number of first bubble line for current 
                   6336:3):                        student or code-based randompick and/or randomorder.
                   6337:3):     totalref         - Ref of scalar used to score total number of bubble
                   6338:3):                        lines needed for responses in a scan line (used when
                   6339:3):                        randompick in use. 
                   6340:3): 
1.423     albertel 6341:  Returns:
                   6342:    Hash containing the result of parsing the scanline
                   6343: 
                   6344:    Keys are all proceeded by the string 'scantron.'
                   6345: 
                   6346:        CODE    - the CODE in use for this scanline
                   6347:        useCODE - 1 if the CODE is invalid but it usage has been forced
                   6348:                  by the operator
                   6349:        CODE_ignore_dup - 1 if the CODE is a duplicated use when unique
                   6350:                             CODEs were selected, but the usage has been
                   6351:                             forced by the operator
1.556     weissno  6352:        ID  - student/employee ID
1.423     albertel 6353:        PaperID - if used, the ID number printed on the sheet when the 
                   6354:                  paper was scanned
                   6355:        FirstName - first name from the sheet
                   6356:        LastName  - last name from the sheet
                   6357: 
                   6358:      if just_header was not true these key may also exist
                   6359: 
1.447     foxr     6360:        missingerror - a list of bubble ranges that are considered to be answers
                   6361:                       to a single question that don't have any bubbles filled in.
                   6362:                       Of the form questionnumber:firstbubblenumber:count.
                   6363:        doubleerror  - a list of bubble ranges that are considered to be answers
                   6364:                       to a single question that have more than one bubble filled in.
                   6365:                       Of the form questionnumber::firstbubblenumber:count
                   6366:    
                   6367:                 In the above, count is the number of bubble responses in the
                   6368:                 input line needed to represent the possible answers to the question.
                   6369:                 e.g. a radioresponse with 15 choices in an answer sheet with 10 choices
                   6370:                 per line would have count = 2.
                   6371: 
1.423     albertel 6372:        maxquest     - the number of the last bubble line that was parsed
                   6373: 
                   6374:        (<number> starts at 1)
                   6375:        <number>.answer - zero or more letters representing the selected
                   6376:                          letters from the scanline for the bubble line 
                   6377:                          <number>.
                   6378:                          if blank there was either no bubble or there where
                   6379:                          multiple bubbles, (consult the keys missingerror and
                   6380:                          doubleerror if this is an error condition)
                   6381: 
                   6382: =cut
                   6383: 
1.82      albertel 6384: sub scantron_parse_scanline {
1.596.2.12.2.  6(raebur 6385:3):     my ($line,$whichline,$scantron_config,$scan_data,$just_header,$idmap,
                   6386:3):         $randomorder,$randompick,$sequence,$master_seq,$symb_to_resource,
                   6387:3):         $partids_by_symb,$orderedforcode,$respnumlookup,$startline,$totalref)=@_;
1.470     foxr     6388: 
1.82      albertel 6389:     my %record;
1.596.2.12.2.  6(raebur 6390:3):     my $data=substr($line,0,$$scantron_config{'Qstart'}-1); # stuff before answers
1.278     albertel 6391:     if (!($$scantron_config{'CODElocation'} eq 0 ||
                   6392: 	  $$scantron_config{'CODElocation'} eq 'none')) {
                   6393: 	if ($$scantron_config{'CODElocation'} < 0 ||
                   6394: 	    $$scantron_config{'CODElocation'} eq 'letter' ||
                   6395: 	    $$scantron_config{'CODElocation'} eq 'number') {
1.191     albertel 6396: 	    $record{'scantron.CODE'}=substr($data,
                   6397: 					    $$scantron_config{'CODEstart'}-1,
1.83      albertel 6398: 					    $$scantron_config{'CODElength'});
1.191     albertel 6399: 	    if (&scan_data($scan_data,"$whichline.useCODE")) {
                   6400: 		$record{'scantron.useCODE'}=1;
                   6401: 	    }
1.192     albertel 6402: 	    if (&scan_data($scan_data,"$whichline.CODE_ignore_dup")) {
                   6403: 		$record{'scantron.CODE_ignore_dup'}=1;
                   6404: 	    }
1.82      albertel 6405: 	} else {
                   6406: 	    #FIXME interpret first N questions
                   6407: 	}
                   6408:     }
1.83      albertel 6409:     $record{'scantron.ID'}=substr($data,$$scantron_config{'IDstart'}-1,
                   6410: 				  $$scantron_config{'IDlength'});
1.157     albertel 6411:     $record{'scantron.PaperID'}=
                   6412: 	substr($data,$$scantron_config{'PaperID'}-1,
                   6413: 	       $$scantron_config{'PaperIDlength'});
                   6414:     $record{'scantron.FirstName'}=
                   6415: 	substr($data,$$scantron_config{'FirstName'}-1,
                   6416: 	       $$scantron_config{'FirstNamelength'});
                   6417:     $record{'scantron.LastName'}=
                   6418: 	substr($data,$$scantron_config{'LastName'}-1,
                   6419: 	       $$scantron_config{'LastNamelength'});
1.423     albertel 6420:     if ($just_header) { return \%record; }
1.194     albertel 6421: 
1.82      albertel 6422:     my @alphabet=('A'..'Z');
                   6423:     my $questnum=0;
1.447     foxr     6424:     my $ansnum  =1;		# Multiple 'answer lines'/question.
                   6425: 
1.596.2.12.2.  6(raebur 6426:3):     my $lastpos = $env{'form.scantron_maxbubble'}*$$scantron_config{'Qlength'};
                   6427:3):     if ($randompick || $randomorder) {
                   6428:3):         my $total = &get_respnum_lookups($sequence,$scan_data,$idmap,$line,\%record,
                   6429:3):                                          $master_seq,$symb_to_resource,
                   6430:3):                                          $partids_by_symb,$orderedforcode,
                   6431:3):                                          $respnumlookup,$startline);
                   6432:3):         if ($total) {
                   6433:3):             $lastpos = $total*$$scantron_config{'Qlength'};
                   6434:3):         }
                   6435:3):         if (ref($totalref)) {
                   6436:3):             $$totalref = $total;
                   6437:3):         }
                   6438:3):     }
                   6439:3):     my $questions=substr($line,$$scantron_config{'Qstart'}-1,$lastpos);  # Answers
1.470     foxr     6440:     chomp($questions);		# Get rid of any trailing \n.
                   6441:     $questions =~ s/\r$//;      # Get rid of trailing \r too (MAC or Win uploads).
                   6442:     while (length($questions)) {
1.596.2.12.2.  6(raebur 6443:3):         my $answers_needed;
                   6444:3):         if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
                   6445:3):             $answers_needed = $bubble_lines_per_response{$respnumlookup->{$questnum}};
                   6446:3):         } else {
                   6447:3):             $answers_needed = $bubble_lines_per_response{$questnum};
                   6448:3):         }
1.503     raeburn  6449:         my $answer_length  = ($$scantron_config{'Qlength'} * $answers_needed)
                   6450:                              || 1;
                   6451:         $questnum++;
                   6452:         my $quest_id = $questnum;
                   6453:         my $currentquest = substr($questions,0,$answer_length);
                   6454:         $questions       = substr($questions,$answer_length);
                   6455:         if (length($currentquest) < $answer_length) { next; }
                   6456: 
1.596.2.12.2.  6(raebur 6457:3):         my $subdivided;
                   6458:3):         if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
                   6459:3):             $subdivided = $subdivided_bubble_lines{$respnumlookup->{$questnum-1}};
                   6460:3):         } else {
                   6461:3):             $subdivided = $subdivided_bubble_lines{$questnum-1};
                   6462:3):         }
                   6463:3):         if ($subdivided =~ /,/) {
1.503     raeburn  6464:             my $subquestnum = 1;
                   6465:             my $subquestions = $currentquest;
1.596.2.12.2.  6(raebur 6466:3):             my @subanswers_needed = split(/,/,$subdivided);
1.503     raeburn  6467:             foreach my $subans (@subanswers_needed) {
                   6468:                 my $subans_length =
                   6469:                     ($$scantron_config{'Qlength'} * $subans)  || 1;
                   6470:                 my $currsubquest = substr($subquestions,0,$subans_length);
                   6471:                 $subquestions   = substr($subquestions,$subans_length);
                   6472:                 $quest_id = "$questnum.$subquestnum";
                   6473:                 if (($$scantron_config{'Qon'} eq 'letter') ||
                   6474:                     ($$scantron_config{'Qon'} eq 'number')) {
                   6475:                     $ansnum = &scantron_validator_lettnum($ansnum, 
                   6476:                         $questnum,$quest_id,$subans,$currsubquest,$whichline,
1.596.2.12.2.  6(raebur 6477:3):                         \@alphabet,\%record,$scantron_config,$scan_data,
                   6478:3):                         $randomorder,$randompick,$respnumlookup);
1.503     raeburn  6479:                 } else {
                   6480:                     $ansnum = &scantron_validator_positional($ansnum,
1.596.2.12.2.  6(raebur 6481:3):                         $questnum,$quest_id,$subans,$currsubquest,$whichline,
                   6482:3):                         \@alphabet,\%record,$scantron_config,$scan_data,
                   6483:3):                         $randomorder,$randompick,$respnumlookup);
1.503     raeburn  6484:                 }
                   6485:                 $subquestnum ++;
                   6486:             }
                   6487:         } else {
                   6488:             if (($$scantron_config{'Qon'} eq 'letter') ||
                   6489:                 ($$scantron_config{'Qon'} eq 'number')) {
                   6490:                 $ansnum = &scantron_validator_lettnum($ansnum,$questnum,
                   6491:                     $quest_id,$answers_needed,$currentquest,$whichline,
1.596.2.12.2.  6(raebur 6492:3):                     \@alphabet,\%record,$scantron_config,$scan_data,
                   6493:3):                     $randomorder,$randompick,$respnumlookup);
1.503     raeburn  6494:             } else {
                   6495:                 $ansnum = &scantron_validator_positional($ansnum,$questnum,
                   6496:                     $quest_id,$answers_needed,$currentquest,$whichline,
1.596.2.12.2.  6(raebur 6497:3):                     \@alphabet,\%record,$scantron_config,$scan_data,
                   6498:3):                     $randomorder,$randompick,$respnumlookup);
1.503     raeburn  6499:             }
                   6500:         }
                   6501:     }
                   6502:     $record{'scantron.maxquest'}=$questnum;
                   6503:     return \%record;
                   6504: }
1.447     foxr     6505: 
1.596.2.12.2.  6(raebur 6506:3): sub get_master_seq {
                   6507:3):     my ($resources,$master_seq,$symb_to_resource) = @_;
                   6508:3):     return unless ((ref($resources) eq 'ARRAY') && (ref($master_seq) eq 'ARRAY') &&
                   6509:3):                    (ref($symb_to_resource) eq 'HASH'));
                   6510:3):     my $resource_error;
                   6511:3):     foreach my $resource (@{$resources}) {
                   6512:3):         my $ressymb;
                   6513:3):         if (ref($resource)) {
                   6514:3):             $ressymb = $resource->symb();
                   6515:3):             push(@{$master_seq},$ressymb);
                   6516:3):             $symb_to_resource->{$ressymb} = $resource;
                   6517:3):         } else {
                   6518:3):             $resource_error = 1;
                   6519:3):             last;
                   6520:3):         }
                   6521:3):     }
                   6522:3):     return $resource_error;
                   6523:3): }
                   6524:3): 
                   6525:3): sub get_respnum_lookups {
                   6526:3):     my ($sequence,$scan_data,$idmap,$line,$record,$master_seq,$symb_to_resource,
                   6527:3):         $partids_by_symb,$orderedforcode,$respnumlookup,$startline) = @_;
                   6528:3):     return unless ((ref($record) eq 'HASH') && (ref($master_seq) eq 'ARRAY') &&
                   6529:3):                    (ref($symb_to_resource) eq 'HASH') && (ref($partids_by_symb) eq 'HASH') &&
                   6530:3):                    (ref($orderedforcode) eq 'HASH') && (ref($respnumlookup) eq 'HASH') &&
                   6531:3):                    (ref($startline) eq 'HASH'));
                   6532:3):     my ($user,$scancode);
                   6533:3):     if ((exists($record->{'scantron.CODE'})) &&
                   6534:3):         (&Apache::lonnet::validCODE($record->{'scantron.CODE'}))) {
                   6535:3):         $scancode = $record->{'scantron.CODE'};
                   6536:3):     } else {
                   6537:3):         $user = &scantron_find_student($record,$scan_data,$idmap,$line);
                   6538:3):     }
                   6539:3):     my @mapresources =
                   6540:3):         &users_order($user,$scancode,$sequence,$master_seq,$symb_to_resource,
                   6541:3):                      $orderedforcode);
                   6542:3):     my $total = 0;
                   6543:3):     my $count = 0;
                   6544:3):     foreach my $resource (@mapresources) {
                   6545:3):         my $id = $resource->id();
                   6546:3):         my $symb = $resource->symb();
                   6547:3):         if (ref($partids_by_symb->{$symb}) eq 'ARRAY') {
                   6548:3):             foreach my $partid (@{$partids_by_symb->{$symb}}) {
                   6549:3):                 my $respnum = $masterseq_id_responsenum{$id.'_'.$partid};
                   6550:3):                 if ($respnum ne '') {
                   6551:3):                     $respnumlookup->{$count} = $respnum;
                   6552:3):                     $startline->{$count} = $total;
                   6553:3):                     $total += $bubble_lines_per_response{$respnum};
                   6554:3):                     $count ++;
                   6555:3):                 }
                   6556:3):             }
                   6557:3):         }
                   6558:3):     }
                   6559:3):     return $total;
                   6560:3): }
                   6561:3): 
1.503     raeburn  6562: sub scantron_validator_lettnum {
                   6563:     my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,$whichline,
1.596.2.12.2.  6(raebur 6564:3):         $alphabet,$record,$scantron_config,$scan_data,$randomorder,
                   6565:3):         $randompick,$respnumlookup) = @_;
1.503     raeburn  6566: 
                   6567:     # Qon 'letter' implies for each slot in currquest we have:
                   6568:     #    ? or * for doubles, a letter in A-Z for a bubble, and
                   6569:     #    about anything else (esp. a value of Qoff) for missing
                   6570:     #    bubbles.
                   6571:     #
                   6572:     # Qon 'number' implies each slot gives a digit that indexes the
                   6573:     #    bubbles filled, or Qoff, or a non-number for unbubbled lines,
                   6574:     #    and * or ? for double bubbles on a single line.
                   6575:     #
1.447     foxr     6576: 
1.503     raeburn  6577:     my $matchon;
                   6578:     if ($$scantron_config{'Qon'} eq 'letter') {
                   6579:         $matchon = '[A-Z]';
                   6580:     } elsif ($$scantron_config{'Qon'} eq 'number') {
                   6581:         $matchon = '\d';
                   6582:     }
                   6583:     my $occurrences = 0;
1.596.2.12.2.  6(raebur 6584:3):     my $responsenum = $questnum-1;
                   6585:3):     if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
                   6586:3):        $responsenum = $respnumlookup->{$questnum-1}
                   6587:3):     }
                   6588:3):     if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
                   6589:3):         ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
                   6590:3):         ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
                   6591:3):         ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
                   6592:3):         ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
                   6593:3):         ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
1.503     raeburn  6594:         my @singlelines = split('',$currquest);
                   6595:         foreach my $entry (@singlelines) {
                   6596:             $occurrences = &occurence_count($entry,$matchon);
                   6597:             if ($occurrences > 1) {
                   6598:                 last;
                   6599:             }
1.596.2.12.2.  6(raebur 6600:3):         }
1.503     raeburn  6601:     } else {
                   6602:         $occurrences = &occurence_count($currquest,$matchon); 
                   6603:     }
                   6604:     if (($currquest =~ /\?/ || $currquest =~ /\*/) || ($occurrences > 1)) {
                   6605:         push(@{$record->{'scantron.doubleerror'}},$quest_id);
                   6606:         for (my $ans=0; $ans<$answers_needed; $ans++) {
                   6607:             my $bubble = substr($currquest,$ans,1);
                   6608:             if ($bubble =~ /$matchon/ ) {
                   6609:                 if ($$scantron_config{'Qon'} eq 'number') {
                   6610:                     if ($bubble == 0) {
                   6611:                         $bubble = 10; 
                   6612:                     }
                   6613:                     $record->{"scantron.$ansnum.answer"} = 
                   6614:                         $alphabet->[$bubble-1];
                   6615:                 } else {
                   6616:                     $record->{"scantron.$ansnum.answer"} = $bubble;
                   6617:                 }
                   6618:             } else {
                   6619:                 $record->{"scantron.$ansnum.answer"}='';
                   6620:             }
                   6621:             $ansnum++;
                   6622:         }
                   6623:     } elsif (!defined($currquest)
                   6624:             || (&occurence_count($currquest, $$scantron_config{'Qoff'}) == length($currquest))
                   6625:             || (&occurence_count($currquest,$matchon) == 0)) {
                   6626:         for (my $ans=0; $ans<$answers_needed; $ans++ ) {
                   6627:             $record->{"scantron.$ansnum.answer"}='';
                   6628:             $ansnum++;
                   6629:         }
                   6630:         if (!&scan_data($scan_data,"$whichline.no_bubble.$quest_id")) {
                   6631:             push(@{$record->{'scantron.missingerror'}},$quest_id);
                   6632:         }
                   6633:     } else {
                   6634:         if ($$scantron_config{'Qon'} eq 'number') {
                   6635:             $currquest = &digits_to_letters($currquest);            
                   6636:         }
                   6637:         for (my $ans=0; $ans<$answers_needed; $ans++) {
                   6638:             my $bubble = substr($currquest,$ans,1);
                   6639:             $record->{"scantron.$ansnum.answer"} = $bubble;
                   6640:             $ansnum++;
                   6641:         }
                   6642:     }
                   6643:     return $ansnum;
                   6644: }
1.447     foxr     6645: 
1.503     raeburn  6646: sub scantron_validator_positional {
                   6647:     my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,
1.596.2.12.2.  6(raebur 6648:3):         $whichline,$alphabet,$record,$scantron_config,$scan_data,
                   6649:3):         $randomorder,$randompick,$respnumlookup) = @_;
1.447     foxr     6650: 
1.503     raeburn  6651:     # Otherwise there's a positional notation;
                   6652:     # each bubble line requires Qlength items, and there are filled in
                   6653:     # bubbles for each case where there 'Qon' characters.
                   6654:     #
1.447     foxr     6655: 
1.503     raeburn  6656:     my @array=split($$scantron_config{'Qon'},$currquest,-1);
1.447     foxr     6657: 
1.503     raeburn  6658:     # If the split only gives us one element.. the full length of the
                   6659:     # answer string, no bubbles are filled in:
1.447     foxr     6660: 
1.507     raeburn  6661:     if ($answers_needed eq '') {
                   6662:         return;
                   6663:     }
                   6664: 
1.503     raeburn  6665:     if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {
                   6666:         for (my $ans=0; $ans<$answers_needed; $ans++ ) {
                   6667:             $record->{"scantron.$ansnum.answer"}='';
                   6668:             $ansnum++;
                   6669:         }
                   6670:         if (!&scan_data($scan_data,"$whichline.no_bubble.$quest_id")) {
                   6671:             push(@{$record->{"scantron.missingerror"}},$quest_id);
                   6672:         }
                   6673:     } elsif (scalar(@array) == 2) {
                   6674:         my $location = length($array[0]);
                   6675:         my $line_num = int($location / $$scantron_config{'Qlength'});
                   6676:         my $bubble   = $alphabet->[$location % $$scantron_config{'Qlength'}];
                   6677:         for (my $ans=0; $ans<$answers_needed; $ans++) {
                   6678:             if ($ans eq $line_num) {
                   6679:                 $record->{"scantron.$ansnum.answer"} = $bubble;
                   6680:             } else {
                   6681:                 $record->{"scantron.$ansnum.answer"} = ' ';
                   6682:             }
                   6683:             $ansnum++;
                   6684:          }
                   6685:     } else {
                   6686:         #  If there's more than one instance of a bubble character
                   6687:         #  That's a double bubble; with positional notation we can
                   6688:         #  record all the bubbles filled in as well as the
                   6689:         #  fact this response consists of multiple bubbles.
                   6690:         #
1.596.2.12.2.  6(raebur 6691:3):         my $responsenum = $questnum-1;
                   6692:3):         if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
                   6693:3):             $responsenum = $respnumlookup->{$questnum-1}
                   6694:3):         }
                   6695:3):         if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
                   6696:3):             ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
                   6697:3):             ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
                   6698:3):             ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
                   6699:3):             ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
                   6700:3):             ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
1.503     raeburn  6701:             my $doubleerror = 0;
                   6702:             while (($currquest >= $$scantron_config{'Qlength'}) && 
                   6703:                    (!$doubleerror)) {
                   6704:                my $currline = substr($currquest,0,$$scantron_config{'Qlength'});
                   6705:                $currquest = substr($currquest,$$scantron_config{'Qlength'});
                   6706:                my @currarray = split($$scantron_config{'Qon'},$currline,-1);
                   6707:                if (length(@currarray) > 2) {
                   6708:                    $doubleerror = 1;
                   6709:                } 
                   6710:             }
                   6711:             if ($doubleerror) {
                   6712:                 push(@{$record->{'scantron.doubleerror'}},$quest_id);
                   6713:             }
                   6714:         } else {
                   6715:             push(@{$record->{'scantron.doubleerror'}},$quest_id);
                   6716:         }
                   6717:         my $item = $ansnum;
                   6718:         for (my $ans=0; $ans<$answers_needed; $ans++) {
                   6719:             $record->{"scantron.$item.answer"} = '';
                   6720:             $item ++;
                   6721:         }
1.447     foxr     6722: 
1.503     raeburn  6723:         my @ans=@array;
                   6724:         my $i=0;
                   6725:         my $increment = 0;
                   6726:         while ($#ans) {
                   6727:             $i+=length($ans[0]) + $increment;
                   6728:             my $line   = int($i/$$scantron_config{'Qlength'} + $ansnum);
                   6729:             my $bubble = $i%$$scantron_config{'Qlength'};
                   6730:             $record->{"scantron.$line.answer"}.=$alphabet->[$bubble];
                   6731:             shift(@ans);
                   6732:             $increment = 1;
                   6733:         }
                   6734:         $ansnum += $answers_needed;
1.82      albertel 6735:     }
1.503     raeburn  6736:     return $ansnum;
1.82      albertel 6737: }
                   6738: 
1.423     albertel 6739: =pod
                   6740: 
                   6741: =item scantron_add_delay
                   6742: 
                   6743:    Adds an error message that occurred during the grading phase to a
                   6744:    queue of messages to be shown after grading pass is complete
                   6745: 
                   6746:  Arguments:
1.424     albertel 6747:    $delayqueue  - arrary ref of hash ref of error messages
1.423     albertel 6748:    $scanline    - the scanline that caused the error
                   6749:    $errormesage - the error message
                   6750:    $errorcode   - a numeric code for the error
                   6751: 
                   6752:  Side Effects:
1.424     albertel 6753:    updates the $delayqueue to have a new hash ref of the error
1.423     albertel 6754: 
                   6755: =cut
                   6756: 
1.82      albertel 6757: sub scantron_add_delay {
1.140     albertel 6758:     my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;
                   6759:     push(@$delayqueue,
                   6760: 	 {'line' => $scanline, 'emsg' => $errormessage,
                   6761: 	  'ecode' => $errorcode }
                   6762: 	 );
1.82      albertel 6763: }
                   6764: 
1.423     albertel 6765: =pod
                   6766: 
                   6767: =item scantron_find_student
                   6768: 
1.424     albertel 6769:    Finds the username for the current scanline
                   6770: 
                   6771:   Arguments:
                   6772:    $scantron_record - hash result from scantron_parse_scanline
                   6773:    $scan_data       - hash of correction information 
                   6774:                       (see &scantron_getfile() form more information)
                   6775:    $idmap           - hash from &username_to_idmap()
                   6776:    $line            - number of current scanline
                   6777:  
                   6778:   Returns:
                   6779:    Either 'username:domain' or undef if unknown
                   6780: 
1.423     albertel 6781: =cut
                   6782: 
1.82      albertel 6783: sub scantron_find_student {
1.157     albertel 6784:     my ($scantron_record,$scan_data,$idmap,$line)=@_;
1.83      albertel 6785:     my $scanID=$$scantron_record{'scantron.ID'};
1.157     albertel 6786:     if ($scanID =~ /^\s*$/) {
                   6787:  	return &scan_data($scan_data,"$line.user");
                   6788:     }
1.83      albertel 6789:     foreach my $id (keys(%$idmap)) {
1.157     albertel 6790:  	if (lc($id) eq lc($scanID)) {
                   6791:  	    return $$idmap{$id};
                   6792:  	}
1.83      albertel 6793:     }
                   6794:     return undef;
                   6795: }
                   6796: 
1.423     albertel 6797: =pod
                   6798: 
                   6799: =item scantron_filter
                   6800: 
1.424     albertel 6801:    Filter sub for lonnavmaps, filters out hidden resources if ignore
                   6802:    hidden resources was selected
                   6803: 
1.423     albertel 6804: =cut
                   6805: 
1.83      albertel 6806: sub scantron_filter {
                   6807:     my ($curres)=@_;
1.331     albertel 6808: 
                   6809:     if (ref($curres) && $curres->is_problem()) {
                   6810: 	# if the user has asked to not have either hidden
                   6811: 	# or 'randomout' controlled resources to be graded
                   6812: 	# don't include them
                   6813: 	if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
                   6814: 	    && $curres->randomout) {
                   6815: 	    return 0;
                   6816: 	}
1.83      albertel 6817: 	return 1;
                   6818:     }
                   6819:     return 0;
1.82      albertel 6820: }
                   6821: 
1.423     albertel 6822: =pod
                   6823: 
                   6824: =item scantron_process_corrections
                   6825: 
1.424     albertel 6826:    Gets correction information out of submitted form data and corrects
                   6827:    the scanline
                   6828: 
1.423     albertel 6829: =cut
                   6830: 
1.157     albertel 6831: sub scantron_process_corrections {
                   6832:     my ($r) = @_;
1.257     albertel 6833:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
1.157     albertel 6834:     my ($scanlines,$scan_data)=&scantron_getfile();
                   6835:     my $classlist=&Apache::loncoursedata::get_classlist();
1.257     albertel 6836:     my $which=$env{'form.scantron_line'};
1.200     albertel 6837:     my $line=&scantron_get_line($scanlines,$scan_data,$which);
1.157     albertel 6838:     my ($skip,$err,$errmsg);
1.257     albertel 6839:     if ($env{'form.scantron_skip_record'}) {
1.157     albertel 6840: 	$skip=1;
1.257     albertel 6841:     } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) {
                   6842: 	my $newstudent=$env{'form.scantron_username'}.':'.
                   6843: 	    $env{'form.scantron_domain'};
1.157     albertel 6844: 	my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID];
                   6845: 	($line,$err,$errmsg)=
                   6846: 	    &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
                   6847: 				     'ID',{'newid'=>$newid,
1.257     albertel 6848: 				    'username'=>$env{'form.scantron_username'},
                   6849: 				    'domain'=>$env{'form.scantron_domain'}});
                   6850:     } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)CODE$/) {
                   6851: 	my $resolution=$env{'form.scantron_CODE_resolution'};
1.190     albertel 6852: 	my $newCODE;
1.192     albertel 6853: 	my %args;
1.190     albertel 6854: 	if      ($resolution eq 'use_unfound') {
1.191     albertel 6855: 	    $newCODE='use_unfound';
1.190     albertel 6856: 	} elsif ($resolution eq 'use_found') {
1.257     albertel 6857: 	    $newCODE=$env{'form.scantron_CODE_selectedvalue'};
1.190     albertel 6858: 	} elsif ($resolution eq 'use_typed') {
1.257     albertel 6859: 	    $newCODE=$env{'form.scantron_CODE_newvalue'};
1.194     albertel 6860: 	} elsif ($resolution =~ /^use_closest_(\d+)/) {
1.257     albertel 6861: 	    $newCODE=$env{"form.scantron_CODE_closest_$1"};
1.190     albertel 6862: 	}
1.257     albertel 6863: 	if ($env{'form.scantron_corrections'} eq 'duplicateCODE') {
1.192     albertel 6864: 	    $args{'CODE_ignore_dup'}=1;
                   6865: 	}
                   6866: 	$args{'CODE'}=$newCODE;
1.186     albertel 6867: 	($line,$err,$errmsg)=
                   6868: 	    &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
1.192     albertel 6869: 				     'CODE',\%args);
1.257     albertel 6870:     } elsif ($env{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) {
                   6871: 	foreach my $question (split(',',$env{'form.scantron_questions'})) {
1.157     albertel 6872: 	    ($line,$err,$errmsg)=
                   6873: 		&scantron_fixup_scanline(\%scantron_config,$scan_data,$line,
                   6874: 					 $which,'answer',
                   6875: 					 { 'question'=>$question,
1.503     raeburn  6876: 		      		   'response'=>$env{"form.scantron_correct_Q_$question"},
                   6877:                                    'questionnum'=>$env{"form.scantron_questionnum_Q_$question"}});
1.157     albertel 6878: 	    if ($err) { last; }
                   6879: 	}
                   6880:     }
                   6881:     if ($err) {
1.596.2.12.2.  0(raebur 6882:3): 	$r->print(
                   6883:3):             '<p class="LC_error">'
                   6884:3):            .&mt('Unable to accept last correction, an error occurred: [_1]',
                   6885:3):                 $errmsg)
          1(raebur 6886:3):            .'</p>');
1.157     albertel 6887:     } else {
1.200     albertel 6888: 	&scantron_put_line($scanlines,$scan_data,$which,$line,$skip);
1.157     albertel 6889: 	&scantron_putfile($scanlines,$scan_data);
                   6890:     }
                   6891: }
                   6892: 
1.423     albertel 6893: =pod
                   6894: 
                   6895: =item reset_skipping_status
                   6896: 
1.424     albertel 6897:    Forgets the current set of remember skipped scanlines (and thus
                   6898:    reverts back to considering all lines in the
                   6899:    scantron_skipped_<filename> file)
                   6900: 
1.423     albertel 6901: =cut
                   6902: 
1.200     albertel 6903: sub reset_skipping_status {
                   6904:     my ($scanlines,$scan_data)=&scantron_getfile();
                   6905:     &scan_data($scan_data,'remember_skipping',undef,1);
                   6906:     &scantron_putfile(undef,$scan_data);
                   6907: }
                   6908: 
1.423     albertel 6909: =pod
                   6910: 
                   6911: =item start_skipping
                   6912: 
1.424     albertel 6913:    Marks a scanline to be skipped. 
                   6914: 
1.423     albertel 6915: =cut
                   6916: 
1.376     albertel 6917: sub start_skipping {
1.200     albertel 6918:     my ($scan_data,$i)=@_;
                   6919:     my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
1.376     albertel 6920:     if ($env{'form.scantron_options_redo'} =~ /^redo_/) {
                   6921: 	$remembered{$i}=2;
                   6922:     } else {
                   6923: 	$remembered{$i}=1;
                   6924:     }
1.200     albertel 6925:     &scan_data($scan_data,'remember_skipping',join(':',%remembered));
                   6926: }
                   6927: 
1.423     albertel 6928: =pod
                   6929: 
                   6930: =item should_be_skipped
                   6931: 
1.424     albertel 6932:    Checks whether a scanline should be skipped.
                   6933: 
1.423     albertel 6934: =cut
                   6935: 
1.200     albertel 6936: sub should_be_skipped {
1.376     albertel 6937:     my ($scanlines,$scan_data,$i)=@_;
1.257     albertel 6938:     if ($env{'form.scantron_options_redo'} !~ /^redo_/) {
1.200     albertel 6939: 	# not redoing old skips
1.376     albertel 6940: 	if ($scanlines->{'skipped'}[$i]) { return 1; }
1.200     albertel 6941: 	return 0;
                   6942:     }
                   6943:     my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
1.376     albertel 6944: 
                   6945:     if (exists($remembered{$i}) && $remembered{$i} != 2 ) {
                   6946: 	return 0;
                   6947:     }
1.200     albertel 6948:     return 1;
                   6949: }
                   6950: 
1.423     albertel 6951: =pod
                   6952: 
                   6953: =item remember_current_skipped
                   6954: 
1.424     albertel 6955:    Discovers what scanlines are in the scantron_skipped_<filename>
                   6956:    file and remembers them into scan_data for later use.
                   6957: 
1.423     albertel 6958: =cut
                   6959: 
1.200     albertel 6960: sub remember_current_skipped {
                   6961:     my ($scanlines,$scan_data)=&scantron_getfile();
                   6962:     my %to_remember;
                   6963:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
                   6964: 	if ($scanlines->{'skipped'}[$i]) {
                   6965: 	    $to_remember{$i}=1;
                   6966: 	}
                   6967:     }
1.376     albertel 6968: 
1.200     albertel 6969:     &scan_data($scan_data,'remember_skipping',join(':',%to_remember));
                   6970:     &scantron_putfile(undef,$scan_data);
                   6971: }
                   6972: 
1.423     albertel 6973: =pod
                   6974: 
                   6975: =item check_for_error
                   6976: 
1.424     albertel 6977:     Checks if there was an error when attempting to remove a specific
1.596.2.6  raeburn  6978:     scantron_.. bubblesheet data file. Prints out an error if
1.424     albertel 6979:     something went wrong.
                   6980: 
1.423     albertel 6981: =cut
                   6982: 
1.200     albertel 6983: sub check_for_error {
                   6984:     my ($r,$result)=@_;
                   6985:     if ($result ne 'ok' && $result ne 'not_found' ) {
1.492     albertel 6986: 	$r->print(&mt("An error occurred ([_1]) when trying to remove the existing corrections.",$result));
1.200     albertel 6987:     }
                   6988: }
1.157     albertel 6989: 
1.423     albertel 6990: =pod
                   6991: 
                   6992: =item scantron_warning_screen
                   6993: 
1.424     albertel 6994:    Interstitial screen to make sure the operator has selected the
                   6995:    correct options before we start the validation phase.
                   6996: 
1.423     albertel 6997: =cut
                   6998: 
1.203     albertel 6999: sub scantron_warning_screen {
                   7000:     my ($button_text)=@_;
1.257     albertel 7001:     my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
1.284     albertel 7002:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
1.373     albertel 7003:     my $CODElist;
1.284     albertel 7004:     if ($scantron_config{'CODElocation'} &&
                   7005: 	$scantron_config{'CODEstart'} &&
                   7006: 	$scantron_config{'CODElength'}) {
                   7007: 	$CODElist=$env{'form.scantron_CODElist'};
1.596.2.12.2.  8(raebur 7008:4): 	if ($env{'form.scantron_CODElist'} eq '') { $CODElist='<span class="LC_warning">'.&mt('None').'</span>'; }
1.284     albertel 7009: 	$CODElist=
1.492     albertel 7010: 	    '<tr><td><b>'.&mt('List of CODES to validate against:').'</b></td><td><tt>'.
1.373     albertel 7011: 	    $env{'form.scantron_CODElist'}.'</tt></td></tr>';
1.284     albertel 7012:     }
1.596.2.12.2.  (raeburn 7013:):     my $lastbubblepoints;
                   7014:):     if ($env{'form.scantron_lastbubblepoints'} ne '') {
                   7015:):         $lastbubblepoints =
                   7016:):             '<tr><td><b>'.&mt('Hand-graded items: points from last bubble in row').'</b></td><td><tt>'.
                   7017:):             $env{'form.scantron_lastbubblepoints'}.'</tt></td></tr>';
                   7018:):     }
1.492     albertel 7019:     return ('
1.203     albertel 7020: <p>
1.492     albertel 7021: <span class="LC_warning">
1.596.2.12.2.  6(raebur 7022:3): '.&mt("Please double check the information below before clicking on '[_1]'",&mt($button_text)).'</span>
1.203     albertel 7023: </p>
                   7024: <table>
1.492     albertel 7025: <tr><td><b>'.&mt('Sequence to be Graded:').'</b></td><td>'.$title.'</td></tr>
                   7026: <tr><td><b>'.&mt('Data File that will be used:').'</b></td><td><tt>'.$env{'form.scantron_selectfile'}.'</tt></td></tr>
1.596.2.12.2.  (raeburn 7027:): '.$CODElist.$lastbubblepoints.'
1.203     albertel 7028: </table>
                   7029: <br />
1.596.2.12.2.  2(raebur 7030:2): <p> '.&mt("If this information is correct, please click on '[_1]'.",&mt($button_text)).'</p>
                   7031:2): <p> '.&mt("If something is incorrect, please click the 'Grading Menu' button to start over.").'</p>
1.203     albertel 7032: 
                   7033: <br />
1.492     albertel 7034: ');
1.203     albertel 7035: }
                   7036: 
1.423     albertel 7037: =pod
                   7038: 
                   7039: =item scantron_do_warning
                   7040: 
1.424     albertel 7041:    Check if the operator has picked something for all required
                   7042:    fields. Error out if something is missing.
                   7043: 
1.423     albertel 7044: =cut
                   7045: 
1.203     albertel 7046: sub scantron_do_warning {
                   7047:     my ($r)=@_;
1.324     albertel 7048:     my ($symb)=&get_symb($r);
1.203     albertel 7049:     if (!$symb) {return '';}
1.324     albertel 7050:     my $default_form_data=&defaultFormData($symb);
1.203     albertel 7051:     $r->print(&scantron_form_start().$default_form_data);
1.257     albertel 7052:     if ( $env{'form.selectpage'} eq '' ||
                   7053: 	 $env{'form.scantron_selectfile'} eq '' ||
                   7054: 	 $env{'form.scantron_format'} eq '' ) {
1.596.2.4  raeburn  7055: 	$r->print("<p>".&mt('You have forgotten to specify some information. Please go Back and try again.')."</p>");
1.257     albertel 7056: 	if ( $env{'form.selectpage'} eq '') {
1.492     albertel 7057: 	    $r->print('<p><span class="LC_error">'.&mt('You have not selected a Sequence to grade').'</span></p>');
1.237     albertel 7058: 	} 
1.257     albertel 7059: 	if ( $env{'form.scantron_selectfile'} eq '') {
1.596.2.4  raeburn  7060: 	    $r->print('<p><span class="LC_error">'.&mt("You have not selected a file that contains the student's response data.").'</span></p>');
1.237     albertel 7061: 	} 
1.257     albertel 7062: 	if ( $env{'form.scantron_format'} eq '') {
1.596.2.5  raeburn  7063: 	    $r->print('<p><span class="LC_error">'.&mt("You have not selected the format of the student's response data.").'</span></p>');
1.237     albertel 7064: 	} 
                   7065:     } else {
1.265     www      7066: 	my $warning=&scantron_warning_screen('Grading: Validate Records');
1.596.2.12.2.  (raeburn 7067:):         my $bubbledbyhand=&hand_bubble_option();
1.492     albertel 7068: 	$r->print('
1.596.2.12.2.  (raeburn 7069:): '.$warning.$bubbledbyhand.'
1.492     albertel 7070: <input type="submit" name="submit" value="'.&mt('Grading: Validate Records').'" />
1.203     albertel 7071: <input type="hidden" name="command" value="scantron_validate" />
1.492     albertel 7072: ');
1.237     albertel 7073:     }
1.352     albertel 7074:     $r->print("</form><br />".&show_grading_menu_form($symb));
1.203     albertel 7075:     return '';
                   7076: }
                   7077: 
1.423     albertel 7078: =pod
                   7079: 
                   7080: =item scantron_form_start
                   7081: 
1.424     albertel 7082:     html hidden input for remembering all selected grading options
                   7083: 
1.423     albertel 7084: =cut
                   7085: 
1.203     albertel 7086: sub scantron_form_start {
                   7087:     my ($max_bubble)=@_;
                   7088:     my $result= <<SCANTRONFORM;
                   7089: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
1.257     albertel 7090:   <input type="hidden" name="selectpage" value="$env{'form.selectpage'}" />
                   7091:   <input type="hidden" name="scantron_format" value="$env{'form.scantron_format'}" />
                   7092:   <input type="hidden" name="scantron_selectfile" value="$env{'form.scantron_selectfile'}" />
1.218     albertel 7093:   <input type="hidden" name="scantron_maxbubble" value="$max_bubble" />
1.257     albertel 7094:   <input type="hidden" name="scantron_CODElist" value="$env{'form.scantron_CODElist'}" />
                   7095:   <input type="hidden" name="scantron_CODEunique" value="$env{'form.scantron_CODEunique'}" />
                   7096:   <input type="hidden" name="scantron_options_redo" value="$env{'form.scantron_options_redo'}" />
                   7097:   <input type="hidden" name="scantron_options_ignore" value="$env{'form.scantron_options_ignore'}" />
1.331     albertel 7098:   <input type="hidden" name="scantron_options_hidden" value="$env{'form.scantron_options_hidden'}" />
1.203     albertel 7099: SCANTRONFORM
1.447     foxr     7100: 
                   7101:   my $line = 0;
                   7102:     while (defined($env{"form.scantron.bubblelines.$line"})) {
                   7103:        my $chunk =
                   7104: 	   '<input type="hidden" name="scantron.bubblelines.'.$line.'" value="'.$env{"form.scantron.bubblelines.$line"}.'" />'."\n";
1.448     foxr     7105:        $chunk .=
                   7106: 	   '<input type="hidden" name="scantron.first_bubble_line.'.$line.'" value="'.$env{"form.scantron.first_bubble_line.$line"}.'" />'."\n";
1.503     raeburn  7107:        $chunk .= 
                   7108:            '<input type="hidden" name="scantron.sub_bubblelines.'.$line.'" value="'.$env{"form.scantron.sub_bubblelines.$line"}.'" />'."\n";
1.504     raeburn  7109:        $chunk .=
                   7110:            '<input type="hidden" name="scantron.responsetype.'.$line.'" value="'.$env{"form.scantron.responsetype.$line"}.'" />'."\n";
1.596.2.12.2.  6(raebur 7111:3):        $chunk .=
                   7112:3):            '<input type="hidden" name="scantron.residpart.'.$line.'" value="'.$env{"form.scantron.residpart.$line"}.'" />'."\n";
1.447     foxr     7113:        $result .= $chunk;
                   7114:        $line++;
1.596.2.12.2.  6(raebur 7115:3):     }
1.203     albertel 7116:     return $result;
                   7117: }
                   7118: 
1.423     albertel 7119: =pod
                   7120: 
                   7121: =item scantron_validate_file
                   7122: 
1.596.2.6  raeburn  7123:     Dispatch routine for doing validation of a bubblesheet data file.
1.424     albertel 7124: 
                   7125:     Also processes any necessary information resets that need to
                   7126:     occur before validation begins (ignore previous corrections,
                   7127:     restarting the skipped records processing)
                   7128: 
1.423     albertel 7129: =cut
                   7130: 
1.157     albertel 7131: sub scantron_validate_file {
                   7132:     my ($r) = @_;
1.324     albertel 7133:     my ($symb)=&get_symb($r);
1.157     albertel 7134:     if (!$symb) {return '';}
1.324     albertel 7135:     my $default_form_data=&defaultFormData($symb);
1.200     albertel 7136:     
1.596.2.12.2.  0(raebur 7137:3):     # do the detection of only doing skipped records first before we delete
1.424     albertel 7138:     # them when doing the corrections reset
1.257     albertel 7139:     if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {
1.200     albertel 7140: 	&reset_skipping_status();
                   7141:     }
1.257     albertel 7142:     if ($env{'form.scantron_options_redo'} eq 'redo_skipped') {
1.200     albertel 7143: 	&remember_current_skipped();
1.257     albertel 7144: 	$env{'form.scantron_options_redo'}='redo_skipped_ready';
1.200     albertel 7145:     }
                   7146: 
1.257     albertel 7147:     if ($env{'form.scantron_options_ignore'} eq 'ignore_corrections') {
1.200     albertel 7148: 	&check_for_error($r,&scantron_remove_file('corrected'));
                   7149: 	&check_for_error($r,&scantron_remove_file('skipped'));
                   7150: 	&check_for_error($r,&scantron_remove_scan_data());
1.257     albertel 7151: 	$env{'form.scantron_options_ignore'}='done';
1.192     albertel 7152:     }
1.200     albertel 7153: 
1.257     albertel 7154:     if ($env{'form.scantron_corrections'}) {
1.157     albertel 7155: 	&scantron_process_corrections($r);
                   7156:     }
1.503     raeburn  7157:     $r->print('<p>'.&mt('Gathering necessary information.').'</p>');$r->rflush();
1.157     albertel 7158:     #get the student pick code ready
                   7159:     $r->print(&Apache::loncommon::studentbrowser_javascript());
1.582     raeburn  7160:     my $nav_error;
1.596.2.12.2.  (raeburn 7161:):     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
                   7162:):     my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
1.582     raeburn  7163:     if ($nav_error) {
                   7164:         $r->print(&navmap_errormsg());
                   7165:         return '';
                   7166:     }
1.203     albertel 7167:     my $result=&scantron_form_start($max_bubble).$default_form_data;
1.596.2.12.2.  (raeburn 7168:):     if ($env{'form.scantron_lastbubblepoints'} ne '') {
                   7169:):         $result .= '<input type="hidden" name="scantron_lastbubblepoints" value="'.$env{'form.scantron_lastbubblepoints'}.'" />';
                   7170:):     }
1.157     albertel 7171:     $r->print($result);
                   7172:     
1.334     albertel 7173:     my @validate_phases=( 'sequence',
                   7174: 			  'ID',
1.157     albertel 7175: 			  'CODE',
                   7176: 			  'doublebubble',
                   7177: 			  'missingbubbles');
1.257     albertel 7178:     if (!$env{'form.validatepass'}) {
                   7179: 	$env{'form.validatepass'} = 0;
1.157     albertel 7180:     }
1.257     albertel 7181:     my $currentphase=$env{'form.validatepass'};
1.157     albertel 7182: 
1.448     foxr     7183: 
1.157     albertel 7184:     my $stop=0;
                   7185:     while (!$stop && $currentphase < scalar(@validate_phases)) {
1.503     raeburn  7186: 	$r->print(&mt('Validating '.$validate_phases[$currentphase]).'<br />');
1.157     albertel 7187: 	$r->rflush();
1.596.2.12.2.  6(raebur 7188:3): 
1.157     albertel 7189: 	my $which="scantron_validate_".$validate_phases[$currentphase];
                   7190: 	{
                   7191: 	    no strict 'refs';
                   7192: 	    ($stop,$currentphase)=&$which($r,$currentphase);
                   7193: 	}
                   7194:     }
                   7195:     if (!$stop) {
1.203     albertel 7196: 	my $warning=&scantron_warning_screen('Start Grading');
1.542     raeburn  7197: 	$r->print(&mt('Validation process complete.').'<br />'.
                   7198:                   $warning.
                   7199:                   &mt('Perform verification for each student after storage of submissions?').
                   7200:                   '&nbsp;<span class="LC_nobreak"><label>'.
                   7201:                   '<input type="radio" name="verifyrecord" value="1" />'.&mt('Yes').'</label>'.
                   7202:                   ('&nbsp;'x3).'<label>'.
                   7203:                   '<input type="radio" name="verifyrecord" value="0" checked="checked" />'.&mt('No').
                   7204:                   '</label></span><br />'.
                   7205:                   &mt('Grading will take longer if you use verification.').'<br />'.
1.572     www      7206:                   &mt("Alternatively, the 'Review bubblesheet data' utility (see grading menu) can be used for all students after grading is complete.").'<br /><br />'.
1.542     raeburn  7207:                   '<input type="submit" name="submit" value="'.&mt('Start Grading').'" />'.
                   7208:                   '<input type="hidden" name="command" value="scantron_process" />'."\n");
1.157     albertel 7209:     } else {
                   7210: 	$r->print('<input type="hidden" name="command" value="scantron_validate" />');
                   7211: 	$r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />");
                   7212:     }
                   7213:     if ($stop) {
1.334     albertel 7214: 	if ($validate_phases[$currentphase] eq 'sequence') {
1.539     riegler  7215: 	    $r->print('<input type="submit" name="submit" value="'.&mt('Ignore').' &rarr; " />');
1.492     albertel 7216: 	    $r->print(' '.&mt('this error').' <br />');
1.334     albertel 7217: 
1.492     albertel 7218: 	    $r->print(" <p>".&mt("Or click the 'Grading Menu' button to start over.")."</p>");
1.334     albertel 7219: 	} else {
1.503     raeburn  7220:             if ($validate_phases[$currentphase] eq 'doublebubble' || $validate_phases[$currentphase] eq 'missingbubbles') {
1.539     riegler  7221: 	        $r->print('<input type="button" name="submitbutton" value="'.&mt('Continue').' &rarr;" onclick="javascript:verify_bubble_radio(this.form)" />');
1.503     raeburn  7222:             } else {
1.539     riegler  7223:                 $r->print('<input type="submit" name="submit" value="'.&mt('Continue').' &rarr;" />');
1.503     raeburn  7224:             }
1.492     albertel 7225: 	    $r->print(' '.&mt('using corrected info').' <br />');
                   7226: 	    $r->print("<input type='submit' value='".&mt("Skip")."' name='scantron_skip_record' />");
                   7227: 	    $r->print(" ".&mt("this scanline saving it for later."));
1.334     albertel 7228: 	}
1.157     albertel 7229:     }
1.352     albertel 7230:     $r->print(" </form><br />".&show_grading_menu_form($symb));
1.157     albertel 7231:     return '';
                   7232: }
                   7233: 
1.423     albertel 7234: 
                   7235: =pod
                   7236: 
                   7237: =item scantron_remove_file
                   7238: 
1.596.2.6  raeburn  7239:    Removes the requested bubblesheet data file, makes sure that
1.424     albertel 7240:    scantron_original_<filename> is never removed
                   7241: 
                   7242: 
1.423     albertel 7243: =cut
                   7244: 
1.200     albertel 7245: sub scantron_remove_file {
1.192     albertel 7246:     my ($which)=@_;
1.257     albertel 7247:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
                   7248:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
1.192     albertel 7249:     my $file='scantron_';
1.200     albertel 7250:     if ($which eq 'corrected' || $which eq 'skipped') {
                   7251: 	$file.=$which.'_';
1.192     albertel 7252:     } else {
                   7253: 	return 'refused';
                   7254:     }
1.257     albertel 7255:     $file.=$env{'form.scantron_selectfile'};
1.200     albertel 7256:     return &Apache::lonnet::removeuserfile($cname,$cdom,$file);
                   7257: }
                   7258: 
1.423     albertel 7259: 
                   7260: =pod
                   7261: 
                   7262: =item scantron_remove_scan_data
                   7263: 
1.596.2.6  raeburn  7264:    Removes all scan_data correction for the requested bubblesheet
1.424     albertel 7265:    data file.  (In the case that both the are doing skipped records we need
                   7266:    to remember the old skipped lines for the time being so that element
                   7267:    persists for a while.)
                   7268: 
1.423     albertel 7269: =cut
                   7270: 
1.200     albertel 7271: sub scantron_remove_scan_data {
1.257     albertel 7272:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
                   7273:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
1.192     albertel 7274:     my @keys=&Apache::lonnet::getkeys('nohist_scantrondata',$cdom,$cname);
                   7275:     my @todelete;
1.257     albertel 7276:     my $filename=$env{'form.scantron_selectfile'};
1.192     albertel 7277:     foreach my $key (@keys) {
                   7278: 	if ($key=~/^\Q$filename\E_/) {
1.257     albertel 7279: 	    if ($env{'form.scantron_options_redo'} eq 'redo_skipped_ready' &&
1.200     albertel 7280: 		$key=~/remember_skipping/) {
                   7281: 		next;
                   7282: 	    }
1.192     albertel 7283: 	    push(@todelete,$key);
                   7284: 	}
                   7285:     }
1.200     albertel 7286:     my $result;
1.192     albertel 7287:     if (@todelete) {
1.491     albertel 7288: 	$result = &Apache::lonnet::del('nohist_scantrondata',
                   7289: 				       \@todelete,$cdom,$cname);
                   7290:     } else {
                   7291: 	$result = 'ok';
1.192     albertel 7292:     }
                   7293:     return $result;
                   7294: }
                   7295: 
1.423     albertel 7296: 
                   7297: =pod
                   7298: 
                   7299: =item scantron_getfile
                   7300: 
1.596.2.6  raeburn  7301:     Fetches the requested bubblesheet data file (all 3 versions), and
1.424     albertel 7302:     the scan_data hash
                   7303:   
                   7304:   Arguments:
                   7305:     None
                   7306: 
                   7307:   Returns:
                   7308:     2 hash references
                   7309: 
                   7310:      - first one has 
                   7311:          orig      -
                   7312:          corrected -
                   7313:          skipped   -  each of which points to an array ref of the specified
                   7314:                       file broken up into individual lines
                   7315:          count     - number of scanlines
                   7316:  
                   7317:      - second is the scan_data hash possible keys are
1.425     albertel 7318:        ($number refers to scanline numbered $number and thus the key affects
                   7319:         only that scanline
                   7320:         $bubline refers to the specific bubble line element and the aspects
                   7321:         refers to that specific bubble line element)
                   7322: 
                   7323:        $number.user - username:domain to use
                   7324:        $number.CODE_ignore_dup 
                   7325:                     - ignore the duplicate CODE error 
                   7326:        $number.useCODE
                   7327:                     - use the CODE in the scanline as is
                   7328:        $number.no_bubble.$bubline
                   7329:                     - it is valid that there is no bubbled in bubble
                   7330:                       at $number $bubline
                   7331:        remember_skipping
                   7332:                     - a frozen hash containing keys of $number and values
                   7333:                       of either 
                   7334:                         1 - we are on a 'do skipped records pass' and plan
                   7335:                             on processing this line
                   7336:                         2 - we are on a 'do skipped records pass' and this
                   7337:                             scanline has been marked to skip yet again
1.424     albertel 7338: 
1.423     albertel 7339: =cut
                   7340: 
1.157     albertel 7341: sub scantron_getfile {
1.200     albertel 7342:     #FIXME really would prefer a scantron directory
1.257     albertel 7343:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
                   7344:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
1.157     albertel 7345:     my $lines;
                   7346:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
1.257     albertel 7347: 		       'scantron_orig_'.$env{'form.scantron_selectfile'});
1.157     albertel 7348:     my %scanlines;
                   7349:     $scanlines{'orig'}=[(split("\n",$lines,-1))];
                   7350:     my $temp=$scanlines{'orig'};
                   7351:     $scanlines{'count'}=$#$temp;
                   7352: 
                   7353:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
1.257     albertel 7354: 		       'scantron_corrected_'.$env{'form.scantron_selectfile'});
1.157     albertel 7355:     if ($lines eq '-1') {
                   7356: 	$scanlines{'corrected'}=[];
                   7357:     } else {
                   7358: 	$scanlines{'corrected'}=[(split("\n",$lines,-1))];
                   7359:     }
                   7360:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
1.257     albertel 7361: 		       'scantron_skipped_'.$env{'form.scantron_selectfile'});
1.157     albertel 7362:     if ($lines eq '-1') {
                   7363: 	$scanlines{'skipped'}=[];
                   7364:     } else {
                   7365: 	$scanlines{'skipped'}=[(split("\n",$lines,-1))];
                   7366:     }
1.175     albertel 7367:     my @tmp=&Apache::lonnet::dump('nohist_scantrondata',$cdom,$cname);
1.157     albertel 7368:     if ($tmp[0] =~ /^(error:|no_such_host)/) { @tmp=(); }
                   7369:     my %scan_data = @tmp;
                   7370:     return (\%scanlines,\%scan_data);
                   7371: }
                   7372: 
1.423     albertel 7373: =pod
                   7374: 
                   7375: =item lonnet_putfile
                   7376: 
1.424     albertel 7377:    Wrapper routine to call &Apache::lonnet::finishuserfileupload
                   7378: 
                   7379:  Arguments:
                   7380:    $contents - data to store
                   7381:    $filename - filename to store $contents into
                   7382: 
                   7383:  Returns:
                   7384:    result value from &Apache::lonnet::finishuserfileupload
                   7385: 
1.423     albertel 7386: =cut
                   7387: 
1.157     albertel 7388: sub lonnet_putfile {
                   7389:     my ($contents,$filename)=@_;
1.257     albertel 7390:     my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
                   7391:     my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'};
                   7392:     $env{'form.sillywaytopassafilearound'}=$contents;
1.275     albertel 7393:     &Apache::lonnet::finishuserfileupload($docuname,$docudom,'sillywaytopassafilearound',$filename);
1.157     albertel 7394: 
                   7395: }
                   7396: 
1.423     albertel 7397: =pod
                   7398: 
                   7399: =item scantron_putfile
                   7400: 
1.596.2.6  raeburn  7401:     Stores the current version of the bubblesheet data files, and the
1.424     albertel 7402:     scan_data hash. (Does not modify the original version only the
                   7403:     corrected and skipped versions.
                   7404: 
                   7405:  Arguments:
                   7406:     $scanlines - hash ref that looks like the first return value from
                   7407:                  &scantron_getfile()
                   7408:     $scan_data - hash ref that looks like the second return value from
                   7409:                  &scantron_getfile()
                   7410: 
1.423     albertel 7411: =cut
                   7412: 
1.157     albertel 7413: sub scantron_putfile {
                   7414:     my ($scanlines,$scan_data) = @_;
1.200     albertel 7415:     #FIXME really would prefer a scantron directory
1.257     albertel 7416:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
                   7417:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
1.200     albertel 7418:     if ($scanlines) {
                   7419: 	my $prefix='scantron_';
1.157     albertel 7420: # no need to update orig, shouldn't change
                   7421: #   &lonnet_putfile(join("\n",@{$scanlines->{'orig'}}),$prefix.'orig_'.
1.257     albertel 7422: #		    $env{'form.scantron_selectfile'});
1.200     albertel 7423: 	&lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}),
                   7424: 			$prefix.'corrected_'.
1.257     albertel 7425: 			$env{'form.scantron_selectfile'});
1.200     albertel 7426: 	&lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}),
                   7427: 			$prefix.'skipped_'.
1.257     albertel 7428: 			$env{'form.scantron_selectfile'});
1.200     albertel 7429:     }
1.175     albertel 7430:     &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname);
1.157     albertel 7431: }
                   7432: 
1.423     albertel 7433: =pod
                   7434: 
                   7435: =item scantron_get_line
                   7436: 
1.424     albertel 7437:    Returns the correct version of the scanline
                   7438: 
                   7439:  Arguments:
                   7440:     $scanlines - hash ref that looks like the first return value from
                   7441:                  &scantron_getfile()
                   7442:     $scan_data - hash ref that looks like the second return value from
                   7443:                  &scantron_getfile()
                   7444:     $i         - number of the requested line (starts at 0)
                   7445: 
                   7446:  Returns:
                   7447:    A scanline, (either the original or the corrected one if it
                   7448:    exists), or undef if the requested scanline should be
                   7449:    skipped. (Either because it's an skipped scanline, or it's an
                   7450:    unskipped scanline and we are not doing a 'do skipped scanlines'
                   7451:    pass.
                   7452: 
1.423     albertel 7453: =cut
                   7454: 
1.157     albertel 7455: sub scantron_get_line {
1.200     albertel 7456:     my ($scanlines,$scan_data,$i)=@_;
1.376     albertel 7457:     if (&should_be_skipped($scanlines,$scan_data,$i)) { return undef; }
                   7458:     #if ($scanlines->{'skipped'}[$i]) { return undef; }
1.157     albertel 7459:     if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];}
                   7460:     return $scanlines->{'orig'}[$i]; 
                   7461: }
                   7462: 
1.423     albertel 7463: =pod
                   7464: 
                   7465: =item scantron_todo_count
                   7466: 
1.424     albertel 7467:     Counts the number of scanlines that need processing.
                   7468: 
                   7469:  Arguments:
                   7470:     $scanlines - hash ref that looks like the first return value from
                   7471:                  &scantron_getfile()
                   7472:     $scan_data - hash ref that looks like the second return value from
                   7473:                  &scantron_getfile()
                   7474: 
                   7475:  Returns:
                   7476:     $count - number of scanlines to process
                   7477: 
1.423     albertel 7478: =cut
                   7479: 
1.200     albertel 7480: sub get_todo_count {
                   7481:     my ($scanlines,$scan_data)=@_;
                   7482:     my $count=0;
                   7483:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
                   7484: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
                   7485: 	if ($line=~/^[\s\cz]*$/) { next; }
                   7486: 	$count++;
                   7487:     }
                   7488:     return $count;
                   7489: }
                   7490: 
1.423     albertel 7491: =pod
                   7492: 
                   7493: =item scantron_put_line
                   7494: 
1.596.2.6  raeburn  7495:     Updates the 'corrected' or 'skipped' versions of the bubblesheet
1.424     albertel 7496:     data file.
                   7497: 
                   7498:  Arguments:
                   7499:     $scanlines - hash ref that looks like the first return value from
                   7500:                  &scantron_getfile()
                   7501:     $scan_data - hash ref that looks like the second return value from
                   7502:                  &scantron_getfile()
                   7503:     $i         - line number to update
                   7504:     $newline   - contents of the updated scanline
                   7505:     $skip      - if true make the line for skipping and update the
                   7506:                  'skipped' file
                   7507: 
1.423     albertel 7508: =cut
                   7509: 
1.157     albertel 7510: sub scantron_put_line {
1.200     albertel 7511:     my ($scanlines,$scan_data,$i,$newline,$skip)=@_;
1.157     albertel 7512:     if ($skip) {
                   7513: 	$scanlines->{'skipped'}[$i]=$newline;
1.376     albertel 7514: 	&start_skipping($scan_data,$i);
1.157     albertel 7515: 	return;
                   7516:     }
                   7517:     $scanlines->{'corrected'}[$i]=$newline;
                   7518: }
                   7519: 
1.423     albertel 7520: =pod
                   7521: 
                   7522: =item scantron_clear_skip
                   7523: 
1.424     albertel 7524:    Remove a line from the 'skipped' file
                   7525: 
                   7526:  Arguments:
                   7527:     $scanlines - hash ref that looks like the first return value from
                   7528:                  &scantron_getfile()
                   7529:     $scan_data - hash ref that looks like the second return value from
                   7530:                  &scantron_getfile()
                   7531:     $i         - line number to update
                   7532: 
1.423     albertel 7533: =cut
                   7534: 
1.376     albertel 7535: sub scantron_clear_skip {
                   7536:     my ($scanlines,$scan_data,$i)=@_;
                   7537:     if (exists($scanlines->{'skipped'}[$i])) {
                   7538: 	undef($scanlines->{'skipped'}[$i]);
                   7539: 	return 1;
                   7540:     }
                   7541:     return 0;
                   7542: }
                   7543: 
1.423     albertel 7544: =pod
                   7545: 
                   7546: =item scantron_filter_not_exam
                   7547: 
1.424     albertel 7548:    Filter routine used by &Apache::lonnavmaps::retrieveResources(), to
                   7549:    filter out resources that are not marked as 'exam' mode
                   7550: 
1.423     albertel 7551: =cut
                   7552: 
1.334     albertel 7553: sub scantron_filter_not_exam {
                   7554:     my ($curres)=@_;
                   7555:     
                   7556:     if (ref($curres) && $curres->is_problem() && !$curres->is_exam()) {
                   7557: 	# if the user has asked to not have either hidden
                   7558: 	# or 'randomout' controlled resources to be graded
                   7559: 	# don't include them
                   7560: 	if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
                   7561: 	    && $curres->randomout) {
                   7562: 	    return 0;
                   7563: 	}
                   7564: 	return 1;
                   7565:     }
                   7566:     return 0;
                   7567: }
                   7568: 
1.423     albertel 7569: =pod
                   7570: 
                   7571: =item scantron_validate_sequence
                   7572: 
1.424     albertel 7573:     Validates the selected sequence, checking for resource that are
                   7574:     not set to exam mode.
                   7575: 
1.423     albertel 7576: =cut
                   7577: 
1.334     albertel 7578: sub scantron_validate_sequence {
                   7579:     my ($r,$currentphase) = @_;
                   7580: 
                   7581:     my $navmap=Apache::lonnavmaps::navmap->new();
1.582     raeburn  7582:     unless (ref($navmap)) {
                   7583:         $r->print(&navmap_errormsg());
                   7584:         return (1,$currentphase);
                   7585:     }
1.334     albertel 7586:     my (undef,undef,$sequence)=
                   7587: 	&Apache::lonnet::decode_symb($env{'form.selectpage'});
                   7588: 
                   7589:     my $map=$navmap->getResourceByUrl($sequence);
                   7590: 
                   7591:     $r->print('<input type="hidden" name="validate_sequence_exam"
                   7592:                                     value="ignore" />');
                   7593:     if ($env{'form.validate_sequence_exam'} ne 'ignore') {
                   7594: 	my @resources=
                   7595: 	    $navmap->retrieveResources($map,\&scantron_filter_not_exam,1,0);
                   7596: 	if (@resources) {
1.596.2.12.2.  0(raebur 7597:2): 	    $r->print('<p class="LC_warning">'
                   7598:2):                .&mt('Some resources in the sequence currently are not set to'
                   7599:2):                    .' exam mode. Grading these resources currently may not'
                   7600:2):                    .' work correctly.')
                   7601:2):                .'</p>'
                   7602:2):             );
1.334     albertel 7603: 	    return (1,$currentphase);
                   7604: 	}
                   7605:     }
                   7606: 
                   7607:     return (0,$currentphase+1);
                   7608: }
                   7609: 
1.423     albertel 7610: 
                   7611: 
1.157     albertel 7612: sub scantron_validate_ID {
                   7613:     my ($r,$currentphase) = @_;
                   7614:     
                   7615:     #get student info
                   7616:     my $classlist=&Apache::loncoursedata::get_classlist();
                   7617:     my %idmap=&username_to_idmap($classlist);
                   7618: 
                   7619:     #get scantron line setup
1.257     albertel 7620:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
1.157     albertel 7621:     my ($scanlines,$scan_data)=&scantron_getfile();
1.582     raeburn  7622: 
                   7623:     my $nav_error;
1.596.2.12.2.  (raeburn 7624:):     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the bubble_lines.. array.
1.582     raeburn  7625:     if ($nav_error) {
                   7626:         $r->print(&navmap_errormsg());
                   7627:         return(1,$currentphase);
                   7628:     }
1.157     albertel 7629: 
                   7630:     my %found=('ids'=>{},'usernames'=>{});
                   7631:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
1.200     albertel 7632: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
1.157     albertel 7633: 	if ($line=~/^[\s\cz]*$/) { next; }
                   7634: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
                   7635: 						 $scan_data);
                   7636: 	my $id=$$scan_record{'scantron.ID'};
                   7637: 	my $found;
                   7638: 	foreach my $checkid (keys(%idmap)) {
                   7639: 	    if (lc($checkid) eq lc($id)) { $found=$checkid;last; }
                   7640: 	}
                   7641: 	if ($found) {
                   7642: 	    my $username=$idmap{$found};
                   7643: 	    if ($found{'ids'}{$found}) {
                   7644: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
                   7645: 					 $line,'duplicateID',$found);
1.194     albertel 7646: 		return(1,$currentphase);
1.157     albertel 7647: 	    } elsif ($found{'usernames'}{$username}) {
                   7648: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
                   7649: 					 $line,'duplicateID',$username);
1.194     albertel 7650: 		return(1,$currentphase);
1.157     albertel 7651: 	    }
1.186     albertel 7652: 	    #FIXME store away line we previously saw the ID on to use above
1.157     albertel 7653: 	    $found{'ids'}{$found}++;
                   7654: 	    $found{'usernames'}{$username}++;
                   7655: 	} else {
                   7656: 	    if ($id =~ /^\s*$/) {
1.158     albertel 7657: 		my $username=&scan_data($scan_data,"$i.user");
1.157     albertel 7658: 		if (defined($username) && $found{'usernames'}{$username}) {
                   7659: 		    &scantron_get_correction($r,$i,$scan_record,
                   7660: 					     \%scantron_config,
                   7661: 					     $line,'duplicateID',$username);
1.194     albertel 7662: 		    return(1,$currentphase);
1.157     albertel 7663: 		} elsif (!defined($username)) {
                   7664: 		    &scantron_get_correction($r,$i,$scan_record,
                   7665: 					     \%scantron_config,
                   7666: 					     $line,'incorrectID');
1.194     albertel 7667: 		    return(1,$currentphase);
1.157     albertel 7668: 		}
                   7669: 		$found{'usernames'}{$username}++;
                   7670: 	    } else {
                   7671: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
                   7672: 					 $line,'incorrectID');
1.194     albertel 7673: 		return(1,$currentphase);
1.157     albertel 7674: 	    }
                   7675: 	}
                   7676:     }
                   7677: 
                   7678:     return (0,$currentphase+1);
                   7679: }
                   7680: 
1.423     albertel 7681: 
1.157     albertel 7682: sub scantron_get_correction {
1.596.2.12.2.  6(raebur 7683:3):     my ($r,$i,$scan_record,$scan_config,$line,$error,$arg,
                   7684:3):         $randomorder,$randompick,$respnumlookup,$startline)=@_;
1.454     banghart 7685: #FIXME in the case of a duplicated ID the previous line, probably need
1.157     albertel 7686: #to show both the current line and the previous one and allow skipping
                   7687: #the previous one or the current one
                   7688: 
1.333     albertel 7689:     if ( $$scan_record{'scantron.PaperID'} =~ /\S/) {
1.596.2.6  raeburn  7690:         $r->print(
                   7691:             '<p class="LC_warning">'
                   7692:            .&mt('An error was detected ([_1]) for PaperID [_2]',
                   7693:                 "<b>$error</b>",
                   7694:                 '<tt>'.$$scan_record{'scantron.PaperID'}.'</tt>')
                   7695:            ."</p> \n");
1.157     albertel 7696:     } else {
1.596.2.6  raeburn  7697:         $r->print(
                   7698:             '<p class="LC_warning">'
                   7699:            .&mt('An error was detected ([_1]) in scanline [_2] [_3]',
                   7700:                 "<b>$error</b>", $i, "<pre>$line</pre>")
                   7701:            ."</p> \n");
                   7702:     }
                   7703:     my $message =
                   7704:         '<p>'
                   7705:        .&mt('The ID on the form is [_1]',
                   7706:             "<tt>$$scan_record{'scantron.ID'}</tt>")
                   7707:        .'<br />'
1.596.2.12  raeburn  7708:        .&mt('The name on the paper is [_1], [_2]',
1.596.2.6  raeburn  7709:             $$scan_record{'scantron.LastName'},
                   7710:             $$scan_record{'scantron.FirstName'})
                   7711:        .'</p>';
1.242     albertel 7712: 
1.157     albertel 7713:     $r->print('<input type="hidden" name="scantron_corrections" value="'.$error.'" />'."\n");
                   7714:     $r->print('<input type="hidden" name="scantron_line" value="'.$i.'" />'."\n");
1.503     raeburn  7715:                            # Array populated for doublebubble or
                   7716:     my @lines_to_correct;  # missingbubble errors to build javascript
                   7717:                            # to validate radio button checking   
                   7718: 
1.157     albertel 7719:     if ($error =~ /ID$/) {
1.186     albertel 7720: 	if ($error eq 'incorrectID') {
1.596.2.6  raeburn  7721: 	    $r->print('<p class="LC_warning">'.&mt("The encoded ID is not in the classlist").
1.492     albertel 7722: 		      "</p>\n");
1.157     albertel 7723: 	} elsif ($error eq 'duplicateID') {
1.596.2.6  raeburn  7724: 	    $r->print('<p class="LC_warning">'.&mt("The encoded ID has also been used by a previous paper [_1]",$arg)."</p>\n");
1.157     albertel 7725: 	}
1.242     albertel 7726: 	$r->print($message);
1.492     albertel 7727: 	$r->print("<p>".&mt("How should I handle this?")." <br /> \n");
1.157     albertel 7728: 	$r->print("\n<ul><li> ");
                   7729: 	#FIXME it would be nice if this sent back the user ID and
                   7730: 	#could do partial userID matches
                   7731: 	$r->print(&Apache::loncommon::selectstudent_link('scantronupload',
                   7732: 				       'scantron_username','scantron_domain'));
                   7733: 	$r->print(": <input type='text' name='scantron_username' value='' />");
1.596.2.12.2.  3(raebur 7734:3): 	$r->print("\n:\n".
1.257     albertel 7735: 		 &Apache::loncommon::select_dom_form($env{'request.role.domain'},'scantron_domain'));
1.157     albertel 7736: 
                   7737: 	$r->print('</li>');
1.186     albertel 7738:     } elsif ($error =~ /CODE$/) {
                   7739: 	if ($error eq 'incorrectCODE') {
1.596.2.6  raeburn  7740: 	    $r->print('<p class="LC_warning">'.&mt("The encoded CODE is not in the list of possible CODEs.")."</p>\n");
1.186     albertel 7741: 	} elsif ($error eq 'duplicateCODE') {
1.596.2.6  raeburn  7742: 	    $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");
1.186     albertel 7743: 	}
1.596.2.6  raeburn  7744:         $r->print("<p>".&mt('The CODE on the form is [_1]',
                   7745:                             "<tt>'$$scan_record{'scantron.CODE'}'</tt>")
                   7746:                  ."</p>\n");
1.242     albertel 7747: 	$r->print($message);
1.596.2.6  raeburn  7748: 	$r->print("<p>".&mt("How should I handle this?")."</p>\n");
1.187     albertel 7749: 	$r->print("\n<br /> ");
1.194     albertel 7750: 	my $i=0;
1.273     albertel 7751: 	if ($error eq 'incorrectCODE' 
                   7752: 	    && $$scan_record{'scantron.CODE'}=~/\S/ ) {
1.194     albertel 7753: 	    my ($max,$closest)=&scantron_get_closely_matching_CODEs($arg,$$scan_record{'scantron.CODE'});
1.278     albertel 7754: 	    if ($closest > 0) {
                   7755: 		foreach my $testcode (@{$closest}) {
                   7756: 		    my $checked='';
1.569     bisitz   7757: 		    if (!$i) { $checked=' checked="checked"'; }
1.492     albertel 7758: 		    $r->print("
                   7759:    <label>
1.569     bisitz   7760:        <input type='radio' name='scantron_CODE_resolution' value='use_closest_$i'$checked />
1.492     albertel 7761:        ".&mt("Use the similar CODE [_1] instead.",
                   7762: 	    "<b><tt>".$testcode."</tt></b>")."
                   7763:     </label>
                   7764:     <input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");
1.278     albertel 7765: 		    $r->print("\n<br />");
                   7766: 		    $i++;
                   7767: 		}
1.194     albertel 7768: 	    }
                   7769: 	}
1.273     albertel 7770: 	if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
1.569     bisitz   7771: 	    my $checked; if (!$i) { $checked=' checked="checked"'; }
1.492     albertel 7772: 	    $r->print("
                   7773:     <label>
1.569     bisitz   7774:         <input type='radio' name='scantron_CODE_resolution' value='use_unfound'$checked />
1.596.2.6  raeburn  7775:        ".&mt("Use the CODE [_1] that was on the paper, ignoring the error.",
1.492     albertel 7776: 	     "<b><tt>".$$scan_record{'scantron.CODE'}."</tt></b>")."
                   7777:     </label>");
1.273     albertel 7778: 	    $r->print("\n<br />");
                   7779: 	}
1.194     albertel 7780: 
1.188     albertel 7781: 	$r->print(<<ENDSCRIPT);
                   7782: <script type="text/javascript">
                   7783: function change_radio(field) {
1.190     albertel 7784:     var slct=document.scantronupload.scantron_CODE_resolution;
1.188     albertel 7785:     var i;
                   7786:     for (i=0;i<slct.length;i++) {
                   7787:         if (slct[i].value==field) { slct[i].checked=true; }
                   7788:     }
                   7789: }
                   7790: </script>
                   7791: ENDSCRIPT
1.187     albertel 7792: 	my $href="/adm/pickcode?".
1.359     www      7793: 	   "form=".&escape("scantronupload").
                   7794: 	   "&scantron_format=".&escape($env{'form.scantron_format'}).
                   7795: 	   "&scantron_CODElist=".&escape($env{'form.scantron_CODElist'}).
                   7796: 	   "&curCODE=".&escape($$scan_record{'scantron.CODE'}).
                   7797: 	   "&scantron_selectfile=".&escape($env{'form.scantron_selectfile'});
1.332     albertel 7798: 	if ($env{'form.scantron_CODElist'} =~ /\S/) { 
1.492     albertel 7799: 	    $r->print("
                   7800:     <label>
                   7801:        <input type='radio' name='scantron_CODE_resolution' value='use_found' />
                   7802:        ".&mt("[_1]Select[_2] a CODE from the list of all CODEs and use it.",
                   7803: 	     "<a target='_blank' href='$href'>","</a>")."
                   7804:     </label> 
1.558     bisitz   7805:     ".&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\')" />'));
1.332     albertel 7806: 	    $r->print("\n<br />");
                   7807: 	}
1.492     albertel 7808: 	$r->print("
                   7809:     <label>
                   7810:        <input type='radio' name='scantron_CODE_resolution' value='use_typed' />
                   7811:        ".&mt("Use [_1] as the CODE.",
                   7812: 	     "</label><input type='text' size='8' name='scantron_CODE_newvalue' onfocus=\"javascript:change_radio('use_typed')\" onkeypress=\"javascript:change_radio('use_typed')\" />"));
1.187     albertel 7813: 	$r->print("\n<br /><br />");
1.157     albertel 7814:     } elsif ($error eq 'doublebubble') {
1.596.2.6  raeburn  7815: 	$r->print('<p class="LC_warning">'.&mt("There have been multiple bubbles scanned for some question(s)")."</p>\n");
1.497     foxr     7816: 
                   7817: 	# The form field scantron_questions is acutally a list of line numbers.
                   7818: 	# represented by this form so:
                   7819: 
1.596.2.12.2.  6(raebur 7820:3): 	my $line_list = &questions_to_line_list($arg,$randomorder,$randompick,
                   7821:3):                                                 $respnumlookup,$startline);
1.497     foxr     7822: 
1.157     albertel 7823: 	$r->print('<input type="hidden" name="scantron_questions" value="'.
1.497     foxr     7824: 		  $line_list.'" />');
1.242     albertel 7825: 	$r->print($message);
1.492     albertel 7826: 	$r->print("<p>".&mt("Please indicate which bubble should be used for grading")."</p>");
1.157     albertel 7827: 	foreach my $question (@{$arg}) {
1.503     raeburn  7828: 	    my @linenums = &prompt_for_corrections($r,$question,$scan_config,
1.596.2.12.2.  6(raebur 7829:3):                                                    $scan_record, $error,
                   7830:3):                                                    $randomorder,$randompick,
                   7831:3):                                                    $respnumlookup,$startline);
1.524     raeburn  7832:             push(@lines_to_correct,@linenums);
1.157     albertel 7833: 	}
1.503     raeburn  7834:         $r->print(&verify_bubbles_checked(@lines_to_correct));
1.157     albertel 7835:     } elsif ($error eq 'missingbubble') {
1.596.2.9  raeburn  7836: 	$r->print('<p class="LC_warning">'.&mt("There have been [_1]no[_2] bubbles scanned for some question(s)",'<b>','</b>')."</p>\n");
1.242     albertel 7837: 	$r->print($message);
1.492     albertel 7838: 	$r->print("<p>".&mt("Please indicate which bubble should be used for grading.")."</p>");
1.503     raeburn  7839: 	$r->print(&mt("Some questions have no scanned bubbles.")."\n");
1.497     foxr     7840: 
1.503     raeburn  7841: 	# The form field scantron_questions is actually a list of line numbers not
1.497     foxr     7842: 	# a list of question numbers. Therefore:
                   7843: 	#
                   7844: 	
1.596.2.12.2.  6(raebur 7845:3): 	my $line_list = &questions_to_line_list($arg,$randomorder,$randompick,
                   7846:3):                                                 $respnumlookup,$startline);
1.497     foxr     7847: 
1.157     albertel 7848: 	$r->print('<input type="hidden" name="scantron_questions" value="'.
1.497     foxr     7849: 		  $line_list.'" />');
1.157     albertel 7850: 	foreach my $question (@{$arg}) {
1.503     raeburn  7851: 	    my @linenums = &prompt_for_corrections($r,$question,$scan_config,
1.596.2.12.2.  6(raebur 7852:3):                                                    $scan_record, $error,
                   7853:3):                                                    $randomorder,$randompick,
                   7854:3):                                                    $respnumlookup,$startline);
1.524     raeburn  7855:             push(@lines_to_correct,@linenums);
1.157     albertel 7856: 	}
1.503     raeburn  7857:         $r->print(&verify_bubbles_checked(@lines_to_correct));
1.157     albertel 7858:     } else {
                   7859: 	$r->print("\n<ul>");
                   7860:     }
                   7861:     $r->print("\n</li></ul>");
1.497     foxr     7862: }
                   7863: 
1.503     raeburn  7864: sub verify_bubbles_checked {
                   7865:     my (@ansnums) = @_;
                   7866:     my $ansnumstr = join('","',@ansnums);
                   7867:     my $warning = &mt("A bubble or 'No bubble' selection has not been made for one or more lines.");
1.596.2.12.2.  6(raebur 7868:6):     &js_escape(\$warning);
1.503     raeburn  7869:     my $output = (<<ENDSCRIPT);
                   7870: <script type="text/javascript">
                   7871: function verify_bubble_radio(form) {
                   7872:     var ansnumArray = new Array ("$ansnumstr");
                   7873:     var need_bubble_count = 0;
                   7874:     for (var i=0; i<ansnumArray.length; i++) {
                   7875:         if (form.elements["scantron_correct_Q_"+ansnumArray[i]].length > 1) {
                   7876:             var bubble_picked = 0; 
                   7877:             for (var j=0; j<form.elements["scantron_correct_Q_"+ansnumArray[i]].length; j++) {
                   7878:                 if (form.elements["scantron_correct_Q_"+ansnumArray[i]][j].checked == true) {
                   7879:                     bubble_picked = 1;
                   7880:                 }
                   7881:             }
                   7882:             if (bubble_picked == 0) {
                   7883:                 need_bubble_count ++;
                   7884:             }
                   7885:         }
                   7886:     }
                   7887:     if (need_bubble_count) {
                   7888:         alert("$warning");
                   7889:         return;
                   7890:     }
                   7891:     form.submit(); 
                   7892: }
                   7893: </script>
                   7894: ENDSCRIPT
                   7895:     return $output;
                   7896: }
                   7897: 
1.497     foxr     7898: =pod
                   7899: 
                   7900: =item  questions_to_line_list
1.157     albertel 7901: 
1.497     foxr     7902: Converts a list of questions into a string of comma separated
                   7903: line numbers in the answer sheet used by the questions.  This is
                   7904: used to fill in the scantron_questions form field.
                   7905: 
                   7906:   Arguments:
                   7907:      questions    - Reference to an array of questions.
1.596.2.12.2.  6(raebur 7908:3):      randomorder  - True if randomorder in use.
                   7909:3):      randompick   - True if randompick in use.
                   7910:3):      respnumlookup - Reference to HASH mapping question numbers in bubble lines
                   7911:3):                      for current line to question number used for same question
                   7912:3):                      in "Master Seqence" (as seen by Course Coordinator).
                   7913:3):      startline    - Reference to hash where key is question number (0 is first)
                   7914:3):                     and key is number of first bubble line for current student
                   7915:3):                     or code-based randompick and/or randomorder.
1.497     foxr     7916: 
                   7917: =cut
                   7918: 
                   7919: 
                   7920: sub questions_to_line_list {
1.596.2.12.2.  6(raebur 7921:3):     my ($questions,$randomorder,$randompick,$respnumlookup,$startline) = @_;
1.497     foxr     7922:     my @lines;
                   7923: 
1.503     raeburn  7924:     foreach my $item (@{$questions}) {
                   7925:         my $question = $item;
                   7926:         my ($first,$count,$last);
                   7927:         if ($item =~ /^(\d+)\.(\d+)$/) {
                   7928:             $question = $1;
                   7929:             my $subquestion = $2;
1.596.2.12.2.  6(raebur 7930:3):             my $responsenum = $question-1;
                   7931:3):             if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
                   7932:3):                 $responsenum = $respnumlookup->{$question-1};
                   7933:3):                 if (ref($startline) eq 'HASH') {
                   7934:3):                     $first = $startline->{$question-1} + 1;
                   7935:3):                 }
                   7936:3):             } else {
                   7937:3):                 $first = $first_bubble_line{$responsenum} + 1;
                   7938:3):             }
          7(raebur 7939:3):             my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
1.503     raeburn  7940:             my $subcount = 1;
                   7941:             while ($subcount<$subquestion) {
                   7942:                 $first += $subans[$subcount-1];
                   7943:                 $subcount ++;
                   7944:             }
                   7945:             $count = $subans[$subquestion-1];
                   7946:         } else {
1.596.2.12.2.  7(raebur 7947:3):             my $responsenum = $question-1;
                   7948:3):             if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
                   7949:3):                 $responsenum = $respnumlookup->{$question-1};
                   7950:3):                 if (ref($startline) eq 'HASH') {
                   7951:3):                     $first = $startline->{$question-1} + 1;
                   7952:3):                 }
                   7953:3):             } else {
                   7954:3):                 $first = $first_bubble_line{$responsenum} + 1;
                   7955:3):             }
                   7956:3):             $count   = $bubble_lines_per_response{$responsenum};
1.503     raeburn  7957:         }
1.506     raeburn  7958:         $last = $first+$count-1;
1.503     raeburn  7959:         push(@lines, ($first..$last));
1.497     foxr     7960:     }
                   7961:     return join(',', @lines);
                   7962: }
                   7963: 
                   7964: =pod 
                   7965: 
                   7966: =item prompt_for_corrections
                   7967: 
                   7968: Prompts for a potentially multiline correction to the
                   7969: user's bubbling (factors out common code from scantron_get_correction
                   7970: for multi and missing bubble cases).
                   7971: 
                   7972:  Arguments:
                   7973:    $r           - Apache request object.
                   7974:    $question    - The question number to prompt for.
                   7975:    $scan_config - The scantron file configuration hash.
                   7976:    $scan_record - Reference to the hash that has the the parsed scanlines.
1.503     raeburn  7977:    $error       - Type of error
1.596.2.12.2.  7(raebur 7978:3):    $randomorder - True if randomorder in use.
                   7979:3):    $randompick  - True if randompick in use.
                   7980:3):    $respnumlookup - Reference to HASH mapping question numbers in bubble lines
                   7981:3):                     for current line to question number used for same question
                   7982:3):                     in "Master Seqence" (as seen by Course Coordinator).
                   7983:3):    $startline   - Reference to hash where key is question number (0 is first)
                   7984:3):                   and value is number of first bubble line for current student
                   7985:3):                   or code-based randompick and/or randomorder.
1.497     foxr     7986: 
                   7987:  Implicit inputs:
                   7988:    %bubble_lines_per_response   - Starting line numbers for each question.
                   7989:                                   Numbered from 0 (but question numbers are from
                   7990:                                   1.
                   7991:    %first_bubble_line           - Starting bubble line for each question.
1.509     raeburn  7992:    %subdivided_bubble_lines     - optionresponse, matchresponse and rankresponse 
                   7993:                                   type problems render as separate sub-questions, 
1.503     raeburn  7994:                                   in exam mode. This hash contains a 
                   7995:                                   comma-separated list of the lines per 
                   7996:                                   sub-question.
1.510     raeburn  7997:    %responsetype_per_response   - essayresponse, formularesponse,
                   7998:                                   stringresponse, imageresponse, reactionresponse,
                   7999:                                   and organicresponse type problem parts can have
1.503     raeburn  8000:                                   multiple lines per response if the weight
                   8001:                                   assigned exceeds 10.  In this case, only
                   8002:                                   one bubble per line is permitted, but more 
                   8003:                                   than one line might contain bubbles, e.g.
                   8004:                                   bubbling of: line 1 - J, line 2 - J, 
                   8005:                                   line 3 - B would assign 22 points.  
1.497     foxr     8006: 
                   8007: =cut
                   8008: 
                   8009: sub prompt_for_corrections {
1.596.2.12.2.  6(raebur 8010:3):     my ($r, $question, $scan_config, $scan_record, $error, $randomorder,
                   8011:3):         $randompick, $respnumlookup, $startline) = @_;
1.503     raeburn  8012:     my ($current_line,$lines);
                   8013:     my @linenums;
                   8014:     my $questionnum = $question;
1.596.2.12.2.  6(raebur 8015:3):     my ($first,$responsenum);
1.503     raeburn  8016:     if ($question =~ /^(\d+)\.(\d+)$/) {
                   8017:         $question = $1;
                   8018:         my $subquestion = $2;
1.596.2.12.2.  6(raebur 8019:3):         if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
                   8020:3):             $responsenum = $respnumlookup->{$question-1};
                   8021:3):             if (ref($startline) eq 'HASH') {
                   8022:3):                 $first = $startline->{$question-1};
                   8023:3):             }
                   8024:3):         } else {
                   8025:3):             $responsenum = $question-1;
          7(raebur 8026:4):             $first = $first_bubble_line{$responsenum};
          6(raebur 8027:3):         }
                   8028:3):         $current_line = $first + 1 ;
                   8029:3):         my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
1.503     raeburn  8030:         my $subcount = 1;
                   8031:         while ($subcount<$subquestion) {
                   8032:             $current_line += $subans[$subcount-1];
                   8033:             $subcount ++;
                   8034:         }
                   8035:         $lines = $subans[$subquestion-1];
                   8036:     } else {
1.596.2.12.2.  6(raebur 8037:3):         if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
                   8038:3):             $responsenum = $respnumlookup->{$question-1};
                   8039:3):             if (ref($startline) eq 'HASH') {
                   8040:3):                 $first = $startline->{$question-1};
                   8041:3):             }
                   8042:3):         } else {
                   8043:3):             $responsenum = $question-1;
                   8044:3):             $first = $first_bubble_line{$responsenum};
                   8045:3):         }
                   8046:3):         $current_line = $first + 1;
                   8047:3):         $lines        = $bubble_lines_per_response{$responsenum};
1.503     raeburn  8048:     }
1.497     foxr     8049:     if ($lines > 1) {
1.503     raeburn  8050:         $r->print(&mt('The group of bubble lines below responds to a single question.').'<br />');
1.596.2.12.2.  6(raebur 8051:3):         if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
                   8052:3):             ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
                   8053:3):             ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
                   8054:3):             ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
                   8055:3):             ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
                   8056:3):             ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
          4(raebur 8057:3):             $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 />');
1.503     raeburn  8058:         } else {
                   8059:             $r->print(&mt("Select at most one bubble in a single line and select 'No Bubble' in all the other lines. ")."<br />");
                   8060:         }
1.497     foxr     8061:     }
                   8062:     for (my $i =0; $i < $lines; $i++) {
1.503     raeburn  8063:         my $selected = $$scan_record{"scantron.$current_line.answer"};
1.596.2.12.2.  6(raebur 8064:3): 	&scantron_bubble_selector($r,$scan_config,$current_line,
1.503     raeburn  8065: 	        		  $questionnum,$error,split('', $selected));
1.524     raeburn  8066:         push(@linenums,$current_line);
1.497     foxr     8067: 	$current_line++;
                   8068:     }
                   8069:     if ($lines > 1) {
                   8070: 	$r->print("<hr /><br />");
                   8071:     }
1.503     raeburn  8072:     return @linenums;
1.157     albertel 8073: }
1.423     albertel 8074: 
                   8075: =pod
                   8076: 
                   8077: =item scantron_bubble_selector
                   8078:   
                   8079:    Generates the html radiobuttons to correct a single bubble line
1.424     albertel 8080:    possibly showing the existing the selected bubbles if known
1.423     albertel 8081: 
                   8082:  Arguments:
                   8083:     $r           - Apache request object
                   8084:     $scan_config - hash from &get_scantron_config()
1.497     foxr     8085:     $line        - Number of the line being displayed.
1.503     raeburn  8086:     $questionnum - Question number (may include subquestion)
                   8087:     $error       - Type of error.
1.497     foxr     8088:     @selected    - Array of bubbles picked on this line.
1.423     albertel 8089: 
                   8090: =cut
                   8091: 
1.157     albertel 8092: sub scantron_bubble_selector {
1.503     raeburn  8093:     my ($r,$scan_config,$line,$questionnum,$error,@selected)=@_;
1.157     albertel 8094:     my $max=$$scan_config{'Qlength'};
1.274     albertel 8095: 
                   8096:     my $scmode=$$scan_config{'Qon'};
1.596.2.12.2.  (raeburn 8097:):     if ($scmode eq 'number' || $scmode eq 'letter') {
                   8098:):         if (($$scan_config{'BubblesPerRow'} =~ /^\d+$/) &&
                   8099:):             ($$scan_config{'BubblesPerRow'} > 0)) {
                   8100:):             $max=$$scan_config{'BubblesPerRow'};
                   8101:):             if (($scmode eq 'number') && ($max > 10)) {
                   8102:):                 $max = 10;
                   8103:):             } elsif (($scmode eq 'letter') && $max > 26) {
                   8104:):                 $max = 26;
                   8105:):             }
                   8106:):         } else {
                   8107:):             $max = 10;
                   8108:):         }
                   8109:):     }
1.274     albertel 8110: 
1.157     albertel 8111:     my @alphabet=('A'..'Z');
1.503     raeburn  8112:     $r->print(&Apache::loncommon::start_data_table().
                   8113:               &Apache::loncommon::start_data_table_row());
                   8114:     $r->print('<td rowspan="2" class="LC_leftcol_header">'.$line.'</td>');
1.497     foxr     8115:     for (my $i=0;$i<$max+1;$i++) {
                   8116: 	$r->print("\n".'<td align="center">');
                   8117: 	if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }
                   8118: 	else { $r->print('&nbsp;'); }
                   8119: 	$r->print('</td>');
                   8120:     }
1.503     raeburn  8121:     $r->print(&Apache::loncommon::end_data_table_row().
                   8122:               &Apache::loncommon::start_data_table_row());
1.497     foxr     8123:     for (my $i=0;$i<$max;$i++) {
                   8124: 	$r->print("\n".
                   8125: 		  '<td><label><input type="radio" name="scantron_correct_Q_'.
                   8126: 		  $line.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");
                   8127:     }
1.503     raeburn  8128:     my $nobub_checked = ' ';
                   8129:     if ($error eq 'missingbubble') {
                   8130:         $nobub_checked = ' checked = "checked" ';
                   8131:     }
                   8132:     $r->print("\n".'<td><label><input type="radio" name="scantron_correct_Q_'.
                   8133: 	      $line.'" value="none"'.$nobub_checked.'/>'.&mt('No bubble').
                   8134:               '</label>'."\n".'<input type="hidden" name="scantron_questionnum_Q_'.
                   8135:               $line.'" value="'.$questionnum.'" /></td>');
                   8136:     $r->print(&Apache::loncommon::end_data_table_row().
                   8137:               &Apache::loncommon::end_data_table());
1.157     albertel 8138: }
                   8139: 
1.423     albertel 8140: =pod
                   8141: 
                   8142: =item num_matches
                   8143: 
1.424     albertel 8144:    Counts the number of characters that are the same between the two arguments.
                   8145: 
                   8146:  Arguments:
                   8147:    $orig - CODE from the scanline
                   8148:    $code - CODE to match against
                   8149: 
                   8150:  Returns:
                   8151:    $count - integer count of the number of same characters between the
                   8152:             two arguments
                   8153: 
1.423     albertel 8154: =cut
                   8155: 
1.194     albertel 8156: sub num_matches {
                   8157:     my ($orig,$code) = @_;
                   8158:     my @code=split(//,$code);
                   8159:     my @orig=split(//,$orig);
                   8160:     my $same=0;
                   8161:     for (my $i=0;$i<scalar(@code);$i++) {
                   8162: 	if ($code[$i] eq $orig[$i]) { $same++; }
                   8163:     }
                   8164:     return $same;
                   8165: }
                   8166: 
1.423     albertel 8167: =pod
                   8168: 
                   8169: =item scantron_get_closely_matching_CODEs
                   8170: 
1.424     albertel 8171:    Cycles through all CODEs and finds the set that has the greatest
                   8172:    number of same characters as the provided CODE
                   8173: 
                   8174:  Arguments:
                   8175:    $allcodes - hash ref returned by &get_codes()
                   8176:    $CODE     - CODE from the current scanline
                   8177: 
                   8178:  Returns:
                   8179:    2 element list
                   8180:     - first elements is number of how closely matching the best fit is 
                   8181:       (5 means best set has 5 matching characters)
                   8182:     - second element is an arrary ref containing the set of valid CODEs
                   8183:       that best fit the passed in CODE
                   8184: 
1.423     albertel 8185: =cut
                   8186: 
1.194     albertel 8187: sub scantron_get_closely_matching_CODEs {
                   8188:     my ($allcodes,$CODE)=@_;
                   8189:     my @CODEs;
                   8190:     foreach my $testcode (sort(keys(%{$allcodes}))) {
                   8191: 	push(@{$CODEs[&num_matches($CODE,$testcode)]},$testcode);
                   8192:     }
                   8193: 
                   8194:     return ($#CODEs,$CODEs[-1]);
                   8195: }
                   8196: 
1.423     albertel 8197: =pod
                   8198: 
                   8199: =item get_codes
                   8200: 
1.424     albertel 8201:    Builds a hash which has keys of all of the valid CODEs from the selected
                   8202:    set of remembered CODEs.
                   8203: 
                   8204:  Arguments:
                   8205:   $old_name - name of the set of remembered CODEs
                   8206:   $cdom     - domain of the course
                   8207:   $cnum     - internal course name
                   8208: 
                   8209:  Returns:
                   8210:   %allcodes - keys are the valid CODEs, values are all 1
                   8211: 
1.423     albertel 8212: =cut
                   8213: 
1.194     albertel 8214: sub get_codes {
1.280     foxr     8215:     my ($old_name, $cdom, $cnum) = @_;
                   8216:     if (!$old_name) {
                   8217: 	$old_name=$env{'form.scantron_CODElist'};
                   8218:     }
                   8219:     if (!$cdom) {
                   8220: 	$cdom =$env{'course.'.$env{'request.course.id'}.'.domain'};
                   8221:     }
                   8222:     if (!$cnum) {
                   8223: 	$cnum =$env{'course.'.$env{'request.course.id'}.'.num'};
                   8224:     }
1.278     albertel 8225:     my %result=&Apache::lonnet::get('CODEs',[$old_name,"type\0$old_name"],
                   8226: 				    $cdom,$cnum);
                   8227:     my %allcodes;
                   8228:     if ($result{"type\0$old_name"} eq 'number') {
                   8229: 	%allcodes=map {($_,1)} split(',',$result{$old_name});
                   8230:     } else {
                   8231: 	%allcodes=map {(&Apache::lonprintout::num_to_letters($_),1)} split(',',$result{$old_name});
                   8232:     }
1.194     albertel 8233:     return %allcodes;
                   8234: }
                   8235: 
1.423     albertel 8236: =pod
                   8237: 
                   8238: =item scantron_validate_CODE
                   8239: 
1.424     albertel 8240:    Validates all scanlines in the selected file to not have any
                   8241:    invalid or underspecified CODEs and that none of the codes are
                   8242:    duplicated if this was requested.
                   8243: 
1.423     albertel 8244: =cut
                   8245: 
1.157     albertel 8246: sub scantron_validate_CODE {
                   8247:     my ($r,$currentphase) = @_;
1.257     albertel 8248:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
1.186     albertel 8249:     if ($scantron_config{'CODElocation'} &&
                   8250: 	$scantron_config{'CODEstart'} &&
                   8251: 	$scantron_config{'CODElength'}) {
1.257     albertel 8252: 	if (!defined($env{'form.scantron_CODElist'})) {
1.186     albertel 8253: 	    &FIXME_blow_up()
                   8254: 	}
                   8255:     } else {
                   8256: 	return (0,$currentphase+1);
                   8257:     }
                   8258:     
                   8259:     my %usedCODEs;
                   8260: 
1.194     albertel 8261:     my %allcodes=&get_codes();
1.186     albertel 8262: 
1.582     raeburn  8263:     my $nav_error;
1.596.2.12.2.  (raeburn 8264:):     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the lines per response array.
1.582     raeburn  8265:     if ($nav_error) {
                   8266:         $r->print(&navmap_errormsg());
                   8267:         return(1,$currentphase);
                   8268:     }
1.447     foxr     8269: 
1.186     albertel 8270:     my ($scanlines,$scan_data)=&scantron_getfile();
                   8271:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
1.200     albertel 8272: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
1.186     albertel 8273: 	if ($line=~/^[\s\cz]*$/) { next; }
                   8274: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
                   8275: 						 $scan_data);
                   8276: 	my $CODE=$$scan_record{'scantron.CODE'};
                   8277: 	my $error=0;
1.224     albertel 8278: 	if (!&Apache::lonnet::validCODE($CODE)) {
                   8279: 	    &scantron_get_correction($r,$i,$scan_record,
                   8280: 				     \%scantron_config,
                   8281: 				     $line,'incorrectCODE',\%allcodes);
                   8282: 	    return(1,$currentphase);
                   8283: 	}
1.221     albertel 8284: 	if (%allcodes && !exists($allcodes{$CODE}) 
                   8285: 	    && !$$scan_record{'scantron.useCODE'}) {
1.186     albertel 8286: 	    &scantron_get_correction($r,$i,$scan_record,
                   8287: 				     \%scantron_config,
1.194     albertel 8288: 				     $line,'incorrectCODE',\%allcodes);
                   8289: 	    return(1,$currentphase);
1.186     albertel 8290: 	}
1.214     albertel 8291: 	if (exists($usedCODEs{$CODE}) 
1.257     albertel 8292: 	    && $env{'form.scantron_CODEunique'} eq 'yes'
1.192     albertel 8293: 	    && !$$scan_record{'scantron.CODE_ignore_dup'}) {
1.186     albertel 8294: 	    &scantron_get_correction($r,$i,$scan_record,
                   8295: 				     \%scantron_config,
1.194     albertel 8296: 				     $line,'duplicateCODE',$usedCODEs{$CODE});
                   8297: 	    return(1,$currentphase);
1.186     albertel 8298: 	}
1.524     raeburn  8299: 	push(@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
1.186     albertel 8300:     }
1.157     albertel 8301:     return (0,$currentphase+1);
                   8302: }
                   8303: 
1.423     albertel 8304: =pod
                   8305: 
                   8306: =item scantron_validate_doublebubble
                   8307: 
1.424     albertel 8308:    Validates all scanlines in the selected file to not have any
                   8309:    bubble lines with multiple bubbles marked.
                   8310: 
1.423     albertel 8311: =cut
                   8312: 
1.157     albertel 8313: sub scantron_validate_doublebubble {
                   8314:     my ($r,$currentphase) = @_;
                   8315:     #get student info
                   8316:     my $classlist=&Apache::loncoursedata::get_classlist();
                   8317:     my %idmap=&username_to_idmap($classlist);
1.596.2.12.2.  6(raebur 8318:3):     my (undef,undef,$sequence)=
                   8319:3):         &Apache::lonnet::decode_symb($env{'form.selectpage'});
1.157     albertel 8320: 
                   8321:     #get scantron line setup
1.257     albertel 8322:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
1.157     albertel 8323:     my ($scanlines,$scan_data)=&scantron_getfile();
1.596.2.12.2.  6(raebur 8324:3): 
                   8325:3):     my $navmap = Apache::lonnavmaps::navmap->new();
                   8326:3):     unless (ref($navmap)) {
                   8327:3):         $r->print(&navmap_errormsg());
                   8328:3):         return(1,$currentphase);
                   8329:3):     }
                   8330:3):     my $map=$navmap->getResourceByUrl($sequence);
                   8331:3):     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
                   8332:3):     my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
                   8333:3):         %grader_randomlists_by_symb,%orderedforcode,%respnumlookup,%startline);
                   8334:3):     my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
                   8335:3): 
1.583     raeburn  8336:     my $nav_error;
1.596.2.12.2.  6(raebur 8337:3):     if (ref($map)) {
                   8338:3):         $randomorder = $map->randomorder();
                   8339:3):         $randompick = $map->randompick();
                   8340:3):         if ($randomorder || $randompick) {
                   8341:3):             $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
                   8342:3):             if ($nav_error) {
                   8343:3):                 $r->print(&navmap_errormsg());
                   8344:3):                 return(1,$currentphase);
                   8345:3):             }
                   8346:3):             &graders_resources_pass(\@resources,\%grader_partids_by_symb,
                   8347:3):                                     \%grader_randomlists_by_symb,$bubbles_per_row);
                   8348:3):         }
                   8349:3):     } else {
                   8350:3):         $r->print(&navmap_errormsg());
                   8351:3):         return(1,$currentphase);
                   8352:3):     }
                   8353:3): 
          (raeburn 8354:):     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the bubble line array.
1.583     raeburn  8355:     if ($nav_error) {
                   8356:         $r->print(&navmap_errormsg());
                   8357:         return(1,$currentphase);
                   8358:     }
1.447     foxr     8359: 
1.157     albertel 8360:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
1.200     albertel 8361: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
1.157     albertel 8362: 	if ($line=~/^[\s\cz]*$/) { next; }
                   8363: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
1.596.2.12.2.  6(raebur 8364:3): 						 $scan_data,undef,\%idmap,$randomorder,
                   8365:3):                                                  $randompick,$sequence,\@master_seq,
                   8366:3):                                                  \%symb_to_resource,\%grader_partids_by_symb,
                   8367:3):                                                  \%orderedforcode,\%respnumlookup,\%startline);
1.157     albertel 8368: 	if (!defined($$scan_record{'scantron.doubleerror'})) { next; }
                   8369: 	&scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line,
                   8370: 				 'doublebubble',
1.596.2.12.2.  6(raebur 8371:3): 				 $$scan_record{'scantron.doubleerror'},
                   8372:3):                                  $randomorder,$randompick,\%respnumlookup,\%startline);
1.157     albertel 8373:     	return (1,$currentphase);
                   8374:     }
                   8375:     return (0,$currentphase+1);
                   8376: }
                   8377: 
1.423     albertel 8378: 
1.503     raeburn  8379: sub scantron_get_maxbubble {
1.596.2.12.2.  (raeburn 8380:):     my ($nav_error,$scantron_config) = @_;
1.257     albertel 8381:     if (defined($env{'form.scantron_maxbubble'}) &&
                   8382: 	$env{'form.scantron_maxbubble'}) {
1.447     foxr     8383: 	&restore_bubble_lines();
1.257     albertel 8384: 	return $env{'form.scantron_maxbubble'};
1.191     albertel 8385:     }
1.330     albertel 8386: 
1.447     foxr     8387:     my (undef, undef, $sequence) =
1.257     albertel 8388: 	&Apache::lonnet::decode_symb($env{'form.selectpage'});
1.330     albertel 8389: 
1.447     foxr     8390:     my $navmap=Apache::lonnavmaps::navmap->new();
1.582     raeburn  8391:     unless (ref($navmap)) {
                   8392:         if (ref($nav_error)) {
                   8393:             $$nav_error = 1;
                   8394:         }
1.591     raeburn  8395:         return;
1.582     raeburn  8396:     }
1.191     albertel 8397:     my $map=$navmap->getResourceByUrl($sequence);
                   8398:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
1.596.2.12.2.  (raeburn 8399:):     my $bubbles_per_row = &bubblesheet_bubbles_per_row($scantron_config);
1.330     albertel 8400: 
                   8401:     &Apache::lonxml::clear_problem_counter();
                   8402: 
1.557     raeburn  8403:     my $uname       = $env{'user.name'};
                   8404:     my $udom        = $env{'user.domain'};
1.435     foxr     8405:     my $cid         = $env{'request.course.id'};
                   8406:     my $total_lines = 0;
                   8407:     %bubble_lines_per_response = ();
1.447     foxr     8408:     %first_bubble_line         = ();
1.503     raeburn  8409:     %subdivided_bubble_lines   = ();
                   8410:     %responsetype_per_response = ();
1.596.2.12.2.  6(raebur 8411:3):     %masterseq_id_responsenum  = ();
1.554     raeburn  8412: 
1.447     foxr     8413:     my $response_number = 0;
                   8414:     my $bubble_line     = 0;
1.191     albertel 8415:     foreach my $resource (@resources) {
1.596.2.12.2.  6(raebur 8416:3):         my $resid = $resource->id();
          (raeburn 8417:):         my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,
          7(raebur 8418:3):                                                           $udom,undef,$bubbles_per_row);
1.542     raeburn  8419:         if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) {
                   8420: 	    foreach my $part_id (@{$parts}) {
                   8421:                 my $lines;
                   8422: 
                   8423: 	        # TODO - make this a persistent hash not an array.
                   8424: 
                   8425:                 # optionresponse, matchresponse and rankresponse type items 
                   8426:                 # render as separate sub-questions in exam mode.
                   8427:                 if (($analysis->{$part_id.'.type'} eq 'optionresponse') ||
                   8428:                     ($analysis->{$part_id.'.type'} eq 'matchresponse') ||
                   8429:                     ($analysis->{$part_id.'.type'} eq 'rankresponse')) {
                   8430:                     my ($numbub,$numshown);
                   8431:                     if ($analysis->{$part_id.'.type'} eq 'optionresponse') {
                   8432:                         if (ref($analysis->{$part_id.'.options'}) eq 'ARRAY') {
                   8433:                             $numbub = scalar(@{$analysis->{$part_id.'.options'}});
                   8434:                         }
                   8435:                     } elsif ($analysis->{$part_id.'.type'} eq 'matchresponse') {
                   8436:                         if (ref($analysis->{$part_id.'.items'}) eq 'ARRAY') {
                   8437:                             $numbub = scalar(@{$analysis->{$part_id.'.items'}});
                   8438:                         }
                   8439:                     } elsif ($analysis->{$part_id.'.type'} eq 'rankresponse') {
                   8440:                         if (ref($analysis->{$part_id.'.foils'}) eq 'ARRAY') {
                   8441:                             $numbub = scalar(@{$analysis->{$part_id.'.foils'}});
                   8442:                         }
                   8443:                     }
                   8444:                     if (ref($analysis->{$part_id.'.shown'}) eq 'ARRAY') {
                   8445:                         $numshown = scalar(@{$analysis->{$part_id.'.shown'}});
                   8446:                     }
1.596.2.12.2.  (raeburn 8447:):                     my $bubbles_per_row =
                   8448:):                         &bubblesheet_bubbles_per_row($scantron_config);
                   8449:):                     my $inner_bubble_lines = int($numbub/$bubbles_per_row);
                   8450:):                     if (($numbub % $bubbles_per_row) != 0) {
1.542     raeburn  8451:                         $inner_bubble_lines++;
                   8452:                     }
                   8453:                     for (my $i=0; $i<$numshown; $i++) {
                   8454:                         $subdivided_bubble_lines{$response_number} .= 
                   8455:                             $inner_bubble_lines.',';
                   8456:                     }
                   8457:                     $subdivided_bubble_lines{$response_number} =~ s/,$//;
                   8458:                     $lines = $numshown * $inner_bubble_lines;
                   8459:                 } else {
                   8460:                     $lines = $analysis->{"$part_id.bubble_lines"};
1.596.2.12.2.  (raeburn 8461:):                 }
1.542     raeburn  8462: 
                   8463:                 $first_bubble_line{$response_number} = $bubble_line;
                   8464: 	        $bubble_lines_per_response{$response_number} = $lines;
                   8465:                 $responsetype_per_response{$response_number} = 
                   8466:                     $analysis->{$part_id.'.type'};
1.596.2.12.2.  6(raebur 8467:3):                 $masterseq_id_responsenum{$resid.'_'.$part_id} = $response_number;
1.542     raeburn  8468: 	        $response_number++;
                   8469: 
                   8470: 	        $bubble_line +=  $lines;
                   8471: 	        $total_lines +=  $lines;
                   8472: 	    }
                   8473:         }
                   8474:     }
1.552     raeburn  8475:     &Apache::lonnet::delenv('scantron.');
1.542     raeburn  8476: 
                   8477:     &save_bubble_lines();
                   8478:     $env{'form.scantron_maxbubble'} =
                   8479: 	$total_lines;
                   8480:     return $env{'form.scantron_maxbubble'};
                   8481: }
1.523     raeburn  8482: 
1.596.2.12.2.  (raeburn 8483:): sub bubblesheet_bubbles_per_row {
                   8484:):     my ($scantron_config) = @_;
                   8485:):     my $bubbles_per_row;
                   8486:):     if (ref($scantron_config) eq 'HASH') {
                   8487:):         $bubbles_per_row = $scantron_config->{'BubblesPerRow'};
                   8488:):     }
                   8489:):     if ((!$bubbles_per_row) || ($bubbles_per_row < 1)) {
                   8490:):         $bubbles_per_row = 10;
                   8491:):     }
                   8492:):     return $bubbles_per_row;
                   8493:): }
                   8494:): 
1.157     albertel 8495: sub scantron_validate_missingbubbles {
                   8496:     my ($r,$currentphase) = @_;
                   8497:     #get student info
                   8498:     my $classlist=&Apache::loncoursedata::get_classlist();
                   8499:     my %idmap=&username_to_idmap($classlist);
1.596.2.12.2.  6(raebur 8500:3):     my (undef,undef,$sequence)=
                   8501:3):         &Apache::lonnet::decode_symb($env{'form.selectpage'});
1.157     albertel 8502: 
                   8503:     #get scantron line setup
1.257     albertel 8504:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
1.157     albertel 8505:     my ($scanlines,$scan_data)=&scantron_getfile();
1.596.2.12.2.  6(raebur 8506:3): 
                   8507:3):     my $navmap = Apache::lonnavmaps::navmap->new();
                   8508:3):     unless (ref($navmap)) {
                   8509:3):         $r->print(&navmap_errormsg());
                   8510:3):         return(1,$currentphase);
                   8511:3):     }
                   8512:3): 
                   8513:3):     my $map=$navmap->getResourceByUrl($sequence);
                   8514:3):     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
                   8515:3):     my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
                   8516:3):         %grader_randomlists_by_symb,%orderedforcode,%respnumlookup,%startline);
                   8517:3):     my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
                   8518:3): 
1.582     raeburn  8519:     my $nav_error;
1.596.2.12.2.  6(raebur 8520:3):     if (ref($map)) {
                   8521:3):         $randomorder = $map->randomorder();
                   8522:3):         $randompick = $map->randompick();
          7(raebur 8523:3):         if ($randomorder || $randompick) {
                   8524:3):             $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
                   8525:3):             if ($nav_error) {
                   8526:3):                 $r->print(&navmap_errormsg());
                   8527:3):                 return(1,$currentphase);
                   8528:3):             }
                   8529:3):             &graders_resources_pass(\@resources,\%grader_partids_by_symb,
                   8530:3):                                     \%grader_randomlists_by_symb,$bubbles_per_row);
                   8531:3):         }
          6(raebur 8532:3):     } else {
                   8533:3):         $r->print(&navmap_errormsg());
          7(raebur 8534:3):         return(1,$currentphase);
          6(raebur 8535:3):     }
                   8536:3): 
                   8537:3): 
          (raeburn 8538:):     my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
1.582     raeburn  8539:     if ($nav_error) {
1.596.2.12.2.  6(raebur 8540:3):         $r->print(&navmap_errormsg());
1.582     raeburn  8541:         return(1,$currentphase);
                   8542:     }
1.596.2.12.2.  6(raebur 8543:3): 
1.157     albertel 8544:     if (!$max_bubble) { $max_bubble=2**31; }
                   8545:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
1.200     albertel 8546: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
1.157     albertel 8547: 	if ($line=~/^[\s\cz]*$/) { next; }
1.596.2.12.2.  6(raebur 8548:3):         my $scan_record =
                   8549:3):             &scantron_parse_scanline($line,$i,\%scantron_config,$scan_data,undef,\%idmap,
                   8550:3):                                      $randomorder,$randompick,$sequence,\@master_seq,
                   8551:3):                                      \%symb_to_resource,\%grader_partids_by_symb,
                   8552:3):                                      \%orderedforcode,\%respnumlookup,\%startline);
1.157     albertel 8553: 	if (!defined($$scan_record{'scantron.missingerror'})) { next; }
                   8554: 	my @to_correct;
1.470     foxr     8555: 	
                   8556: 	# Probably here's where the error is...
                   8557: 
1.157     albertel 8558: 	foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
1.505     raeburn  8559:             my $lastbubble;
                   8560:             if ($missing =~ /^(\d+)\.(\d+)$/) {
1.596.2.12.2.  6(raebur 8561:3):                 my $question = $1;
                   8562:3):                 my $subquestion = $2;
                   8563:3):                 my ($first,$responsenum);
                   8564:3):                 if ($randomorder || $randompick) {
                   8565:3):                     $responsenum = $respnumlookup{$question-1};
                   8566:3):                     $first = $startline{$question-1};
                   8567:3):                 } else {
                   8568:3):                     $responsenum = $question-1;
                   8569:3):                     $first = $first_bubble_line{$responsenum};
                   8570:3):                 }
                   8571:3):                 if (!defined($first)) { next; }
          7(raebur 8572:3):                 my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
          6(raebur 8573:3):                 my $subcount = 1;
                   8574:3):                 while ($subcount<$subquestion) {
                   8575:3):                     $first += $subans[$subcount-1];
                   8576:3):                     $subcount ++;
                   8577:3):                 }
                   8578:3):                 my $count = $subans[$subquestion-1];
                   8579:3):                 $lastbubble = $first + $count;
1.505     raeburn  8580:             } else {
1.596.2.12.2.  6(raebur 8581:3):                 my ($first,$responsenum);
                   8582:3):                 if ($randomorder || $randompick) {
                   8583:3):                     $responsenum = $respnumlookup{$missing-1};
                   8584:3):                     $first = $startline{$missing-1};
                   8585:3):                 } else {
                   8586:3):                     $responsenum = $missing-1;
                   8587:3):                     $first = $first_bubble_line{$responsenum};
                   8588:3):                 }
                   8589:3):                 if (!defined($first)) { next; }
                   8590:3):                 $lastbubble = $first + $bubble_lines_per_response{$responsenum};
1.505     raeburn  8591:             }
                   8592:             if ($lastbubble > $max_bubble) { next; }
1.157     albertel 8593: 	    push(@to_correct,$missing);
                   8594: 	}
                   8595: 	if (@to_correct) {
                   8596: 	    &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
1.596.2.12.2.  6(raebur 8597:3): 				     $line,'missingbubble',\@to_correct,
                   8598:3):                                      $randomorder,$randompick,\%respnumlookup,
                   8599:3):                                      \%startline);
1.157     albertel 8600: 	    return (1,$currentphase);
                   8601: 	}
                   8602: 
                   8603:     }
                   8604:     return (0,$currentphase+1);
                   8605: }
                   8606: 
1.596.2.12.2.  (raeburn 8607:): sub hand_bubble_option {
                   8608:):     my (undef, undef, $sequence) =
                   8609:):         &Apache::lonnet::decode_symb($env{'form.selectpage'});
                   8610:):     return if ($sequence eq '');
                   8611:):     my $navmap = Apache::lonnavmaps::navmap->new();
                   8612:):     unless (ref($navmap)) {
                   8613:):         return;
                   8614:):     }
                   8615:):     my $needs_hand_bubbles;
                   8616:):     my $map=$navmap->getResourceByUrl($sequence);
                   8617:):     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
                   8618:):     foreach my $res (@resources) {
                   8619:):         if (ref($res)) {
                   8620:):             if ($res->is_problem()) {
                   8621:):                 my $partlist = $res->parts();
                   8622:):                 foreach my $part (@{ $partlist }) {
                   8623:):                     my @types = $res->responseType($part);
                   8624:):                     if (grep(/^(chem|essay|image|formula|math|string|functionplot)$/,@types)) {
                   8625:):                         $needs_hand_bubbles = 1;
                   8626:):                         last;
                   8627:):                     }
                   8628:):                 }
                   8629:):             }
                   8630:):         }
                   8631:):     }
                   8632:):     if ($needs_hand_bubbles) {
                   8633:):         my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
                   8634:):         my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
                   8635:):         return &mt('The sequence to be graded contains response types which are handgraded.').'<p>'.
                   8636:):                &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 />').
                   8637:):                '<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;'.
          8(raebur 8638:4):                '<label><input type="radio" name="scantron_lastbubblepoints" value="0" />'.&mt('0 points').'</label></p>';
          (raeburn 8639:):     }
                   8640:):     return;
                   8641:): }
1.423     albertel 8642: 
1.82      albertel 8643: sub scantron_process_students {
1.75      albertel 8644:     my ($r) = @_;
1.513     foxr     8645: 
1.257     albertel 8646:     my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
1.324     albertel 8647:     my ($symb)=&get_symb($r);
1.513     foxr     8648:     if (!$symb) {
                   8649: 	return '';
                   8650:     }
1.324     albertel 8651:     my $default_form_data=&defaultFormData($symb);
1.82      albertel 8652: 
1.257     albertel 8653:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
1.596.2.12.2.  6(raebur 8654:3):     my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
1.157     albertel 8655:     my ($scanlines,$scan_data)=&scantron_getfile();
1.82      albertel 8656:     my $classlist=&Apache::loncoursedata::get_classlist();
                   8657:     my %idmap=&username_to_idmap($classlist);
1.132     bowersj2 8658:     my $navmap=Apache::lonnavmaps::navmap->new();
1.582     raeburn  8659:     unless (ref($navmap)) {
                   8660:         $r->print(&navmap_errormsg());
                   8661:         return '';
1.596.2.12.2.  6(raebur 8662:3):     }
1.83      albertel 8663:     my $map=$navmap->getResourceByUrl($sequence);
1.596.2.12.2.  6(raebur 8664:3):     my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
                   8665:3):         %grader_randomlists_by_symb);
          1(raebur 8666:2):     if (ref($map)) {
                   8667:2):         $randomorder = $map->randomorder();
          6(raebur 8668:3):         $randompick = $map->randompick();
                   8669:3):     } else {
                   8670:3):         $r->print(&navmap_errormsg());
                   8671:3):         return '';
          1(raebur 8672:2):     }
          6(raebur 8673:3):     my $nav_error;
1.83      albertel 8674:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
1.596.2.12.2.  6(raebur 8675:3):     if ($randomorder || $randompick) {
                   8676:3):         $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
                   8677:3):         if ($nav_error) {
                   8678:3):             $r->print(&navmap_errormsg());
                   8679:3):             return '';
1.586     raeburn  8680:         }
                   8681:     }
1.596.2.12.2.  6(raebur 8682:3):     &graders_resources_pass(\@resources,\%grader_partids_by_symb,
                   8683:3):                             \%grader_randomlists_by_symb,$bubbles_per_row);
1.557     raeburn  8684: 
1.554     raeburn  8685:     my ($uname,$udom);
1.82      albertel 8686:     my $result= <<SCANTRONFORM;
1.81      albertel 8687: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
                   8688:   <input type="hidden" name="command" value="scantron_configphase" />
                   8689:   $default_form_data
                   8690: SCANTRONFORM
1.82      albertel 8691:     $r->print($result);
                   8692: 
                   8693:     my @delayqueue;
1.542     raeburn  8694:     my (%completedstudents,%scandata);
1.140     albertel 8695:     
1.520     www      8696:     my $lock=&Apache::lonnet::set_lock(&mt('Grading bubblesheet exam'));
1.200     albertel 8697:     my $count=&get_todo_count($scanlines,$scan_data);
1.596.2.12.2.  (raeburn 8698:):     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count);
1.140     albertel 8699:     &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
                   8700: 					  'Processing first student');
1.542     raeburn  8701:     $r->print('<br />');
1.140     albertel 8702:     my $start=&Time::HiRes::time();
1.158     albertel 8703:     my $i=-1;
1.542     raeburn  8704:     my $started;
1.447     foxr     8705: 
1.596.2.12.2.  (raeburn 8706:):     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse.
1.582     raeburn  8707:     if ($nav_error) {
                   8708:         $r->print(&navmap_errormsg());
                   8709:         return '';
                   8710:     }
                   8711: 
1.513     foxr     8712:     # If an ssi failed in scantron_get_maxbubble, put an error message out to
                   8713:     # the user and return.
                   8714: 
                   8715:     if ($ssi_error) {
                   8716: 	$r->print("</form>");
                   8717: 	&ssi_print_error($r);
                   8718: 	$r->print(&show_grading_menu_form($symb));
1.520     www      8719:         &Apache::lonnet::remove_lock($lock);
1.513     foxr     8720: 	return '';		# Dunno why the other returns return '' rather than just returning.
                   8721:     }
1.447     foxr     8722: 
1.542     raeburn  8723:     my %lettdig = &letter_to_digits();
                   8724:     my $numletts = scalar(keys(%lettdig));
1.596.2.12.2.  6(raebur 8725:3):     my %orderedforcode;
1.542     raeburn  8726: 
1.157     albertel 8727:     while ($i<$scanlines->{'count'}) {
                   8728:  	($uname,$udom)=('','');
                   8729:  	$i++;
1.200     albertel 8730:  	my $line=&scantron_get_line($scanlines,$scan_data,$i);
1.157     albertel 8731:  	if ($line=~/^[\s\cz]*$/) { next; }
1.200     albertel 8732: 	if ($started) {
                   8733: 	    &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
                   8734: 						     'last student');
                   8735: 	}
                   8736: 	$started=1;
1.596.2.12.2.  6(raebur 8737:3):         my %respnumlookup = ();
                   8738:3):         my %startline = ();
                   8739:3):         my $total;
1.157     albertel 8740:  	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
1.596.2.12.2.  6(raebur 8741:3):  						 $scan_data,undef,\%idmap,$randomorder,
                   8742:3):                                                  $randompick,$sequence,\@master_seq,
                   8743:3):                                                  \%symb_to_resource,\%grader_partids_by_symb,
                   8744:3):                                                  \%orderedforcode,\%respnumlookup,\%startline,
                   8745:3):                                                  \$total);
1.157     albertel 8746:  	unless ($uname=&scantron_find_student($scan_record,$scan_data,
                   8747:  					      \%idmap,$i)) {
                   8748:   	    &scantron_add_delay(\@delayqueue,$line,
                   8749:  				'Unable to find a student that matches',1);
                   8750:  	    next;
                   8751:   	}
                   8752:  	if (exists $completedstudents{$uname}) {
                   8753:  	    &scantron_add_delay(\@delayqueue,$line,
                   8754:  				'Student '.$uname.' has multiple sheets',2);
                   8755:  	    next;
                   8756:  	}
1.596.2.12.2.  1(raebur 8757:2):         my $usec = $classlist->{$uname}->[&Apache::loncoursedata::CL_SECTION];
                   8758:2):         my $user = $uname.':'.$usec;
1.157     albertel 8759:   	($uname,$udom)=split(/:/,$uname);
1.330     albertel 8760: 
1.596.2.12.2.  1(raebur 8761:2):         my $scancode;
                   8762:2):         if ((exists($scan_record->{'scantron.CODE'})) &&
                   8763:2):             (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
                   8764:2):             $scancode = $scan_record->{'scantron.CODE'};
                   8765:2):         } else {
                   8766:2):             $scancode = '';
                   8767:2):         }
                   8768:2): 
                   8769:2):         my @mapresources = @resources;
          6(raebur 8770:3):         if ($randomorder || $randompick) {
          1(raebur 8771:2):             @mapresources =
          6(raebur 8772:3):                 &users_order($user,$scancode,$sequence,\@master_seq,\%symb_to_resource,
                   8773:3):                              \%orderedforcode);
          1(raebur 8774:2):         }
1.586     raeburn  8775:         my (%partids_by_symb,$res_error);
1.596.2.12.2.  1(raebur 8776:2):         foreach my $resource (@mapresources) {
1.586     raeburn  8777:             my $ressymb;
                   8778:             if (ref($resource)) {
                   8779:                 $ressymb = $resource->symb();
                   8780:             } else {
                   8781:                 $res_error = 1;
                   8782:                 last;
                   8783:             }
1.557     raeburn  8784:             if ((exists($grader_randomlists_by_symb{$ressymb})) ||
                   8785:                 (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
                   8786:                 my ($analysis,$parts) =
1.596.2.12.2.  (raeburn 8787:):                     &scantron_partids_tograde($resource,$env{'request.course.id'},
                   8788:):                                               $uname,$udom,undef,$bubbles_per_row);
1.557     raeburn  8789:                 $partids_by_symb{$ressymb} = $parts;
                   8790:             } else {
                   8791:                 $partids_by_symb{$ressymb} = $grader_partids_by_symb{$ressymb};
                   8792:             }
1.554     raeburn  8793:         }
                   8794: 
1.586     raeburn  8795:         if ($res_error) {
                   8796:             &scantron_add_delay(\@delayqueue,$line,
                   8797:                                 'An error occurred while grading student '.$uname,2);
                   8798:             next;
                   8799:         }
                   8800: 
1.330     albertel 8801: 	&Apache::lonxml::clear_problem_counter();
1.514     raeburn  8802:   	&Apache::lonnet::appenv($scan_record);
1.376     albertel 8803: 
                   8804: 	if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
                   8805: 	    &scantron_putfile($scanlines,$scan_data);
                   8806: 	}
1.161     albertel 8807: 	
1.542     raeburn  8808:         if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
1.596.2.12.2.  1(raebur 8809:2):                                    \@mapresources,\%partids_by_symb,
          6(raebur 8810:3):                                    $bubbles_per_row,$randomorder,$randompick,
                   8811:3):                                    \%respnumlookup,\%startline) 
                   8812:3):             eq 'ssi_error') {
1.542     raeburn  8813:             $ssi_error = 0; # So end of handler error message does not trigger.
                   8814:             $r->print("</form>");
                   8815:             &ssi_print_error($r);
                   8816:             $r->print(&show_grading_menu_form($symb));
                   8817:             &Apache::lonnet::remove_lock($lock);
                   8818:             return '';      # Why return ''?  Beats me.
                   8819:         }
1.513     foxr     8820: 
1.596.2.12.2.  6(raebur 8821:3):         if (($scancode) && ($randomorder || $randompick)) {
                   8822:3):             my $parmresult =
                   8823:3):                 &Apache::lonparmset::storeparm_by_symb($symb,
                   8824:3):                                                        '0_examcode',2,$scancode,
                   8825:3):                                                        'string_examcode',$uname,
                   8826:3):                                                        $udom);
                   8827:3):         }
1.140     albertel 8828: 	$completedstudents{$uname}={'line'=>$line};
1.542     raeburn  8829:         if ($env{'form.verifyrecord'}) {
                   8830:             my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
1.596.2.12.2.  6(raebur 8831:3):             if ($randompick) {
                   8832:3):                 if ($total) {
                   8833:3):                     $lastpos = $total*$scantron_config{'Qlength'};
                   8834:3):                 }
                   8835:3):             }
                   8836:3): 
1.542     raeburn  8837:             my $studentdata = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
                   8838:             chomp($studentdata);
                   8839:             $studentdata =~ s/\r$//;
                   8840:             my $studentrecord = '';
                   8841:             my $counter = -1;
1.596.2.12.2.  1(raebur 8842:2):             foreach my $resource (@mapresources) {
1.554     raeburn  8843:                 my $ressymb = $resource->symb();
1.542     raeburn  8844:                 ($counter,my $recording) =
                   8845:                     &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
1.554     raeburn  8846:                                              $counter,$studentdata,$partids_by_symb{$ressymb},
1.596.2.12.2.  6(raebur 8847:3):                                              \%scantron_config,\%lettdig,$numletts,$randomorder,
                   8848:3):                                              $randompick,\%respnumlookup,\%startline);
1.542     raeburn  8849:                 $studentrecord .= $recording;
                   8850:             }
                   8851:             if ($studentrecord ne $studentdata) {
1.554     raeburn  8852:                 &Apache::lonxml::clear_problem_counter();
                   8853:                 if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
1.596.2.12.2.  1(raebur 8854:2):                                            \@mapresources,\%partids_by_symb,
          6(raebur 8855:3):                                            $bubbles_per_row,$randomorder,$randompick,
                   8856:3):                                            \%respnumlookup,\%startline)
                   8857:3):                     eq 'ssi_error') {
1.554     raeburn  8858:                     $ssi_error = 0; # So end of handler error message does not trigger.
                   8859:                     $r->print("</form>");
                   8860:                     &ssi_print_error($r);
                   8861:                     $r->print(&show_grading_menu_form($symb));
                   8862:                     &Apache::lonnet::remove_lock($lock);
                   8863:                     delete($completedstudents{$uname});
                   8864:                     return '';
                   8865:                 }
1.542     raeburn  8866:                 $counter = -1;
                   8867:                 $studentrecord = '';
1.596.2.12.2.  1(raebur 8868:2):                 foreach my $resource (@mapresources) {
1.554     raeburn  8869:                     my $ressymb = $resource->symb();
1.542     raeburn  8870:                     ($counter,my $recording) =
                   8871:                         &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
1.554     raeburn  8872:                                                  $counter,$studentdata,$partids_by_symb{$ressymb},
1.596.2.12.2.  6(raebur 8873:3):                                                  \%scantron_config,\%lettdig,$numletts,
                   8874:3):                                                  $randomorder,$randompick,\%respnumlookup,
                   8875:3):                                                  \%startline);
1.542     raeburn  8876:                     $studentrecord .= $recording;
                   8877:                 }
                   8878:                 if ($studentrecord ne $studentdata) {
1.596.2.6  raeburn  8879:                     $r->print('<p><span class="LC_warning">');
1.542     raeburn  8880:                     if ($scancode eq '') {
1.596.2.6  raeburn  8881:                         $r->print(&mt('Mismatch grading bubblesheet for user: [_1] with ID: [_2].',
1.542     raeburn  8882:                                   $uname.':'.$udom,$scan_record->{'scantron.ID'}));
                   8883:                     } else {
1.596.2.6  raeburn  8884:                         $r->print(&mt('Mismatch grading bubblesheet for user: [_1] with ID: [_2] and CODE: [_3].',
1.542     raeburn  8885:                                   $uname.':'.$udom,$scan_record->{'scantron.ID'},$scancode));
                   8886:                     }
                   8887:                     $r->print('</span><br />'.&Apache::loncommon::start_data_table()."\n".
                   8888:                               &Apache::loncommon::start_data_table_header_row()."\n".
                   8889:                               '<th>'.&mt('Source').'</th><th>'.&mt('Bubbled responses').'</th>'.
                   8890:                               &Apache::loncommon::end_data_table_header_row()."\n".
                   8891:                               &Apache::loncommon::start_data_table_row().
1.596.2.6  raeburn  8892:                               '<td>'.&mt('Bubblesheet').'</td>'.
1.596.2.12.2.  4(raebur 8893:3):                               '<td><span class="LC_nobreak" style="white-space: pre;"><tt>'.$studentdata.'</tt></span></td>'.
1.542     raeburn  8894:                               &Apache::loncommon::end_data_table_row().
                   8895:                               &Apache::loncommon::start_data_table_row().
1.596.2.6  raeburn  8896:                               '<td>'.&mt('Stored submissions').'</td>'.
1.596.2.12.2.  4(raebur 8897:3):                               '<td><span class="LC_nobreak" style="white-space: pre;"><tt>'.$studentrecord.'</tt></span></td>'."\n".
1.542     raeburn  8898:                               &Apache::loncommon::end_data_table_row().
                   8899:                               &Apache::loncommon::end_data_table().'</p>');
                   8900:                 } else {
                   8901:                     $r->print('<br /><span class="LC_warning">'.
                   8902:                              &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 />'.
                   8903:                              &mt("As a consequence, this user's submission history records two tries.").
                   8904:                                  '</span><br />');
                   8905:                 }
                   8906:             }
                   8907:         }
1.543     raeburn  8908:         if (&Apache::loncommon::connection_aborted($r)) { last; }
1.140     albertel 8909:     } continue {
1.330     albertel 8910: 	&Apache::lonxml::clear_problem_counter();
1.552     raeburn  8911: 	&Apache::lonnet::delenv('scantron.');
1.82      albertel 8912:     }
1.140     albertel 8913:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
1.520     www      8914:     &Apache::lonnet::remove_lock($lock);
1.172     albertel 8915: #    my $lasttime = &Time::HiRes::time()-$start;
                   8916: #    $r->print("<p>took $lasttime</p>");
1.140     albertel 8917: 
1.200     albertel 8918:     $r->print("</form>");
1.324     albertel 8919:     $r->print(&show_grading_menu_form($symb));
1.157     albertel 8920:     return '';
1.75      albertel 8921: }
1.157     albertel 8922: 
1.557     raeburn  8923: sub graders_resources_pass {
1.596.2.12.2.  (raeburn 8924:):     my ($resources,$grader_partids_by_symb,$grader_randomlists_by_symb,
                   8925:):         $bubbles_per_row) = @_;
1.557     raeburn  8926:     if ((ref($resources) eq 'ARRAY') && (ref($grader_partids_by_symb)) && 
                   8927:         (ref($grader_randomlists_by_symb) eq 'HASH')) {
                   8928:         foreach my $resource (@{$resources}) {
                   8929:             my $ressymb = $resource->symb();
                   8930:             my ($analysis,$parts) =
                   8931:                 &scantron_partids_tograde($resource,$env{'request.course.id'},
1.596.2.12.2.  (raeburn 8932:):                                           $env{'user.name'},$env{'user.domain'},
                   8933:):                                           1,$bubbles_per_row);
1.557     raeburn  8934:             $grader_partids_by_symb->{$ressymb} = $parts;
                   8935:             if (ref($analysis) eq 'HASH') {
                   8936:                 if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') {
                   8937:                     $grader_randomlists_by_symb->{$ressymb} =
                   8938:                         $analysis->{'parts_withrandomlist'};
                   8939:                 }
                   8940:             }
                   8941:         }
                   8942:     }
                   8943:     return;
                   8944: }
                   8945: 
1.596.2.12.2.  1(raebur 8946:2): =pod
                   8947:2): 
                   8948:2): =item users_order
                   8949:2): 
                   8950:2):   Returns array of resources in current map, ordered based on either CODE,
                   8951:2):   if this is a CODEd exam, or based on student's identity if this is a
                   8952:2):   "NAMEd" exam.
                   8953:2): 
          6(raebur 8954:3):   Should be used when randomorder and/or randompick applied when the 
                   8955:3):   corresponding exam was printed, prior to students completing bubblesheets 
                   8956:3):   for the version of the exam the student received.
          1(raebur 8957:2): 
                   8958:2): =cut
                   8959:2): 
                   8960:2): sub users_order  {
          6(raebur 8961:3):     my ($user,$scancode,$mapurl,$master_seq,$symb_to_resource,$orderedforcode) = @_;
          1(raebur 8962:2):     my @mapresources;
          6(raebur 8963:3):     unless ((ref($master_seq) eq 'ARRAY') && (ref($symb_to_resource) eq 'HASH')) {
          1(raebur 8964:2):         return @mapresources;
                   8965:2):     }
          6(raebur 8966:3):     if ($scancode) {
                   8967:3):         if ((ref($orderedforcode) eq 'HASH') && (ref($orderedforcode->{$scancode}) eq 'ARRAY')) {
                   8968:3):             @mapresources = @{$orderedforcode->{$scancode}};
                   8969:3):         } else {
                   8970:3):             $env{'form.CODE'} = $scancode;
                   8971:3):             my $actual_seq =
                   8972:3):                 &Apache::lonprintout::master_seq_to_person_seq($mapurl,
                   8973:3):                                                                $master_seq,
                   8974:3):                                                                $user,$scancode,1);
                   8975:3):             if (ref($actual_seq) eq 'ARRAY') {
                   8976:3):                 @mapresources = map { $symb_to_resource->{$_}; } @{$actual_seq};
                   8977:3):                 if (ref($orderedforcode) eq 'HASH') {
                   8978:3):                     if (@mapresources > 0) {
                   8979:3):                         $orderedforcode->{$scancode} = \@mapresources;
                   8980:3):                     }
                   8981:3):                 }
                   8982:3):             }
                   8983:3):             delete($env{'form.CODE'});
          1(raebur 8984:2):         }
                   8985:2):     } else {
                   8986:2):         my $actual_seq =
                   8987:2):             &Apache::lonprintout::master_seq_to_person_seq($mapurl,
                   8988:2):                                                            $master_seq,
          5(raebur 8989:3):                                                            $user,undef,1);
          1(raebur 8990:2):         if (ref($actual_seq) eq 'ARRAY') {
                   8991:2):             @mapresources =
                   8992:2):                 map { $symb_to_resource->{$_}; } @{$actual_seq};
                   8993:2):         }
          6(raebur 8994:3):     }
                   8995:3):     return @mapresources;
          1(raebur 8996:2): }
                   8997:2): 
1.542     raeburn  8998: sub grade_student_bubbles {
1.596.2.12.2.  6(raebur 8999:3):     my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts,$bubbles_per_row,
                   9000:3):         $randomorder,$randompick,$respnumlookup,$startline) = @_;
                   9001:3):     my $uselookup = 0;
                   9002:3):     if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH') &&
                   9003:3):         (ref($startline) eq 'HASH')) {
                   9004:3):         $uselookup = 1;
                   9005:3):     }
                   9006:3): 
1.554     raeburn  9007:     if (ref($resources) eq 'ARRAY') {
                   9008:         my $count = 0;
                   9009:         foreach my $resource (@{$resources}) {
                   9010:             my $ressymb = $resource->symb();
                   9011:             my %form = ('submitted'      => 'scantron',
                   9012:                         'grade_target'   => 'grade',
                   9013:                         'grade_username' => $uname,
                   9014:                         'grade_domain'   => $udom,
                   9015:                         'grade_courseid' => $env{'request.course.id'},
                   9016:                         'grade_symb'     => $ressymb,
                   9017:                         'CODE'           => $scancode
                   9018:                        );
1.596.2.12.2.  (raeburn 9019:):             if ($bubbles_per_row ne '') {
                   9020:):                 $form{'bubbles_per_row'} = $bubbles_per_row;
                   9021:):             }
                   9022:):             if ($env{'form.scantron_lastbubblepoints'} ne '') {
                   9023:):                 $form{'scantron_lastbubblepoints'} = $env{'form.scantron_lastbubblepoints'};
                   9024:):             }
1.554     raeburn  9025:             if (ref($parts) eq 'HASH') {
                   9026:                 if (ref($parts->{$ressymb}) eq 'ARRAY') {
                   9027:                     foreach my $part (@{$parts->{$ressymb}}) {
1.596.2.12.2.  6(raebur 9028:3):                         if ($uselookup) {
                   9029:3):                             $form{'scantron_questnum_start.'.$part} = $startline->{$count} + 1;
                   9030:3):                         } else {
                   9031:3):                             $form{'scantron_questnum_start.'.$part} =
                   9032:3):                                 1+$env{'form.scantron.first_bubble_line.'.$count};
                   9033:3):                         }
1.554     raeburn  9034:                         $count++;
                   9035:                     }
                   9036:                 }
                   9037:             }
                   9038:             my $result=&ssi_with_retries($resource->src(),$ssi_retries,%form);
                   9039:             return 'ssi_error' if ($ssi_error);
                   9040:             last if (&Apache::loncommon::connection_aborted($r));
                   9041:         }
1.542     raeburn  9042:     }
                   9043:     return;
                   9044: }
                   9045: 
1.157     albertel 9046: sub scantron_upload_scantron_data {
                   9047:     my ($r)=@_;
1.565     raeburn  9048:     my $dom = $env{'request.role.domain'};
                   9049:     my $domdesc = &Apache::lonnet::domain($dom,'description');
                   9050:     $r->print(&Apache::loncommon::coursebrowser_javascript($dom));
1.157     albertel 9051:     my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
1.181     albertel 9052: 							  'domainid',
1.565     raeburn  9053: 							  'coursename',$dom);
                   9054:     my $syllabuslink = '<a href="javascript:ToSyllabus();">'.&mt('Syllabus').'</a>'.
1.596.2.12.2.  (raeburn 9055:):                        ('&nbsp'x2).&mt('(shows course personnel)');
                   9056:):     my ($symb) = &get_symb($r,1);
                   9057:):     my $default_form_data=&defaultFormData($symb);
1.579     raeburn  9058:     my $nofile_alert = &mt('Please use the browse button to select a file from your local directory.');
1.596.2.12.2.  7(raebur 9059:6):     &js_escape(\$nofile_alert);
1.579     raeburn  9060:     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.");
1.596.2.12.2.  6(raebur 9061:6):     &js_escape(\$nocourseid_alert);
1.492     albertel 9062:     $r->print('
1.157     albertel 9063: <script type="text/javascript" language="javascript">
                   9064:     function checkUpload(formname) {
                   9065: 	if (formname.upfile.value == "") {
1.579     raeburn  9066: 	    alert("'.$nofile_alert.'");
1.157     albertel 9067: 	    return false;
                   9068: 	}
1.565     raeburn  9069:         if (formname.courseid.value == "") {
1.579     raeburn  9070:             alert("'.$nocourseid_alert.'");
1.565     raeburn  9071:             return false;
                   9072:         }
1.157     albertel 9073: 	formname.submit();
                   9074:     }
1.565     raeburn  9075: 
                   9076:     function ToSyllabus() {
                   9077:         var cdom = '."'$dom'".';
                   9078:         var cnum = document.rules.courseid.value;
                   9079:         if (cdom == "" || cdom == null) {
                   9080:             return;
                   9081:         }
                   9082:         if (cnum == "" || cnum == null) {
                   9083:            return;
                   9084:         }
                   9085:         syllwin=window.open("/public/"+cdom+"/"+cnum+"/syllabus","LONCAPASyllabus",
                   9086:                             "height=350,width=350,scrollbars=yes,menubar=no");
                   9087:         return;
                   9088:     }
                   9089: 
1.157     albertel 9090: </script>
                   9091: 
1.596.2.4  raeburn  9092: <h3>'.&mt('Send bubblesheet data to a course').'</h3>
1.566     raeburn  9093: 
1.492     albertel 9094: <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
1.565     raeburn  9095: '.$default_form_data.
                   9096:   &Apache::lonhtmlcommon::start_pick_box().
                   9097:   &Apache::lonhtmlcommon::row_title(&mt('Course ID')).
                   9098:   '<input name="courseid" type="text" size="30" />'.$select_link.
                   9099:   &Apache::lonhtmlcommon::row_closure().
                   9100:   &Apache::lonhtmlcommon::row_title(&mt('Course Name')).
                   9101:   '<input name="coursename" type="text" size="30" />'.$syllabuslink.
                   9102:   &Apache::lonhtmlcommon::row_closure().
                   9103:   &Apache::lonhtmlcommon::row_title(&mt('Domain')).
                   9104:   '<input name="domainid" type="hidden" />'.$domdesc.
                   9105:   &Apache::lonhtmlcommon::row_closure().
                   9106:   &Apache::lonhtmlcommon::row_title(&mt('File to upload')).
                   9107:   '<input type="file" name="upfile" size="50" />'.
                   9108:   &Apache::lonhtmlcommon::row_closure(1).
                   9109:   &Apache::lonhtmlcommon::end_pick_box().'<br />
                   9110: 
1.492     albertel 9111: <input name="command" value="scantronupload_save" type="hidden" />
1.589     bisitz   9112: <input type="button" onclick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
1.157     albertel 9113: </form>
1.492     albertel 9114: ');
1.157     albertel 9115:     return '';
                   9116: }
                   9117: 
1.423     albertel 9118: 
1.157     albertel 9119: sub scantron_upload_scantron_data_save {
                   9120:     my($r)=@_;
1.324     albertel 9121:     my ($symb)=&get_symb($r,1);
1.182     albertel 9122:     my $doanotherupload=
                   9123: 	'<br /><form action="/adm/grades" method="post">'."\n".
                   9124: 	'<input type="hidden" name="command" value="scantronupload" />'."\n".
1.492     albertel 9125: 	'<input type="submit" name="submit" value="'.&mt('Do Another Upload').'" />'."\n".
1.182     albertel 9126: 	'</form>'."\n";
1.257     albertel 9127:     if (!&Apache::lonnet::allowed('usc',$env{'form.domainid'}) &&
1.162     albertel 9128: 	!&Apache::lonnet::allowed('usc',
1.257     albertel 9129: 			    $env{'form.domainid'}.'_'.$env{'form.courseid'})) {
1.575     www      9130: 	$r->print(&mt("You are not allowed to upload bubblesheet data to the requested course.")."<br />");
1.182     albertel 9131: 	if ($symb) {
1.324     albertel 9132: 	    $r->print(&show_grading_menu_form($symb));
1.182     albertel 9133: 	} else {
                   9134: 	    $r->print($doanotherupload);
                   9135: 	}
1.162     albertel 9136: 	return '';
                   9137:     }
1.257     albertel 9138:     my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
1.568     raeburn  9139:     my $uploadedfile;
1.596.2.12.2.  5(raebur 9140:3):     $r->print('<p>'.&mt("Uploading file to [_1]",$coursedata{'description'}).'</p>');
1.257     albertel 9141:     if (length($env{'form.upfile'}) < 2) {
1.596.2.12.2.  5(raebur 9142:3):         $r->print(
                   9143:3):             &Apache::lonhtmlcommon::confirm_success(
                   9144:3):                 &mt('The file: [_1] you attempted to upload contained no information. Please check that you entered the correct filename.',
                   9145:3):                         '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'),1));
1.183     albertel 9146:     } else {
1.568     raeburn  9147:         my $result = 
                   9148:             &Apache::lonnet::userfileupload('upfile','','scantron','','','',
                   9149:                                             $env{'form.courseid'},$env{'form.domainid'});
                   9150: 	if ($result =~ m{^/uploaded/}) {
1.596.2.12.2.  5(raebur 9151:3):             $r->print(
                   9152:3):                 &Apache::lonhtmlcommon::confirm_success(&mt('Upload successful')).'<br />'.
                   9153:3):                 &mt('Uploaded [_1] bytes of data into location: [_2]',
                   9154:3):                         (length($env{'form.upfile'})-1),
                   9155:3):                         '<span class="LC_filename">'.$result.'</span>'));
1.568     raeburn  9156:             ($uploadedfile) = ($result =~ m{/([^/]+)$});
1.567     raeburn  9157:             $r->print(&validate_uploaded_scantron_file($env{'form.domainid'},
1.568     raeburn  9158:                                                        $env{'form.courseid'},$uploadedfile));
1.210     albertel 9159: 	} else {
1.596.2.12.2.  5(raebur 9160:3):             $r->print(
                   9161:3):                 &Apache::lonhtmlcommon::confirm_success(&mt('Upload failed'),1).'<br />'.
                   9162:3):                     &mt('An error ([_1]) occurred when attempting to upload the file: [_2]',
                   9163:3):                           $result,
1.568     raeburn  9164: 			  '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'));
1.183     albertel 9165: 	}
                   9166:     }
1.174     albertel 9167:     if ($symb) {
1.209     ng       9168: 	$r->print(&scantron_selectphase($r,$uploadedfile));
1.174     albertel 9169:     } else {
1.182     albertel 9170: 	$r->print($doanotherupload);
1.174     albertel 9171:     }
1.157     albertel 9172:     return '';
                   9173: }
                   9174: 
1.567     raeburn  9175: sub validate_uploaded_scantron_file {
                   9176:     my ($cdom,$cname,$fname) = @_;
                   9177:     my $scanlines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.$fname);
                   9178:     my @lines;
                   9179:     if ($scanlines ne '-1') {
                   9180:         @lines=split("\n",$scanlines,-1);
                   9181:     }
                   9182:     my $output;
                   9183:     if (@lines) {
                   9184:         my (%counts,$max_match_format);
1.596.2.12.2.  5(raebur 9185:3):         my ($found_match_count,$max_match_count,$max_match_pct) = (0,0,0);
1.567     raeburn  9186:         my $classlist = &Apache::loncoursedata::get_classlist($cdom,$cname);
                   9187:         my %idmap = &username_to_idmap($classlist);
                   9188:         foreach my $key (keys(%idmap)) {
                   9189:             my $lckey = lc($key);
                   9190:             $idmap{$lckey} = $idmap{$key};
                   9191:         }
                   9192:         my %unique_formats;
                   9193:         my @formatlines = &get_scantronformat_file();
                   9194:         foreach my $line (@formatlines) {
                   9195:             chomp($line);
                   9196:             my @config = split(/:/,$line);
                   9197:             my $idstart = $config[5];
                   9198:             my $idlength = $config[6];
                   9199:             if (($idstart ne '') && ($idlength > 0)) {
                   9200:                 if (ref($unique_formats{$idstart.':'.$idlength}) eq 'ARRAY') {
                   9201:                     push(@{$unique_formats{$idstart.':'.$idlength}},$config[0].':'.$config[1]); 
                   9202:                 } else {
                   9203:                     $unique_formats{$idstart.':'.$idlength} = [$config[0].':'.$config[1]];
                   9204:                 }
                   9205:             }
                   9206:         }
                   9207:         foreach my $key (keys(%unique_formats)) {
                   9208:             my ($idstart,$idlength) = split(':',$key);
                   9209:             %{$counts{$key}} = (
                   9210:                                'found'   => 0,
                   9211:                                'total'   => 0,
                   9212:                               );
                   9213:             foreach my $line (@lines) {
                   9214:                 next if ($line =~ /^#/);
                   9215:                 next if ($line =~ /^[\s\cz]*$/);
                   9216:                 my $id = substr($line,$idstart-1,$idlength);
                   9217:                 $id = lc($id);
                   9218:                 if (exists($idmap{$id})) {
                   9219:                     $counts{$key}{'found'} ++;
                   9220:                 }
                   9221:                 $counts{$key}{'total'} ++;
                   9222:             }
                   9223:             if ($counts{$key}{'total'}) {
                   9224:                 my $percent_match = (100*$counts{$key}{'found'})/($counts{$key}{'total'});
                   9225:                 if (($max_match_format eq '') || ($percent_match > $max_match_pct)) {
                   9226:                     $max_match_pct = $percent_match;
                   9227:                     $max_match_format = $key;
1.596.2.12.2.  5(raebur 9228:3):                     $found_match_count = $counts{$key}{'found'};
1.567     raeburn  9229:                     $max_match_count = $counts{$key}{'total'};
                   9230:                 }
                   9231:             }
                   9232:         }
                   9233:         if (ref($unique_formats{$max_match_format}) eq 'ARRAY') {
                   9234:             my $format_descs;
                   9235:             my $numwithformat = @{$unique_formats{$max_match_format}};
                   9236:             for (my $i=0; $i<$numwithformat; $i++) {
                   9237:                 my ($name,$desc) = split(':',$unique_formats{$max_match_format}[$i]);
                   9238:                 if ($i<$numwithformat-2) {
                   9239:                     $format_descs .= '"<i>'.$desc.'</i>", ';
                   9240:                 } elsif ($i==$numwithformat-2) {
                   9241:                     $format_descs .= '"<i>'.$desc.'</i>" '.&mt('and').' ';
                   9242:                 } elsif ($i==$numwithformat-1) {
                   9243:                     $format_descs .= '"<i>'.$desc.'</i>"';
                   9244:                 }
                   9245:             }
                   9246:             my $showpct = sprintf("%.0f",$max_match_pct).'%';
1.596.2.12.2.  5(raebur 9247:3):             $output .= '<br />';
                   9248:3):             if ($found_match_count == $max_match_count) {
                   9249:3):                 # 100% matching entries
                   9250:3):                 $output .= &Apache::lonhtmlcommon::confirm_success(
                   9251:3):                      &mt('Comparison of student IDs: [_1] matching ([quant,_2,entry,entries])',
                   9252:3):                             '<b>'.$showpct.'</b>',$found_match_count)).'<br />'.
                   9253:3):                 &mt('Comparison of student IDs in the uploaded file with'.
                   9254:3):                     ' the course roster found matches for [_1] of the [_2] entries'.
                   9255:3):                     ' in the file (for the format defined for [_3]).',
                   9256:3):                         '<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs);
                   9257:3):             } else {
                   9258:3):                 # Not all entries matching? -> Show warning and additional info
                   9259:3):                 $output .=
                   9260:3):                     &Apache::lonhtmlcommon::confirm_success(
                   9261:3):                         &mt('Comparison of student IDs: [_1] matching ([_2]/[quant,_3,entry,entries])',
                   9262:3):                                 '<b>'.$showpct.'</b>',$found_match_count,$max_match_count).'<br />'.
                   9263:3):                         &mt('Not all entries could be matched!'),1).'<br />'.
                   9264:3):                     &mt('Comparison of student IDs in the uploaded file with'.
                   9265:3):                         ' the course roster found matches for [_1] of the [_2] entries'.
                   9266:3):                         ' in the file (for the format defined for [_3]).',
                   9267:3):                             '<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs).
                   9268:3):                     '<p class="LC_info">'.
                   9269:3):                     &mt('A low percentage of matches results from one of the following:').
                   9270:3):                     '</p><ul>'.
                   9271:3):                     '<li>'.&mt('The file was uploaded to the wrong course.').'</li>'.
                   9272:3):                     '<li>'.&mt('The data is not in the format expected for the domain: [_1]',
                   9273:3):                                '<i>'.$cdom.'</i>').'</li>'.
                   9274:3):                     '<li>'.&mt('Students did not bubble their IDs, or mis-bubbled them').'</li>'.
                   9275:3):                     '<li>'.&mt('The course roster is not up to date.').'</li>'.
                   9276:3):                     '</ul>';
                   9277:3):             }
1.567     raeburn  9278:         }
                   9279:     } else {
1.596.2.12.2.  5(raebur 9280:3):         $output = '<p class="LC_warning">'.&mt('Uploaded file contained no data').'</p>';
1.567     raeburn  9281:     }
                   9282:     return $output;
                   9283: }
                   9284: 
1.202     albertel 9285: sub valid_file {
                   9286:     my ($requested_file)=@_;
                   9287:     foreach my $filename (sort(&scantron_filenames())) {
                   9288: 	if ($requested_file eq $filename) { return 1; }
                   9289:     }
                   9290:     return 0;
                   9291: }
                   9292: 
                   9293: sub scantron_download_scantron_data {
                   9294:     my ($r)=@_;
1.596.2.12.2.  (raeburn 9295:):     my ($symb) = &get_symb($r,1);
                   9296:):     my $default_form_data=&defaultFormData($symb);
1.257     albertel 9297:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
                   9298:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
                   9299:     my $file=$env{'form.scantron_selectfile'};
1.202     albertel 9300:     if (! &valid_file($file)) {
1.492     albertel 9301: 	$r->print('
1.202     albertel 9302: 	<p>
1.596.2.12.2.  3(raebur 9303:3): 	    '.&mt('The requested filename was invalid.').'
1.202     albertel 9304:         </p>
1.492     albertel 9305: ');
1.596.2.12.2.  (raeburn 9306:): 	$r->print(&show_grading_menu_form($symb));
1.202     albertel 9307: 	return;
                   9308:     }
                   9309:     my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;
                   9310:     my $corrected='/uploaded/'.$cdom.'/'.$cname.'/scantron_corrected_'.$file;
                   9311:     my $skipped='/uploaded/'.$cdom.'/'.$cname.'/scantron_skipped_'.$file;
                   9312:     &Apache::lonnet::allowuploaded('/adm/grades',$orig);
                   9313:     &Apache::lonnet::allowuploaded('/adm/grades',$corrected);
                   9314:     &Apache::lonnet::allowuploaded('/adm/grades',$skipped);
1.492     albertel 9315:     $r->print('
1.202     albertel 9316:     <p>
1.596.2.12.2.  8(raebur 9317:4): 	'.&mt('[_1]Original[_2] file as uploaded by bubblesheet scanning office.',
1.492     albertel 9318: 	      '<a href="'.$orig.'">','</a>').'
1.202     albertel 9319:     </p>
                   9320:     <p>
1.492     albertel 9321: 	'.&mt('[_1]Corrections[_2], a file of corrected records that were used in grading.',
                   9322: 	      '<a href="'.$corrected.'">','</a>').'
1.202     albertel 9323:     </p>
                   9324:     <p>
1.492     albertel 9325: 	'.&mt('[_1]Skipped[_2], a file of records that were skipped.',
                   9326: 	      '<a href="'.$skipped.'">','</a>').'
1.202     albertel 9327:     </p>
1.492     albertel 9328: ');
1.596.2.12.2.  (raeburn 9329:):     $r->print(&show_grading_menu_form($symb));
1.202     albertel 9330:     return '';
                   9331: }
1.157     albertel 9332: 
1.523     raeburn  9333: sub checkscantron_results {
                   9334:     my ($r) = @_;
                   9335:     my ($symb)=&get_symb($r);
                   9336:     if (!$symb) {return '';}
                   9337:     my $grading_menu_button=&show_grading_menu_form($symb);
                   9338:     my $cid = $env{'request.course.id'};
1.542     raeburn  9339:     my %lettdig = &letter_to_digits();
1.523     raeburn  9340:     my $numletts = scalar(keys(%lettdig));
                   9341:     my $cnum = $env{'course.'.$cid.'.num'};
                   9342:     my $cdom = $env{'course.'.$cid.'.domain'};
                   9343:     my (undef, undef, $sequence) = &Apache::lonnet::decode_symb($env{'form.selectpage'});
                   9344:     my %record;
                   9345:     my %scantron_config =
                   9346:         &Apache::grades::get_scantron_config($env{'form.scantron_format'});
1.596.2.12.2.  (raeburn 9347:):     my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
1.523     raeburn  9348:     my ($scanlines,$scan_data)=&Apache::grades::scantron_getfile();
                   9349:     my $classlist=&Apache::loncoursedata::get_classlist();
                   9350:     my %idmap=&Apache::grades::username_to_idmap($classlist);
                   9351:     my $navmap=Apache::lonnavmaps::navmap->new();
1.582     raeburn  9352:     unless (ref($navmap)) {
                   9353:         $r->print(&navmap_errormsg());
                   9354:         return '';
                   9355:     }
1.523     raeburn  9356:     my $map=$navmap->getResourceByUrl($sequence);
1.596.2.12.2.  6(raebur 9357:3):     my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
                   9358:3):         %grader_randomlists_by_symb,%orderedforcode);
          1(raebur 9359:2):     if (ref($map)) {
                   9360:2):         $randomorder=$map->randomorder();
          7(raebur 9361:3):         $randompick=$map->randompick();
          1(raebur 9362:2):     }
1.557     raeburn  9363:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
1.596.2.12.2.  6(raebur 9364:3):     my $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
                   9365:3):     if ($nav_error) {
                   9366:3):         $r->print(&navmap_errormsg());
                   9367:3):         return '';
          1(raebur 9368:2):     }
          (raeburn 9369:):     &graders_resources_pass(\@resources,\%grader_partids_by_symb,
                   9370:):                             \%grader_randomlists_by_symb,$bubbles_per_row);
1.554     raeburn  9371:     my ($uname,$udom);
1.523     raeburn  9372:     my (%scandata,%lastname,%bylast);
                   9373:     $r->print('
                   9374: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="checkscantron">'."\n");
                   9375: 
                   9376:     my @delayqueue;
                   9377:     my %completedstudents;
                   9378: 
1.596.2.12.2.  6(raebur 9379:3):     my $count=&get_todo_count($scanlines,$scan_data);
          (raeburn 9380:):     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count);
          6(raebur 9381:3):     my ($username,$domain,$started);
          (raeburn 9382:):     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse.
1.582     raeburn  9383:     if ($nav_error) {
                   9384:         $r->print(&navmap_errormsg());
                   9385:         return '';
                   9386:     }
1.523     raeburn  9387: 
                   9388:     &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,
                   9389:                                           'Processing first student');
                   9390:     my $start=&Time::HiRes::time();
                   9391:     my $i=-1;
                   9392: 
                   9393:     while ($i<$scanlines->{'count'}) {
                   9394:         ($username,$domain,$uname)=('','','');
                   9395:         $i++;
                   9396:         my $line=&Apache::grades::scantron_get_line($scanlines,$scan_data,$i);
                   9397:         if ($line=~/^[\s\cz]*$/) { next; }
                   9398:         if ($started) {
                   9399:             &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,
                   9400:                                                      'last student');
                   9401:         }
                   9402:         $started=1;
                   9403:         my $scan_record=
                   9404:             &Apache::grades::scantron_parse_scanline($line,$i,\%scantron_config,
                   9405:                                                      $scan_data);
1.596.2.12.2.  6(raebur 9406:3):         unless ($uname=&scantron_find_student($scan_record,$scan_data,
                   9407:3):                                               \%idmap,$i)) {
1.523     raeburn  9408:             &Apache::grades::scantron_add_delay(\@delayqueue,$line,
                   9409:                                 'Unable to find a student that matches',1);
                   9410:             next;
                   9411:         }
                   9412:         if (exists $completedstudents{$uname}) {
                   9413:             &Apache::grades::scantron_add_delay(\@delayqueue,$line,
                   9414:                                 'Student '.$uname.' has multiple sheets',2);
                   9415:             next;
                   9416:         }
                   9417:         my $pid = $scan_record->{'scantron.ID'};
                   9418:         $lastname{$pid} = $scan_record->{'scantron.LastName'};
                   9419:         push(@{$bylast{$lastname{$pid}}},$pid);
1.596.2.12.2.  1(raebur 9420:2):         my $usec = $classlist->{$uname}->[&Apache::loncoursedata::CL_SECTION];
                   9421:2):         my $user = $uname.':'.$usec;
1.523     raeburn  9422:         ($username,$domain)=split(/:/,$uname);
1.596.2.12.2.  1(raebur 9423:2): 
                   9424:2):         my $scancode;
                   9425:2):         if ((exists($scan_record->{'scantron.CODE'})) &&
                   9426:2):             (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
                   9427:2):             $scancode = $scan_record->{'scantron.CODE'};
                   9428:2):         } else {
                   9429:2):             $scancode = '';
                   9430:2):         }
                   9431:2): 
                   9432:2):         my @mapresources = @resources;
          6(raebur 9433:3):         my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
                   9434:3):         my %respnumlookup=();
                   9435:3):         my %startline=();
                   9436:3):         if ($randomorder || $randompick) {
          1(raebur 9437:2):             @mapresources =
          6(raebur 9438:3):                 &users_order($user,$scancode,$sequence,\@master_seq,\%symb_to_resource,
                   9439:3):                              \%orderedforcode);
                   9440:3):             my $total = &get_respnum_lookups($sequence,$scan_data,\%idmap,$line,
                   9441:3):                                              $scan_record,\@master_seq,\%symb_to_resource,
                   9442:3):                                              \%grader_partids_by_symb,\%orderedforcode,
                   9443:3):                                              \%respnumlookup,\%startline);
                   9444:3):             if ($randompick && $total) {
                   9445:3):                 $lastpos = $total*$scantron_config{'Qlength'};
                   9446:3):             }
          1(raebur 9447:2):         }
          6(raebur 9448:3):         $scandata{$pid} = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
                   9449:3):         chomp($scandata{$pid});
                   9450:3):         $scandata{$pid} =~ s/\r$//;
                   9451:3): 
1.523     raeburn  9452:         my $counter = -1;
1.596.2.12.2.  1(raebur 9453:2):         foreach my $resource (@mapresources) {
1.557     raeburn  9454:             my $parts;
1.554     raeburn  9455:             my $ressymb = $resource->symb();
1.557     raeburn  9456:             if ((exists($grader_randomlists_by_symb{$ressymb})) ||
                   9457:                 (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
                   9458:                 (my $analysis,$parts) =
1.596.2.12.2.  (raeburn 9459:):                     &scantron_partids_tograde($resource,$env{'request.course.id'},
                   9460:):                                               $username,$domain,undef,
                   9461:):                                               $bubbles_per_row);
1.557     raeburn  9462:             } else {
                   9463:                 $parts = $grader_partids_by_symb{$ressymb};
                   9464:             }
1.542     raeburn  9465:             ($counter,my $recording) =
                   9466:                 &verify_scantron_grading($resource,$domain,$username,$cid,$counter,
1.554     raeburn  9467:                                          $scandata{$pid},$parts,
1.596.2.12.2.  6(raebur 9468:3):                                          \%scantron_config,\%lettdig,$numletts,
                   9469:3):                                          $randomorder,$randompick,
                   9470:3):                                          \%respnumlookup,\%startline);
1.542     raeburn  9471:             $record{$pid} .= $recording;
1.523     raeburn  9472:         }
                   9473:     }
                   9474:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
                   9475:     $r->print('<br />');
                   9476:     my ($okstudents,$badstudents,$numstudents,$passed,$failed);
                   9477:     $passed = 0;
                   9478:     $failed = 0;
                   9479:     $numstudents = 0;
                   9480:     foreach my $last (sort(keys(%bylast))) {
                   9481:         if (ref($bylast{$last}) eq 'ARRAY') {
                   9482:             foreach my $pid (sort(@{$bylast{$last}})) {
                   9483:                 my $showscandata = $scandata{$pid};
                   9484:                 my $showrecord = $record{$pid};
                   9485:                 $showscandata =~ s/\s/&nbsp;/g;
                   9486:                 $showrecord =~ s/\s/&nbsp;/g;
                   9487:                 if ($scandata{$pid} eq $record{$pid}) {
                   9488:                     my $css_class = ($passed % 2)?'LC_odd_row':'LC_even_row';
                   9489:                     $okstudents .= '<tr class="'.$css_class.'">'.
1.581     www      9490: '<td>'.&mt('Bubblesheet').'</td><td>'.$showscandata.'</td><td rowspan="2">'.$last.'</td><td rowspan="2">'.$pid.'</td>'."\n".
1.523     raeburn  9491: '</tr>'."\n".
                   9492: '<tr class="'.$css_class.'">'."\n".
1.596.2.12.2.  8(raebur 9493:4): '<td>'.&mt('Submissions').'</td><td>'.$showrecord.'</td></tr>'."\n";
1.523     raeburn  9494:                     $passed ++;
                   9495:                 } else {
                   9496:                     my $css_class = ($failed % 2)?'LC_odd_row':'LC_even_row';
1.581     www      9497:                     $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".
1.523     raeburn  9498: '</tr>'."\n".
                   9499: '<tr class="'.$css_class.'">'."\n".
1.596.2.12.2.  8(raebur 9500:4): '<td>'.&mt('Submissions').'</td><td><span class="LC_nobreak">'.$record{$pid}.'</span></td>'."\n".
1.523     raeburn  9501: '</tr>'."\n";
                   9502:                     $failed ++;
                   9503:                 }
                   9504:                 $numstudents ++;
                   9505:             }
                   9506:         }
                   9507:     }
1.596.2.4  raeburn  9508:     $r->print('<p>'.
1.596.2.8  raeburn  9509:               &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).',
1.596.2.4  raeburn  9510:                   '<b>',
                   9511:                   $numstudents,
                   9512:                   '</b>',
                   9513:                   $env{'form.scantron_maxbubble'}).
                   9514:               '</p>'
                   9515:     );
1.596.2.12.2.  2(raebur 9516:2):     $r->print('<p>'
                   9517:2):              .&mt('Exact matches for [_1][quant,_2,student][_3].','<b>',$passed,'</b>')
                   9518:2):              .'<br />'
                   9519:2):              .&mt('Discrepancies detected for [_1][quant,_2,student][_3].','<b>',$failed,'</b>')
                   9520:2):              .'</p>');
1.523     raeburn  9521:     if ($passed) {
1.572     www      9522:         $r->print(&mt('Students with exact correspondence between bubblesheet data and submissions are as follows:').'<br /><br />');
1.523     raeburn  9523:         $r->print(&Apache::loncommon::start_data_table()."\n".
                   9524:                  &Apache::loncommon::start_data_table_header_row()."\n".
                   9525:                  '<th>'.&mt('Source').'</th><th>'.&mt('Bubble records').'</th><th>'.&mt('Name').'</th><th>'.&mt('ID').'</th>'.
                   9526:                  &Apache::loncommon::end_data_table_header_row()."\n".
                   9527:                  $okstudents."\n".
                   9528:                  &Apache::loncommon::end_data_table().'<br />');
                   9529:     }
                   9530:     if ($failed) {
1.572     www      9531:         $r->print(&mt('Students with differences between bubblesheet data and submissions are as follows:').'<br /><br />');
1.523     raeburn  9532:         $r->print(&Apache::loncommon::start_data_table()."\n".
                   9533:                  &Apache::loncommon::start_data_table_header_row()."\n".
                   9534:                  '<th>'.&mt('Source').'</th><th>'.&mt('Bubble records').'</th><th>'.&mt('Name').'</th><th>'.&mt('ID').'</th>'.
                   9535:                  &Apache::loncommon::end_data_table_header_row()."\n".
                   9536:                  $badstudents."\n".
                   9537:                  &Apache::loncommon::end_data_table()).'<br />'.
1.572     www      9538:                  &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.');  
1.523     raeburn  9539:     }
                   9540:     $r->print('</form><br />'.$grading_menu_button);
                   9541:     return;
                   9542: }
                   9543: 
1.542     raeburn  9544: sub verify_scantron_grading {
1.554     raeburn  9545:     my ($resource,$domain,$username,$cid,$counter,$scandata,$partids,
1.596.2.12.2.  6(raebur 9546:3):         $scantron_config,$lettdig,$numletts,$randomorder,$randompick,
                   9547:3):         $respnumlookup,$startline) = @_;
1.542     raeburn  9548:     my ($record,%expected,%startpos);
                   9549:     return ($counter,$record) if (!ref($resource));
                   9550:     return ($counter,$record) if (!$resource->is_problem());
                   9551:     my $symb = $resource->symb();
1.554     raeburn  9552:     return ($counter,$record) if (ref($partids) ne 'ARRAY');
                   9553:     foreach my $part_id (@{$partids}) {
1.542     raeburn  9554:         $counter ++;
                   9555:         $expected{$part_id} = 0;
1.596.2.12.2.  6(raebur 9556:3):         my $respnum = $counter;
                   9557:3):         if ($randomorder || $randompick) {
                   9558:3):             $respnum = $respnumlookup->{$counter};
                   9559:3):             $startpos{$part_id} = $startline->{$counter} + 1;
                   9560:3):         } else {
                   9561:3):             $startpos{$part_id} = $env{"form.scantron.first_bubble_line.$counter"};
                   9562:3):         }
                   9563:3):         if ($env{"form.scantron.sub_bubblelines.$respnum"}) {
                   9564:3):             my @sub_lines = split(/,/,$env{"form.scantron.sub_bubblelines.$respnum"});
1.542     raeburn  9565:             foreach my $item (@sub_lines) {
                   9566:                 $expected{$part_id} += $item;
                   9567:             }
                   9568:         } else {
1.596.2.12.2.  6(raebur 9569:3):             $expected{$part_id} = $env{"form.scantron.bubblelines.$respnum"};
1.542     raeburn  9570:         }
                   9571:     }
                   9572:     if ($symb) {
                   9573:         my %recorded;
                   9574:         my (%returnhash) = &Apache::lonnet::restore($symb,$cid,$domain,$username);
                   9575:         if ($returnhash{'version'}) {
                   9576:             my %lasthash=();
                   9577:             my $version;
                   9578:             for ($version=1;$version<=$returnhash{'version'};$version++) {
                   9579:                 foreach my $key (sort(split(/\:/,$returnhash{$version.':keys'}))) {
                   9580:                     $lasthash{$key}=$returnhash{$version.':'.$key};
                   9581:                 }
                   9582:             }
                   9583:             foreach my $key (keys(%lasthash)) {
                   9584:                 if ($key =~ /\.scantron$/) {
                   9585:                     my $value = &unescape($lasthash{$key});
                   9586:                     my ($part_id) = ($key =~ /^resource\.(.+)\.scantron$/);
                   9587:                     if ($value eq '') {
                   9588:                         for (my $i=0; $i<$expected{$part_id}; $i++) {
                   9589:                             for (my $j=0; $j<$scantron_config->{'length'}; $j++) {
                   9590:                                 $recorded{$part_id} .= $scantron_config->{'Qoff'};
                   9591:                             }
                   9592:                         }
                   9593:                     } else {
                   9594:                         my @tocheck;
                   9595:                         my @items = split(//,$value);
                   9596:                         if (($scantron_config->{'Qon'} eq 'letter') ||
                   9597:                             ($scantron_config->{'Qon'} eq 'number')) {
                   9598:                             if (@items < $expected{$part_id}) {
                   9599:                                 my $fragment = substr($scandata,$startpos{$part_id},$expected{$part_id});
                   9600:                                 my @singles = split(//,$fragment);
                   9601:                                 foreach my $pos (@singles) {
                   9602:                                     if ($pos eq ' ') {
                   9603:                                         push(@tocheck,$pos);
                   9604:                                     } else {
                   9605:                                         my $next = shift(@items);
                   9606:                                         push(@tocheck,$next);
                   9607:                                     }
                   9608:                                 }
                   9609:                             } else {
                   9610:                                 @tocheck = @items;
                   9611:                             }
                   9612:                             foreach my $letter (@tocheck) {
                   9613:                                 if ($scantron_config->{'Qon'} eq 'letter') {
                   9614:                                     if ($letter !~ /^[A-J]$/) {
                   9615:                                         $letter = $scantron_config->{'Qoff'};
                   9616:                                     }
                   9617:                                     $recorded{$part_id} .= $letter;
                   9618:                                 } elsif ($scantron_config->{'Qon'} eq 'number') {
                   9619:                                     my $digit;
                   9620:                                     if ($letter !~ /^[A-J]$/) {
                   9621:                                         $digit = $scantron_config->{'Qoff'};
                   9622:                                     } else {
                   9623:                                         $digit = $lettdig->{$letter};
                   9624:                                     }
                   9625:                                     $recorded{$part_id} .= $digit;
                   9626:                                 }
                   9627:                             }
                   9628:                         } else {
                   9629:                             @tocheck = @items;
                   9630:                             for (my $i=0; $i<$expected{$part_id}; $i++) {
                   9631:                                 my $curr_sub = shift(@tocheck);
                   9632:                                 my $digit;
                   9633:                                 if ($curr_sub =~ /^[A-J]$/) {
                   9634:                                     $digit = $lettdig->{$curr_sub}-1;
                   9635:                                 }
                   9636:                                 if ($curr_sub eq 'J') {
                   9637:                                     $digit += scalar($numletts);
                   9638:                                 }
                   9639:                                 for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) {
                   9640:                                     if ($j == $digit) {
                   9641:                                         $recorded{$part_id} .= $scantron_config->{'Qon'};
                   9642:                                     } else {
                   9643:                                         $recorded{$part_id} .= $scantron_config->{'Qoff'};
                   9644:                                     }
                   9645:                                 }
                   9646:                             }
                   9647:                         }
                   9648:                     }
                   9649:                 }
                   9650:             }
                   9651:         }
1.554     raeburn  9652:         foreach my $part_id (@{$partids}) {
1.542     raeburn  9653:             if ($recorded{$part_id} eq '') {
                   9654:                 for (my $i=0; $i<$expected{$part_id}; $i++) {
                   9655:                     for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) {
                   9656:                         $recorded{$part_id} .= $scantron_config->{'Qoff'};
                   9657:                     }
                   9658:                 }
                   9659:             }
                   9660:             $record .= $recorded{$part_id};
                   9661:         }
                   9662:     }
                   9663:     return ($counter,$record);
                   9664: }
                   9665: 
1.596.2.12.2.  6(raebur 9666:3): sub letter_to_digits {
1.542     raeburn  9667:     my %lettdig = (
                   9668:                     A => 1,
                   9669:                     B => 2,
                   9670:                     C => 3,
                   9671:                     D => 4,
                   9672:                     E => 5,
                   9673:                     F => 6,
                   9674:                     G => 7,
                   9675:                     H => 8,
                   9676:                     I => 9,
                   9677:                     J => 0,
                   9678:                   );
                   9679:     return %lettdig;
                   9680: }
                   9681: 
1.423     albertel 9682: 
1.75      albertel 9683: #-------- end of section for handling grading scantron forms -------
                   9684: #
                   9685: #-------------------------------------------------------------------
                   9686: 
1.72      ng       9687: #-------------------------- Menu interface -------------------------
                   9688: #
                   9689: #--- Show a Grading Menu button - Calls the next routine ---
                   9690: sub show_grading_menu_form {
1.324     albertel 9691:     my ($symb)=@_;
1.125     ng       9692:     my $result.='<br /><form action="/adm/grades" method="post">'."\n".
1.418     albertel 9693: 	'<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1.257     albertel 9694: 	'<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
1.72      ng       9695: 	'<input type="hidden" name="command" value="gradingmenu" />'."\n".
1.478     albertel 9696: 	'<input type="submit" name="submit" value="'.&mt('Grading Menu').'" />'."\n".
1.72      ng       9697: 	'</form>'."\n";
                   9698:     return $result;
                   9699: }
                   9700: 
1.77      ng       9701: # -- Retrieve choices for grading form
                   9702: sub savedState {
                   9703:     my %savedState = ();
1.257     albertel 9704:     if ($env{'form.saveState'}) {
                   9705: 	foreach (split(/:/,$env{'form.saveState'})) {
1.77      ng       9706: 	    my ($key,$value) = split(/=/,$_,2);
                   9707: 	    $savedState{$key} = $value;
                   9708: 	}
                   9709:     }
                   9710:     return \%savedState;
                   9711: }
1.76      ng       9712: 
1.596.2.12.2.  (raeburn 9713:): #--- Href with symb and command ---
                   9714:): 
                   9715:): sub href_symb_cmd {
                   9716:):     my ($symb,$cmd)=@_;
                   9717:):     return '/adm/grades?symb='.&HTML::Entities::encode(&Apache::lonenc::check_encrypt($symb),'<>&"').'&amp;command='.$cmd;
                   9718:): }
                   9719:): 
1.443     banghart 9720: sub grading_menu {
                   9721:     my ($request) = @_;
                   9722:     my ($symb)=&get_symb($request);
                   9723:     if (!$symb) {return '';}
                   9724:     my $probTitle = &Apache::lonnet::gettitle($symb);
                   9725:     my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
                   9726: 
1.444     banghart 9727:     $request->print($table);
1.443     banghart 9728:     my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),
                   9729:                   'handgrade'=>$hdgrade,
                   9730:                   'probTitle'=>$probTitle,
                   9731:                   'command'=>'submit_options',
                   9732:                   'saveState'=>"",
                   9733:                   'gradingMenu'=>1,
                   9734:                   'showgrading'=>"yes");
1.538     schulted 9735:     
                   9736:     my $url1 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
                   9737:     
1.443     banghart 9738:     $fields{'command'} = 'csvform';
1.538     schulted 9739:     my $url2 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
                   9740:     
1.443     banghart 9741:     $fields{'command'} = 'processclicker';
1.538     schulted 9742:     my $url3 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
                   9743:     
1.443     banghart 9744:     $fields{'command'} = 'scantron_selectphase';
1.538     schulted 9745:     my $url4 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
                   9746:     
                   9747:     my @menu = ({	categorytitle=>'Course Grading',
                   9748:             items =>[
                   9749:                         {	linktext => 'Manual Grading/View Submissions',
                   9750:                     		url => $url1,
                   9751:                     		permission => 'F',
                   9752:                     		icon => 'edit-find-replace.png',
                   9753:                     		linktitle => 'Start the process of hand grading submissions.'
                   9754:                         },
                   9755:                 	    {	linktext => 'Upload Scores',
                   9756:                     		url => $url2,
                   9757:                     		permission => 'F',
                   9758:                     		icon => 'uploadscores.png',
                   9759:                     		linktitle => 'Specify a file containing the class scores for current resource.'
                   9760:                 	    },
                   9761:                 	    {	linktext => 'Process Clicker',
                   9762:                     		url => $url3,
                   9763:                     		permission => 'F',
                   9764:                     		icon => 'addClickerInfoFile.png',
                   9765:                     		linktitle => 'Specify a file containing the clicker information for this resource.'
                   9766:                 	    },
1.587     raeburn  9767:                 	    {	linktext => 'Grade/Manage/Review Bubblesheets',
1.538     schulted 9768:                     		url => $url4,
                   9769:                     		permission => 'F',
                   9770:                     		icon => 'stat.png',
1.596.2.4  raeburn  9771:                     		linktitle => 'Grade bubblesheet exams, upload/download bubblesheet data files, and review previously graded bubblesheet exams.'
1.538     schulted 9772:                 	    }
                   9773:                     ]
                   9774:             });
                   9775: 
                   9776:     #$fields{'command'} = 'verify';
                   9777:     #$url = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
1.443     banghart 9778:     #
                   9779:     # Create the menu
                   9780:     my $Str;
1.444     banghart 9781:     # $Str .= '<h2>'.&mt('Please select a grading task').'</h2>';
1.445     banghart 9782:     $Str .= '<form method="post" action="" name="gradingMenu">';
                   9783:     $Str .= '<input type="hidden" name="command" value="" />'.
                   9784:     	'<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
                   9785: 	'<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
1.476     albertel 9786: 	'<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".
1.445     banghart 9787: 	'<input type="hidden" name="saveState"   value="" />'."\n".
                   9788: 	'<input type="hidden" name="gradingMenu" value="1" />'."\n".
                   9789: 	'<input type="hidden" name="showgrading" value="yes" />'."\n";
                   9790: 
1.538     schulted 9791:     $Str .= Apache::lonhtmlcommon::generate_menu(@menu);
                   9792:     #$menudata->{'jscript'}
1.584     bisitz   9793:     $Str .='<hr /><input type="button" value="'.&mt('Verify Receipt No.').'" '.
1.589     bisitz   9794:         ' onclick="javascript:checkChoice(document.forms.gradingMenu,\'5\',\'verify\')" '.
1.538     schulted 9795:         ' /> '.
                   9796:         &Apache::lonnet::recprefix($env{'request.course.id'}).
1.589     bisitz   9797:         '-<input type="text" name="receipt" size="4" onchange="javascript:checkReceiptNo(this.form,\'OK\')" />';
1.538     schulted 9798: 
1.444     banghart 9799:     $Str .="</form>\n";
1.539     riegler  9800:     my $receiptalert = &mt("Please enter a receipt number given by a student in the receipt box.");
1.443     banghart 9801:     $request->print(<<GRADINGMENUJS);
                   9802: <script type="text/javascript" language="javascript">
                   9803:     function checkChoice(formname,val,cmdx) {
                   9804: 	if (val <= 2) {
                   9805: 	    var cmd = radioSelection(formname.radioChoice);
                   9806: 	    var cmdsave = cmd;
                   9807: 	} else {
                   9808: 	    cmd = cmdx;
                   9809: 	    cmdsave = 'submission';
                   9810: 	}
                   9811: 	formname.command.value = cmd;
                   9812: 	if (val < 5) formname.submit();
                   9813: 	if (val == 5) {
1.458     banghart 9814: 	    if (!checkReceiptNo(formname,'notOK')) { 
                   9815: 	        return false;
                   9816: 	    } else {
                   9817: 	        formname.submit();
                   9818: 	    }
1.445     banghart 9819: 	}
                   9820:     }
1.443     banghart 9821: 
                   9822:     function checkReceiptNo(formname,nospace) {
                   9823: 	var receiptNo = formname.receipt.value;
                   9824: 	var checkOpt = false;
                   9825: 	if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
                   9826: 	if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
                   9827: 	if (checkOpt) {
1.539     riegler  9828: 	    alert("$receiptalert");
1.443     banghart 9829: 	    formname.receipt.value = "";
                   9830: 	    formname.receipt.focus();
                   9831: 	    return false;
                   9832: 	}
                   9833: 	return true;
                   9834:     }
                   9835: </script>
                   9836: GRADINGMENUJS
                   9837:     &commonJSfunctions($request);
                   9838:     return $Str;    
                   9839: }
                   9840: 
                   9841: 
                   9842: #--- Displays the submissions first page -------
                   9843: sub submit_options {
1.72      ng       9844:     my ($request) = @_;
1.324     albertel 9845:     my ($symb)=&get_symb($request);
1.72      ng       9846:     if (!$symb) {return '';}
1.76      ng       9847:     my $probTitle = &Apache::lonnet::gettitle($symb);
1.72      ng       9848: 
1.539     riegler  9849:     my $receiptalert = &mt("Please enter a receipt number given by a student in the receipt box."); 
1.72      ng       9850:     $request->print(<<GRADINGMENUJS);
                   9851: <script type="text/javascript" language="javascript">
1.116     ng       9852:     function checkChoice(formname,val,cmdx) {
                   9853: 	if (val <= 2) {
                   9854: 	    var cmd = radioSelection(formname.radioChoice);
1.118     ng       9855: 	    var cmdsave = cmd;
1.116     ng       9856: 	} else {
                   9857: 	    cmd = cmdx;
1.118     ng       9858: 	    cmdsave = 'submission';
1.116     ng       9859: 	}
                   9860: 	formname.command.value = cmd;
1.118     ng       9861: 	formname.saveState.value = "saveCmd="+cmdsave+":saveSec="+pullDownSelection(formname.section)+
1.145     albertel 9862: 	    ":saveSub="+pullDownSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.Status);
1.116     ng       9863: 	if (val < 5) formname.submit();
                   9864: 	if (val == 5) {
1.72      ng       9865: 	    if (!checkReceiptNo(formname,'notOK')) { return false;}
                   9866: 	    formname.submit();
                   9867: 	}
1.238     albertel 9868: 	if (val < 7) formname.submit();
1.72      ng       9869:     }
                   9870: 
                   9871:     function checkReceiptNo(formname,nospace) {
                   9872: 	var receiptNo = formname.receipt.value;
                   9873: 	var checkOpt = false;
                   9874: 	if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;}
                   9875: 	if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;}
                   9876: 	if (checkOpt) {
1.539     riegler  9877: 	    alert("$receiptalert");
1.72      ng       9878: 	    formname.receipt.value = "";
                   9879: 	    formname.receipt.focus();
                   9880: 	    return false;
                   9881: 	}
                   9882: 	return true;
                   9883:     }
                   9884: </script>
                   9885: GRADINGMENUJS
1.118     ng       9886:     &commonJSfunctions($request);
1.324     albertel 9887:     my ($table,undef,$hdgrade) = &showResourceInfo($symb,$probTitle);
1.473     albertel 9888:     my $result;
1.76      ng       9889:     my (undef,$sections) = &getclasslist('all','0');
1.77      ng       9890:     my $savedState = &savedState();
1.118     ng       9891:     my $saveCmd = ($$savedState{'saveCmd'} eq '' ? 'submission' : $$savedState{'saveCmd'});
1.77      ng       9892:     my $saveSec = ($$savedState{'saveSec'} eq '' ? 'all' : $$savedState{'saveSec'});
1.118     ng       9893:     my $saveSub = ($$savedState{'saveSub'} eq '' ? 'all' : $$savedState{'saveSub'});
1.77      ng       9894:     my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'});
1.72      ng       9895: 
1.533     bisitz   9896:     # Preselect sections
                   9897:     my $selsec="";
                   9898:     if (ref($sections)) {
                   9899:         foreach my $section (sort(@$sections)) {
                   9900:             $selsec.='<option value="'.$section.'" '.
                   9901:                 ($saveSec eq $section ? 'selected="selected"':'').'>'.$section.'</option>'."\n";
                   9902:         }
                   9903:     }
                   9904: 
1.72      ng       9905:     $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
1.418     albertel 9906: 	'<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1.72      ng       9907: 	'<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
                   9908: 	'<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".
1.116     ng       9909: 	'<input type="hidden" name="command"     value="" />'."\n".
1.77      ng       9910: 	'<input type="hidden" name="saveState"   value="" />'."\n".
1.124     ng       9911: 	'<input type="hidden" name="gradingMenu" value="1" />'."\n".
1.72      ng       9912: 	'<input type="hidden" name="showgrading" value="yes" />'."\n";
                   9913: 
1.472     albertel 9914:     $result.='
1.533     bisitz   9915: <h2>
                   9916:   '.&mt('Grade Current Resource').'
                   9917: </h2>
                   9918: <div>
                   9919:   '.$table.'
                   9920: </div>
                   9921: 
1.537     harmsja  9922: <div class="LC_columnSection">
                   9923:   
1.533     bisitz   9924:     <fieldset>
                   9925:       <legend>
                   9926:        '.&mt('Sections').'
                   9927:       </legend>
                   9928:       <select name="section" multiple="multiple" size="5">'."\n";
                   9929:     $result.= $selsec;
1.401     albertel 9930:     $result.= '<option value="all" '.($saveSec eq 'all' ? 'selected="selected"' : ''). '>all</option></select> &nbsp; ';
1.472     albertel 9931:     $result.='
1.533     bisitz   9932:     </fieldset>
1.537     harmsja  9933:   
1.533     bisitz   9934:     <fieldset>
                   9935:       <legend>
                   9936:         '.&mt('Groups').'
                   9937:       </legend>
                   9938:       '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'
                   9939:     </fieldset>
1.537     harmsja  9940:   
1.533     bisitz   9941:     <fieldset>
                   9942:       <legend>
                   9943:         '.&mt('Access Status').'
                   9944:       </legend>
                   9945:       '.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,5,undef,'mult').'
                   9946:     </fieldset>
1.537     harmsja  9947:   
1.533     bisitz   9948:     <fieldset>
                   9949:       <legend>
                   9950:         '.&mt('Submission Status').'
                   9951:       </legend>
                   9952:       <select name="submitonly" size="5">
1.473     albertel 9953: 	         <option value="yes" '.      ($saveSub eq 'yes'       ? 'selected="selected"' : '').'>'.&mt('with submissions').'</option>
                   9954: 	         <option value="queued" '.   ($saveSub eq 'queued'    ? 'selected="selected"' : '').'>'.&mt('in grading queue').'</option>
                   9955: 	         <option value="graded" '.   ($saveSub eq 'graded'    ? 'selected="selected"' : '').'>'.&mt('with ungraded submissions').'</option>
                   9956: 	         <option value="incorrect" '.($saveSub eq 'incorrect' ? 'selected="selected"' : '').'>'.&mt('with incorrect submissions').'</option>
                   9957:                  <option value="all" '.      ($saveSub eq 'all'       ? 'selected="selected"' : '').'>'.&mt('with any status').'</option>
1.533     bisitz   9958:       </select>
                   9959:     </fieldset>
1.537     harmsja  9960:   
1.533     bisitz   9961: </div>
                   9962: 
                   9963: <br />
                   9964:           <div>
                   9965:             <div>
1.473     albertel 9966:               <label>
                   9967:                 <input type="radio" name="radioChoice" value="submission" '.
                   9968:                   ($saveCmd eq 'submission' ? 'checked="checked"' : '').' /> '.
                   9969:              &mt('Select individual students to grade and view submissions.').'
                   9970: 	      </label> 
                   9971:             </div>
1.533     bisitz   9972:             <div>
1.473     albertel 9973: 	      <label>
                   9974:                 <input type="radio" name="radioChoice" value="viewgrades" '.
                   9975:                   ($saveCmd eq 'viewgrades' ? 'checked="checked"' : '').' /> '.
                   9976:                     &mt('Grade all selected students in a grading table.').'
                   9977:               </label>
                   9978:             </div>
1.533     bisitz   9979:             <div>
1.589     bisitz   9980: 	      <input type="button" onclick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next').' &rarr;" />
1.473     albertel 9981:             </div>
1.472     albertel 9982:           </div>
1.533     bisitz   9983: 
                   9984: 
1.473     albertel 9985:         <h2>
                   9986:          '.&mt('Grade Complete Folder for One Student').'
                   9987:         </h2>
1.533     bisitz   9988:         <div>
                   9989:             <div>
1.473     albertel 9990:               <label>
                   9991:                 <input type="radio" name="radioChoice" value="pickStudentPage" '.
                   9992: 	  ($saveCmd eq 'pickStudentPage' ? 'checked="checked"' : '').' /> '.
                   9993:   &mt('The <b>complete</b> page/sequence/folder: For one student').'
                   9994:               </label>
                   9995:             </div>
1.533     bisitz   9996:             <div>
1.589     bisitz   9997: 	      <input type="button" onclick="javascript:checkChoice(this.form,\'2\');" value="'.&mt('Next').' &rarr;" />
1.473     albertel 9998:             </div>
1.472     albertel 9999:         </div>
                   10000:   </form>';
1.499     albertel 10001:     $result .= &show_grading_menu_form($symb);
1.44      ng       10002:     return $result;
1.2       albertel 10003: }
                   10004: 
1.596.2.12.2.  7(raebur 10005:6): sub substatus_options {
                   10006:6):     return &Apache::lonlocal::texthash(
                   10007:6):                                       'yes'       => 'with submissions',
                   10008:6):                                       'queued'    => 'in grading queue',
                   10009:6):                                       'graded'    => 'with ungraded submissions',
                   10010:6):                                       'incorrect' => 'with incorrect submissions',
                   10011:6):                                       'all'       => 'with any status');
                   10012:6): }
                   10013:6): 
1.285     albertel 10014: sub reset_perm {
                   10015:     undef(%perm);
                   10016: }
                   10017: 
                   10018: sub init_perm {
                   10019:     &reset_perm();
1.300     albertel 10020:     foreach my $test_perm ('vgr','mgr','opa') {
                   10021: 
                   10022: 	my $scope = $env{'request.course.id'};
                   10023: 	if (!($perm{$test_perm}=&Apache::lonnet::allowed($test_perm,$scope))) {
                   10024: 
                   10025: 	    $scope .= '/'.$env{'request.course.sec'};
                   10026: 	    if ( $perm{$test_perm}=
                   10027: 		 &Apache::lonnet::allowed($test_perm,$scope)) {
                   10028: 		$perm{$test_perm.'_section'}=$env{'request.course.sec'};
                   10029: 	    } else {
                   10030: 		delete($perm{$test_perm});
                   10031: 	    }
1.285     albertel 10032: 	}
                   10033:     }
                   10034: }
                   10035: 
1.596.2.12.2.  (raeburn 10036:): sub init_old_essays {
                   10037:):     my ($symb,$apath,$adom,$aname) = @_;
                   10038:):     if ($symb ne '') {
                   10039:):         my %essays = &Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
                   10040:):         if (keys(%essays) > 0) {
                   10041:):             $old_essays{$symb} = \%essays;
                   10042:):         }
                   10043:):     }
                   10044:):     return;
                   10045:): }
                   10046:): 
                   10047:): sub reset_old_essays {
                   10048:):     undef(%old_essays);
                   10049:): }
                   10050:): 
1.400     www      10051: sub gather_clicker_ids {
1.408     albertel 10052:     my %clicker_ids;
1.400     www      10053: 
                   10054:     my $classlist = &Apache::loncoursedata::get_classlist();
                   10055: 
                   10056:     # Set up a couple variables.
1.407     albertel 10057:     my $username_idx = &Apache::loncoursedata::CL_SNAME();
                   10058:     my $domain_idx   = &Apache::loncoursedata::CL_SDOM();
1.438     www      10059:     my $status_idx   = &Apache::loncoursedata::CL_STATUS();
1.400     www      10060: 
1.407     albertel 10061:     foreach my $student (keys(%$classlist)) {
1.438     www      10062:         if ($classlist->{$student}->[$status_idx] ne 'Active') { next; }
1.407     albertel 10063:         my $username = $classlist->{$student}->[$username_idx];
                   10064:         my $domain   = $classlist->{$student}->[$domain_idx];
1.400     www      10065:         my $clickers =
1.408     albertel 10066: 	    (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];
1.400     www      10067:         foreach my $id (split(/\,/,$clickers)) {
1.414     www      10068:             $id=~s/^[\#0]+//;
1.421     www      10069:             $id=~s/[\-\:]//g;
1.407     albertel 10070:             if (exists($clicker_ids{$id})) {
1.408     albertel 10071: 		$clicker_ids{$id}.=','.$username.':'.$domain;
1.400     www      10072:             } else {
1.408     albertel 10073: 		$clicker_ids{$id}=$username.':'.$domain;
1.400     www      10074:             }
                   10075:         }
                   10076:     }
1.407     albertel 10077:     return %clicker_ids;
1.400     www      10078: }
                   10079: 
1.402     www      10080: sub gather_adv_clicker_ids {
1.408     albertel 10081:     my %clicker_ids;
1.402     www      10082:     my $cnum=$env{'course.'.$env{'request.course.id'}.'.num'};
                   10083:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
                   10084:     my %coursepersonnel=&Apache::lonnet::get_course_adv_roles($cdom.'/'.$cnum);
1.409     albertel 10085:     foreach my $element (sort(keys(%coursepersonnel))) {
1.402     www      10086:         foreach my $person (split(/\,/,$coursepersonnel{$element})) {
                   10087:             my ($puname,$pudom)=split(/\:/,$person);
                   10088:             my $clickers =
1.408     albertel 10089: 		(&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];
1.405     www      10090:             foreach my $id (split(/\,/,$clickers)) {
1.414     www      10091: 		$id=~s/^[\#0]+//;
1.421     www      10092:                 $id=~s/[\-\:]//g;
1.408     albertel 10093: 		if (exists($clicker_ids{$id})) {
                   10094: 		    $clicker_ids{$id}.=','.$puname.':'.$pudom;
                   10095: 		} else {
                   10096: 		    $clicker_ids{$id}=$puname.':'.$pudom;
                   10097: 		}
1.405     www      10098:             }
1.402     www      10099:         }
                   10100:     }
1.407     albertel 10101:     return %clicker_ids;
1.402     www      10102: }
                   10103: 
1.413     www      10104: sub clicker_grading_parameters {
                   10105:     return ('gradingmechanism' => 'scalar',
                   10106:             'upfiletype' => 'scalar',
                   10107:             'specificid' => 'scalar',
                   10108:             'pcorrect' => 'scalar',
                   10109:             'pincorrect' => 'scalar');
                   10110: }
                   10111: 
1.400     www      10112: sub process_clicker {
                   10113:     my ($r)=@_;
                   10114:     my ($symb)=&get_symb($r);
                   10115:     if (!$symb) {return '';}
                   10116:     my $result=&checkforfile_js();
                   10117:     $env{'form.probTitle'} = &Apache::lonnet::gettitle($symb);
                   10118:     my ($table) = &showResourceInfo($symb,$env{'form.probTitle'});
                   10119:     $result.=$table;
                   10120:     $result.='<br /><table width="100%" border="0"><tr><td bgcolor="#777777">'."\n";
                   10121:     $result.='<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>'."\n";
1.538     schulted 10122:     $result.='&nbsp;<b>'.&mt('Specify a file containing the clicker information for this resource.').
                   10123:         '</b></td></tr>'."\n";
1.596.2.4  raeburn  10124:     $result.='<tr bgcolor="#ffffe6"><td>'."\n";
1.413     www      10125: # Attempt to restore parameters from last session, set defaults if not present
                   10126:     my %Saveable_Parameters=&clicker_grading_parameters();
                   10127:     &Apache::loncommon::restore_course_settings('grades_clicker',
                   10128:                                                  \%Saveable_Parameters);
                   10129:     if (!$env{'form.pcorrect'}) { $env{'form.pcorrect'}=100; }
                   10130:     if (!$env{'form.pincorrect'}) { $env{'form.pincorrect'}=100; }
                   10131:     if (!$env{'form.gradingmechanism'}) { $env{'form.gradingmechanism'}='attendance'; }
                   10132:     if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }
                   10133: 
                   10134:     my %checked;
1.521     www      10135:     foreach my $gradingmechanism ('attendance','personnel','specific','given') {
1.413     www      10136:        if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
1.569     bisitz   10137:           $checked{$gradingmechanism}=' checked="checked"';
1.413     www      10138:        }
                   10139:     }
                   10140: 
1.400     www      10141:     my $upload=&mt("Upload File");
                   10142:     my $type=&mt("Type");
1.402     www      10143:     my $attendance=&mt("Award points just for participation");
                   10144:     my $personnel=&mt("Correctness determined from response by course personnel");
1.414     www      10145:     my $specific=&mt("Correctness determined from response with clicker ID(s)"); 
1.521     www      10146:     my $given=&mt("Correctness determined from given list of answers").' '.
                   10147:               '<font size="-2"><tt>('.&mt("Provide comma-separated list. Use '*' for any answer correct, '-' for skip").')</tt></font>';
1.402     www      10148:     my $pcorrect=&mt("Percentage points for correct solution");
                   10149:     my $pincorrect=&mt("Percentage points for incorrect solution");
1.413     www      10150:     my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
1.596.2.1  raeburn  10151:                                                    {'iclicker' => 'i>clicker',
1.596.2.12.2.  (raeburn 10152:):                                                     'interwrite' => 'interwrite PRS',
                   10153:):                                                     'turning' => 'Turning Technologies'});
1.418     albertel 10154:     $symb = &Apache::lonenc::check_encrypt($symb);
1.400     www      10155:     $result.=<<ENDUPFORM;
1.402     www      10156: <script type="text/javascript">
                   10157: function sanitycheck() {
                   10158: // Accept only integer percentages
                   10159:    document.forms.gradesupload.pcorrect.value=Math.round(document.forms.gradesupload.pcorrect.value);
                   10160:    document.forms.gradesupload.pincorrect.value=Math.round(document.forms.gradesupload.pincorrect.value);
                   10161: // Find out grading choice
                   10162:    for (i=0; i<document.forms.gradesupload.gradingmechanism.length; i++) {
                   10163:       if (document.forms.gradesupload.gradingmechanism[i].checked) {
                   10164:          gradingchoice=document.forms.gradesupload.gradingmechanism[i].value;
                   10165:       }
                   10166:    }
                   10167: // By default, new choice equals user selection
                   10168:    newgradingchoice=gradingchoice;
                   10169: // Not good to give more points for false answers than correct ones
                   10170:    if (Math.round(document.forms.gradesupload.pcorrect.value)<Math.round(document.forms.gradesupload.pincorrect.value)) {
                   10171:       document.forms.gradesupload.pcorrect.value=document.forms.gradesupload.pincorrect.value;
                   10172:    }
                   10173: // If new choice is attendance only, and old choice was correctness-based, restore defaults
                   10174:    if ((gradingchoice=='attendance') && (document.forms.gradesupload.waschecked.value!='attendance')) {
                   10175:       document.forms.gradesupload.pcorrect.value=100;
                   10176:       document.forms.gradesupload.pincorrect.value=100;
                   10177:    }
                   10178: // If the values are different, cannot be attendance only
                   10179:    if ((Math.round(document.forms.gradesupload.pcorrect.value)!=Math.round(document.forms.gradesupload.pincorrect.value)) &&
                   10180:        (gradingchoice=='attendance')) {
                   10181:        newgradingchoice='personnel';
                   10182:    }
                   10183: // Change grading choice to new one
                   10184:    for (i=0; i<document.forms.gradesupload.gradingmechanism.length; i++) {
                   10185:       if (document.forms.gradesupload.gradingmechanism[i].value==newgradingchoice) {
                   10186:          document.forms.gradesupload.gradingmechanism[i].checked=true;
                   10187:       } else {
                   10188:          document.forms.gradesupload.gradingmechanism[i].checked=false;
                   10189:       }
                   10190:    }
                   10191: // Remember the old state
                   10192:    document.forms.gradesupload.waschecked.value=newgradingchoice;
                   10193: }
                   10194: </script>
1.400     www      10195: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
                   10196: <input type="hidden" name="symb" value="$symb" />
                   10197: <input type="hidden" name="command" value="processclickerfile" />
                   10198: <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
                   10199: <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
                   10200: <input type="file" name="upfile" size="50" />
                   10201: <br /><label>$type: $selectform</label>
1.589     bisitz   10202: <br /><label><input type="radio" name="gradingmechanism" value="attendance"$checked{'attendance'} onclick="sanitycheck()" />$attendance </label>
                   10203: <br /><label><input type="radio" name="gradingmechanism" value="personnel"$checked{'personnel'} onclick="sanitycheck()" />$personnel</label>
                   10204: <br /><label><input type="radio" name="gradingmechanism" value="specific"$checked{'specific'} onclick="sanitycheck()" />$specific </label>
1.414     www      10205: <input type="text" name="specificid" value="$env{'form.specificid'}" size="20" />
1.589     bisitz   10206: <br /><label><input type="radio" name="gradingmechanism" value="given"$checked{'given'} onclick="sanitycheck()" />$given </label>
1.521     www      10207: <br />&nbsp;&nbsp;&nbsp;
                   10208: <input type="text" name="givenanswer" size="50" />
1.413     www      10209: <input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" />
1.589     bisitz   10210: <br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onchange="sanitycheck()" /></label>
                   10211: <br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onchange="sanitycheck()" /></label>
                   10212: <br /><input type="button" onclick="javascript:checkUpload(this.form);" value="$upload" />
1.400     www      10213: </form>
                   10214: ENDUPFORM
                   10215:     $result.='</td></tr></table>'."\n".
                   10216:              '</td></tr></table><br /><br />'."\n";
                   10217:     $result.=&show_grading_menu_form($symb);
                   10218:     return $result;
                   10219: }
                   10220: 
                   10221: sub process_clicker_file {
                   10222:     my ($r)=@_;
                   10223:     my ($symb)=&get_symb($r);
                   10224:     if (!$symb) {return '';}
1.413     www      10225: 
                   10226:     my %Saveable_Parameters=&clicker_grading_parameters();
                   10227:     &Apache::loncommon::store_course_settings('grades_clicker',
                   10228:                                               \%Saveable_Parameters);
                   10229: 
1.400     www      10230:     my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
1.404     www      10231:     if (($env{'form.gradingmechanism'} eq 'specific') && ($env{'form.specificid'}!~/\w/)) {
1.408     albertel 10232: 	$result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';
                   10233: 	return $result.&show_grading_menu_form($symb);
1.404     www      10234:     }
1.522     www      10235:     if (($env{'form.gradingmechanism'} eq 'given') && ($env{'form.givenanswer'}!~/\S/)) {
1.521     www      10236:         $result.='<span class="LC_error">'.&mt('You need to specify the correct answer').'</span>';
                   10237:         return $result.&show_grading_menu_form($symb);
                   10238:     }
1.522     www      10239:     my $foundgiven=0;
1.521     www      10240:     if ($env{'form.gradingmechanism'} eq 'given') {
                   10241:         $env{'form.givenanswer'}=~s/^\s*//gs;
                   10242:         $env{'form.givenanswer'}=~s/\s*$//gs;
1.596.2.4  raeburn  10243:         $env{'form.givenanswer'}=~s/[^a-zA-Z0-9\.\*\-\+]+/\,/g;
1.521     www      10244:         $env{'form.givenanswer'}=uc($env{'form.givenanswer'});
1.522     www      10245:         my @answers=split(/\,/,$env{'form.givenanswer'});
                   10246:         $foundgiven=$#answers+1;
1.521     www      10247:     }
1.407     albertel 10248:     my %clicker_ids=&gather_clicker_ids();
1.408     albertel 10249:     my %correct_ids;
1.404     www      10250:     if ($env{'form.gradingmechanism'} eq 'personnel') {
1.408     albertel 10251: 	%correct_ids=&gather_adv_clicker_ids();
1.404     www      10252:     }
                   10253:     if ($env{'form.gradingmechanism'} eq 'specific') {
1.414     www      10254: 	foreach my $correct_id (split(/[\s\,]/,$env{'form.specificid'})) {;
                   10255: 	   $correct_id=~tr/a-z/A-Z/;
                   10256: 	   $correct_id=~s/\s//gs;
                   10257: 	   $correct_id=~s/^[\#0]+//;
1.421     www      10258:            $correct_id=~s/[\-\:]//g;
1.414     www      10259:            if ($correct_id) {
                   10260: 	      $correct_ids{$correct_id}='specified';
                   10261:            }
                   10262:         }
1.400     www      10263:     }
1.404     www      10264:     if ($env{'form.gradingmechanism'} eq 'attendance') {
1.408     albertel 10265: 	$result.=&mt('Score based on attendance only');
1.521     www      10266:     } elsif ($env{'form.gradingmechanism'} eq 'given') {
1.522     www      10267:         $result.=&mt('Score based on [_1] ([_2] answers)','<tt>'.$env{'form.givenanswer'}.'</tt>',$foundgiven);
1.404     www      10268:     } else {
1.408     albertel 10269: 	my $number=0;
1.411     www      10270: 	$result.='<p><b>'.&mt('Correctness determined by the following IDs').'</b>';
1.408     albertel 10271: 	foreach my $id (sort(keys(%correct_ids))) {
1.411     www      10272: 	    $result.='<br /><tt>'.$id.'</tt> - ';
1.408     albertel 10273: 	    if ($correct_ids{$id} eq 'specified') {
                   10274: 		$result.=&mt('specified');
                   10275: 	    } else {
                   10276: 		my ($uname,$udom)=split(/\:/,$correct_ids{$id});
                   10277: 		$result.=&Apache::loncommon::plainname($uname,$udom);
                   10278: 	    }
                   10279: 	    $number++;
                   10280: 	}
1.411     www      10281:         $result.="</p>\n";
1.596.2.12.2.  5(raebur 10282:3):         if ($number==0) {
                   10283:3):             $result .=
                   10284:3):                  &Apache::lonhtmlcommon::confirm_success(
                   10285:3):                      &mt('No IDs found to determine correct answer'),1);
                   10286:3):             return $result,.&show_grading_menu_form($symb);
                   10287:3):         }
1.404     www      10288:     }
1.405     www      10289:     if (length($env{'form.upfile'}) < 2) {
1.596.2.12.2.  5(raebur 10290:3):         $result .=
                   10291:3):             &Apache::lonhtmlcommon::confirm_success(
                   10292:3):                 &mt('The file: [_1] you attempted to upload contained no information. Please check that you entered the correct filename.',
                   10293:3):                         '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'),1);
1.405     www      10294:         return $result.&show_grading_menu_form($symb);
                   10295:     }
1.410     www      10296: 
                   10297: # Were able to get all the info needed, now analyze the file
                   10298: 
1.411     www      10299:     $result.=&Apache::loncommon::studentbrowser_javascript();
1.418     albertel 10300:     $symb = &Apache::lonenc::check_encrypt($symb);
1.410     www      10301:     my $heading=&mt('Scanning clicker file');
                   10302:     $result.=(<<ENDHEADER);
                   10303: <br /><table width="100%" border="0"><tr><td bgcolor="#777777">
                   10304: <table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
1.596.2.4  raeburn  10305: <b>$heading</b></td></tr><tr bgcolor="#ffffe6"><td>
1.410     www      10306: <form method="post" action="/adm/grades" name="clickeranalysis">
                   10307: <input type="hidden" name="symb" value="$symb" />
                   10308: <input type="hidden" name="command" value="assignclickergrades" />
                   10309: <input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
                   10310: <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
1.411     www      10311: <input type="hidden" name="gradingmechanism" value="$env{'form.gradingmechanism'}" />
                   10312: <input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />
                   10313: <input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />
1.410     www      10314: ENDHEADER
1.522     www      10315:     if ($env{'form.gradingmechanism'} eq 'given') {
                   10316:        $result.='<input type="hidden" name="correct:given" value="'.$env{'form.givenanswer'}.'" />';
                   10317:     } 
1.408     albertel 10318:     my %responses;
                   10319:     my @questiontitles;
1.405     www      10320:     my $errormsg='';
                   10321:     my $number=0;
                   10322:     if ($env{'form.upfiletype'} eq 'iclicker') {
1.408     albertel 10323: 	($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses);
1.406     www      10324:     }
1.419     www      10325:     if ($env{'form.upfiletype'} eq 'interwrite') {
                   10326:         ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses);
                   10327:     }
1.596.2.12.2.  (raeburn 10328:):     if ($env{'form.upfiletype'} eq 'turning') {
                   10329:):         ($errormsg,$number)=&turning_eval(\@questiontitles,\%responses);
                   10330:):     }
1.411     www      10331:     $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'.
                   10332:              '<input type="hidden" name="number" value="'.$number.'" />'.
                   10333:              &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',
                   10334:                  $env{'form.pcorrect'},$env{'form.pincorrect'}).
                   10335:              '<br />';
1.522     www      10336:     if (($env{'form.gradingmechanism'} eq 'given') && ($number!=$foundgiven)) {
                   10337:        $result.='<span class="LC_error">'.&mt('Number of given answers does not agree with number of questions in file.').'</span>';
                   10338:        return $result.&show_grading_menu_form($symb);
                   10339:     } 
1.414     www      10340: # Remember Question Titles
                   10341: # FIXME: Possibly need delimiter other than ":"
                   10342:     for (my $i=0;$i<$number;$i++) {
                   10343:         $result.='<input type="hidden" name="question:'.$i.'" value="'.
                   10344:                  &HTML::Entities::encode($questiontitles[$i],'"&<>').'" />';
                   10345:     }
1.411     www      10346:     my $correct_count=0;
                   10347:     my $student_count=0;
                   10348:     my $unknown_count=0;
1.414     www      10349: # Match answers with usernames
                   10350: # FIXME: Possibly need delimiter other than ":"
1.409     albertel 10351:     foreach my $id (keys(%responses)) {
1.410     www      10352:        if ($correct_ids{$id}) {
1.414     www      10353:           $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />';
1.411     www      10354:           $correct_count++;
1.410     www      10355:        } elsif ($clicker_ids{$id}) {
1.437     www      10356:           if ($clicker_ids{$id}=~/\,/) {
                   10357: # More than one user with the same clicker!
                   10358:              $result.="\n<hr />".&mt('Clicker registered more than once').": <tt>".$id."</tt><br />";
                   10359:              $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
                   10360:                            "<select name='multi".$id."'>";
                   10361:              foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) {
                   10362:                  $result.="<option value='".$reguser."'>".&Apache::loncommon::plainname(split(/\:/,$reguser)).' ('.$reguser.')</option>';
                   10363:              }
                   10364:              $result.='</select>';
                   10365:              $unknown_count++;
                   10366:           } else {
                   10367: # Good: found one and only one user with the right clicker
                   10368:              $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />';
                   10369:              $student_count++;
                   10370:           }
1.410     www      10371:        } else {
1.411     www      10372:           $result.="\n<hr />".&mt('Unregistered Clicker')." <tt>".$id."</tt><br />";
                   10373:           $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
                   10374:                    "\n".&mt("Username").": <input type='text' name='uname".$id."' />&nbsp;".
                   10375:                    "\n".&mt("Domain").": ".
                   10376:                    &Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).'&nbsp;'.
1.596.2.4  raeburn  10377:                    &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id,0,$id);
1.411     www      10378:           $unknown_count++;
1.410     www      10379:        }
1.405     www      10380:     }
1.412     www      10381:     $result.='<hr />'.
                   10382:              &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);
1.521     www      10383:     if (($env{'form.gradingmechanism'} ne 'attendance') && ($env{'form.gradingmechanism'} ne 'given')) {
1.412     www      10384:        if ($correct_count==0) {
1.596.2.12.2.  8(raebur 10385:3):           $errormsg.="Found no correct answers for grading!";
1.412     www      10386:        } elsif ($correct_count>1) {
1.414     www      10387:           $result.='<br /><span class="LC_warning">'.&mt("Found [_1] entries for grading!",$correct_count).'</span>';
1.412     www      10388:        }
                   10389:     }
1.428     www      10390:     if ($number<1) {
                   10391:        $errormsg.="Found no questions.";
                   10392:     }
1.412     www      10393:     if ($errormsg) {
                   10394:        $result.='<br /><span class="LC_error">'.&mt($errormsg).'</span>';
                   10395:     } else {
                   10396:        $result.='<br /><input type="submit" name="finalize" value="'.&mt('Finalize Grading').'" />';
                   10397:     }
                   10398:     $result.='</form></td></tr></table>'."\n".
1.410     www      10399:              '</td></tr></table><br /><br />'."\n";
1.404     www      10400:     return $result.&show_grading_menu_form($symb);
1.400     www      10401: }
                   10402: 
1.405     www      10403: sub iclicker_eval {
1.406     www      10404:     my ($questiontitles,$responses)=@_;
1.405     www      10405:     my $number=0;
                   10406:     my $errormsg='';
                   10407:     foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
1.410     www      10408:         my %components=&Apache::loncommon::record_sep($line);
                   10409:         my @entries=map {$components{$_}} (sort(keys(%components)));
1.408     albertel 10410: 	if ($entries[0] eq 'Question') {
                   10411: 	    for (my $i=3;$i<$#entries;$i+=6) {
                   10412: 		$$questiontitles[$number]=$entries[$i];
                   10413: 		$number++;
                   10414: 	    }
                   10415: 	}
                   10416: 	if ($entries[0]=~/^\#/) {
                   10417: 	    my $id=$entries[0];
                   10418: 	    my @idresponses;
                   10419: 	    $id=~s/^[\#0]+//;
                   10420: 	    for (my $i=0;$i<$number;$i++) {
                   10421: 		my $idx=3+$i*6;
1.596.2.4  raeburn  10422:                 $entries[$idx]=~s/[^a-zA-Z0-9\.\*\-\+]+//g;
1.408     albertel 10423: 		push(@idresponses,$entries[$idx]);
                   10424: 	    }
                   10425: 	    $$responses{$id}=join(',',@idresponses);
                   10426: 	}
1.405     www      10427:     }
                   10428:     return ($errormsg,$number);
                   10429: }
                   10430: 
1.419     www      10431: sub interwrite_eval {
                   10432:     my ($questiontitles,$responses)=@_;
                   10433:     my $number=0;
                   10434:     my $errormsg='';
1.420     www      10435:     my $skipline=1;
                   10436:     my $questionnumber=0;
                   10437:     my %idresponses=();
1.419     www      10438:     foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
                   10439:         my %components=&Apache::loncommon::record_sep($line);
                   10440:         my @entries=map {$components{$_}} (sort(keys(%components)));
1.420     www      10441:         if ($entries[1] eq 'Time') { $skipline=0; next; }
                   10442:         if ($entries[1] eq 'Response') { $skipline=1; }
                   10443:         next if $skipline;
                   10444:         if ($entries[0]!=$questionnumber) {
                   10445:            $questionnumber=$entries[0];
                   10446:            $$questiontitles[$number]=&mt('Question [_1]',$questionnumber);
                   10447:            $number++;
1.419     www      10448:         }
1.420     www      10449:         my $id=$entries[4];
                   10450:         $id=~s/^[\#0]+//;
1.421     www      10451:         $id=~s/^v\d*\://i;
                   10452:         $id=~s/[\-\:]//g;
1.420     www      10453:         $idresponses{$id}[$number]=$entries[6];
                   10454:     }
1.524     raeburn  10455:     foreach my $id (keys(%idresponses)) {
1.420     www      10456:        $$responses{$id}=join(',',@{$idresponses{$id}});
                   10457:        $$responses{$id}=~s/^\s*\,//;
1.419     www      10458:     }
                   10459:     return ($errormsg,$number);
                   10460: }
                   10461: 
1.596.2.12.2.  (raeburn 10462:): sub turning_eval {
                   10463:):     my ($questiontitles,$responses)=@_;
                   10464:):     my $number=0;
                   10465:):     my $errormsg='';
                   10466:):     foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
                   10467:):         my %components=&Apache::loncommon::record_sep($line);
                   10468:):         my @entries=map {$components{$_}} (sort(keys(%components)));
                   10469:):         if ($#entries>$number) { $number=$#entries; }
                   10470:):         my $id=$entries[0];
                   10471:):         my @idresponses;
                   10472:):         $id=~s/^[\#0]+//;
                   10473:):         unless ($id) { next; }
                   10474:):         for (my $idx=1;$idx<=$#entries;$idx++) {
                   10475:):             $entries[$idx]=~s/\,/\;/g;
                   10476:):             $entries[$idx]=~s/[^a-zA-Z0-9\.\*\-\+\;]+//g;
                   10477:):             push(@idresponses,$entries[$idx]);
                   10478:):         }
                   10479:):         $$responses{$id}=join(',',@idresponses);
                   10480:):     }
                   10481:):     for (my $i=1; $i<=$number; $i++) {
                   10482:):         $$questiontitles[$i]=&mt('Question [_1]',$i);
                   10483:):     }
                   10484:):     return ($errormsg,$number);
                   10485:): }
                   10486:): 
1.414     www      10487: sub assign_clicker_grades {
                   10488:     my ($r)=@_;
                   10489:     my ($symb)=&get_symb($r);
                   10490:     if (!$symb) {return '';}
1.416     www      10491: # See which part we are saving to
1.582     raeburn  10492:     my $res_error;
                   10493:     my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
                   10494:     if ($res_error) {
                   10495:         return &navmap_errormsg();
                   10496:     }
1.416     www      10497: # FIXME: This should probably look for the first handgradeable part
                   10498:     my $part=$$partlist[0];
                   10499: # Start screen output
1.596.2.10  raeburn  10500:     my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
1.596.2.4  raeburn  10501: 
1.596.2.10  raeburn  10502:     $result .= '<br />'.
                   10503:                &Apache::loncommon::start_data_table().
1.596.2.4  raeburn  10504:                &Apache::loncommon::start_data_table_header_row().
                   10505:                '<th>'.&mt('Assigning grades based on clicker file').'</th>'.
                   10506:                &Apache::loncommon::end_data_table_header_row().
                   10507:                &Apache::loncommon::start_data_table_row().'<td>';
1.416     www      10508: 
1.414     www      10509: # Get correct result
                   10510: # FIXME: Possibly need delimiter other than ":"
                   10511:     my @correct=();
1.415     www      10512:     my $gradingmechanism=$env{'form.gradingmechanism'};
                   10513:     my $number=$env{'form.number'};
                   10514:     if ($gradingmechanism ne 'attendance') {
1.414     www      10515:        foreach my $key (keys(%env)) {
                   10516:           if ($key=~/^form\.correct\:/) {
                   10517:              my @input=split(/\,/,$env{$key});
                   10518:              for (my $i=0;$i<=$#input;$i++) {
                   10519:                  if (($correct[$i]) && ($input[$i]) &&
                   10520:                      ($correct[$i] ne $input[$i])) {
                   10521:                     $result.='<br /><span class="LC_warning">'.
                   10522:                              &mt('More than one correct result given for question "[_1]": [_2] versus [_3].',
                   10523:                                  $env{'form.question:'.$i},$correct[$i],$input[$i]).'</span>';
1.596.2.4  raeburn  10524:                  } elsif (($input[$i]) || ($input[$i] eq '0')) {
1.414     www      10525:                     $correct[$i]=$input[$i];
                   10526:                  }
                   10527:              }
                   10528:           }
                   10529:        }
1.415     www      10530:        for (my $i=0;$i<$number;$i++) {
1.596.2.4  raeburn  10531:           if ((!$correct[$i]) && ($correct[$i] ne '0')) {
1.414     www      10532:              $result.='<br /><span class="LC_error">'.
                   10533:                       &mt('No correct result given for question "[_1]"!',
                   10534:                           $env{'form.question:'.$i}).'</span>';
                   10535:           }
                   10536:        }
1.596.2.4  raeburn  10537:        $result.='<br />'.&mt("Correct answer: [_1]",join(', ',map { ((($_) || ($_ eq '0'))?$_:'-') } @correct));
1.414     www      10538:     }
                   10539: # Start grading
1.415     www      10540:     my $pcorrect=$env{'form.pcorrect'};
                   10541:     my $pincorrect=$env{'form.pincorrect'};
1.416     www      10542:     my $storecount=0;
1.596.2.4  raeburn  10543:     my %users=();
1.415     www      10544:     foreach my $key (keys(%env)) {
1.420     www      10545:        my $user='';
1.415     www      10546:        if ($key=~/^form\.student\:(.*)$/) {
1.420     www      10547:           $user=$1;
                   10548:        }
                   10549:        if ($key=~/^form\.unknown\:(.*)$/) {
                   10550:           my $id=$1;
                   10551:           if (($env{'form.uname'.$id}) && ($env{'form.udom'.$id})) {
                   10552:              $user=$env{'form.uname'.$id}.':'.$env{'form.udom'.$id};
1.437     www      10553:           } elsif ($env{'form.multi'.$id}) {
                   10554:              $user=$env{'form.multi'.$id};
1.420     www      10555:           }
                   10556:        }
1.596.2.4  raeburn  10557:        if ($user) {
                   10558:           if ($users{$user}) {
                   10559:              $result.='<br /><span class="LC_warning">'.
1.596.2.12.2.  8(raebur 10560:3):                       &mt('More than one entry found for [_1]!','<tt>'.$user.'</tt>').
1.596.2.4  raeburn  10561:                       '</span><br />';
                   10562:           }
                   10563:           $users{$user}=1;
1.415     www      10564:           my @answer=split(/\,/,$env{$key});
                   10565:           my $sum=0;
1.522     www      10566:           my $realnumber=$number;
1.415     www      10567:           for (my $i=0;$i<$number;$i++) {
1.576     www      10568:              if  ($correct[$i] eq '-') {
                   10569:                 $realnumber--;
                   10570:              } elsif ($answer[$i]) {
1.415     www      10571:                 if ($gradingmechanism eq 'attendance') {
                   10572:                    $sum+=$pcorrect;
1.576     www      10573:                 } elsif ($correct[$i] eq '*') {
1.522     www      10574:                    $sum+=$pcorrect;
1.415     www      10575:                 } else {
1.596.2.4  raeburn  10576: # We actually grade if correct or not
                   10577:                    my $increment=$pincorrect;
                   10578: # Special case: numerical answer "0"
                   10579:                    if ($correct[$i] eq '0') {
                   10580:                       if ($answer[$i]=~/^[0\.]+$/) {
                   10581:                          $increment=$pcorrect;
                   10582:                       }
                   10583: # General numerical answer, both evaluate to something non-zero
                   10584:                    } elsif ((1.0*$correct[$i]!=0) && (1.0*$answer[$i]!=0)) {
                   10585:                       if (1.0*$correct[$i]==1.0*$answer[$i]) {
                   10586:                          $increment=$pcorrect;
                   10587:                       }
                   10588: # Must be just alphanumeric
                   10589:                    } elsif ($answer[$i] eq $correct[$i]) {
                   10590:                       $increment=$pcorrect;
1.415     www      10591:                    }
1.596.2.4  raeburn  10592:                    $sum+=$increment;
1.415     www      10593:                 }
                   10594:              }
                   10595:           }
1.522     www      10596:           my $ave=$sum/(100*$realnumber);
1.416     www      10597: # Store
                   10598:           my ($username,$domain)=split(/\:/,$user);
                   10599:           my %grades=();
                   10600:           $grades{"resource.$part.solved"}='correct_by_override';
                   10601:           $grades{"resource.$part.awarded"}=$ave;
                   10602:           $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
                   10603:           my $returncode=&Apache::lonnet::cstore(\%grades,$symb,
                   10604:                                                  $env{'request.course.id'},
                   10605:                                                  $domain,$username);
                   10606:           if ($returncode ne 'ok') {
                   10607:              $result.="<br /><span class=\"LC_error\">Failed to save student $username:$domain. Message when trying to save was ($returncode)</span>";
                   10608:           } else {
                   10609:              $storecount++;
                   10610:           }
1.415     www      10611:        }
                   10612:     }
                   10613: # We are done
1.549     hauer    10614:     $result.='<br />'.&mt('Successfully stored grades for [quant,_1,student].',$storecount).
1.596.2.4  raeburn  10615:              '</td>'.
                   10616:              &Apache::loncommon::end_data_table_row().
                   10617:              &Apache::loncommon::end_data_table()."<br /><br />\n";
1.414     www      10618:     return $result.&show_grading_menu_form($symb);
                   10619: }
                   10620: 
1.582     raeburn  10621: sub navmap_errormsg {
                   10622:     return '<div class="LC_error">'.
                   10623:            &mt('An error occurred retrieving information about resources in the course.').'<br />'.
1.595     raeburn  10624:            &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>').
1.582     raeburn  10625:            '</div>';
                   10626: }
                   10627: 
1.596.2.12.2.  (raeburn 10628:): sub startpage {
                   10629:):     my ($r,$symb,$crumbs,$onlyfolderflag,$nodisplayflag,$stuvcurrent,$stuvdisp,$nomenu,$js) = @_;
                   10630:):     if ($nomenu) {
                   10631:):         $r->print(&Apache::loncommon::start_page("Student's Version",$js,{'only_body' => '1'}));
                   10632:):     } else {
                   10633:):         $r->print(&Apache::loncommon::start_page('Grading',$js,
                   10634:):                                                  {'bread_crumbs' => $crumbs}));
                   10635:):     }
                   10636:):     unless ($nodisplayflag) {
                   10637:):        $r->print(&Apache::lonhtmlcommon::resource_info_box($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp));
                   10638:):     }
                   10639:): }
                   10640:): 
1.1       albertel 10641: sub handler {
1.41      ng       10642:     my $request=$_[0];
1.434     albertel 10643:     &reset_caches();
1.596.2.4  raeburn  10644:     if ($request->header_only) {
                   10645:         &Apache::loncommon::content_type($request,'text/html');
                   10646:         $request->send_http_header;
                   10647:         return OK;
1.41      ng       10648:     }
                   10649:     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});
1.596.2.4  raeburn  10650: 
1.324     albertel 10651:     my $symb=&get_symb($request,1);
1.160     albertel 10652:     my @commands=&Apache::loncommon::get_env_multiple('form.command');
                   10653:     my $command=$commands[0];
1.447     foxr     10654: 
1.160     albertel 10655:     if ($#commands > 0) {
                   10656: 	&Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
                   10657:     }
1.447     foxr     10658: 
1.513     foxr     10659:     $ssi_error = 0;
1.535     raeburn  10660:     my $brcrum = [{href=>"/adm/grades",text=>"Grading"}];
1.596.2.4  raeburn  10661:     my $start_page = &Apache::loncommon::start_page('Grading',undef,
1.596.2.12.2.  (raeburn 10662:):                                                     {'bread_crumbs' => $brcrum});
1.324     albertel 10663:     if ($symb eq '' && $command eq '') {
1.257     albertel 10664: 	if ($env{'user.adv'}) {
1.596.2.4  raeburn  10665:             &Apache::loncommon::content_type($request,'text/html');
                   10666:             $request->send_http_header;
                   10667:             $request->print($start_page);
1.257     albertel 10668: 	    if (($env{'form.codeone'}) && ($env{'form.codetwo'}) &&
                   10669: 		($env{'form.codethree'})) {
                   10670: 		my $token=$env{'form.codeone'}.'*'.$env{'form.codetwo'}.'*'.
                   10671: 		    $env{'form.codethree'};
1.41      ng       10672: 		my ($tsymb,$tuname,$tudom,$tcrsid)=
                   10673: 		    &Apache::lonnet::checkin($token);
                   10674: 		if ($tsymb) {
1.137     albertel 10675: 		    my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb);
1.41      ng       10676: 		    if (&Apache::lonnet::allowed('mgr',$tcrsid)) {
1.513     foxr     10677: 			$request->print(&ssi_with_retries('/res/'.$url, $ssi_retries,
1.99      albertel 10678: 					  ('grade_username' => $tuname,
                   10679: 					   'grade_domain' => $tudom,
                   10680: 					   'grade_courseid' => $tcrsid,
                   10681: 					   'grade_symb' => $tsymb)));
1.41      ng       10682: 		    } else {
1.45      ng       10683: 			$request->print('<h3>Not authorized: '.$token.'</h3>');
1.99      albertel 10684: 		    }
1.41      ng       10685: 		} else {
1.45      ng       10686: 		    $request->print('<h3>Not a valid DocID: '.$token.'</h3>');
1.41      ng       10687: 		}
1.14      www      10688: 	    } else {
1.41      ng       10689: 		$request->print(&Apache::lonxml::tokeninputfield());
                   10690: 	    }
1.596.2.4  raeburn  10691:         } elsif ($env{'request.course.id'}) {
                   10692:             &init_perm(); 
                   10693:             if (!%perm) {
                   10694:                 $request->internal_redirect('/adm/quickgrades');
1.596.2.12.2.  3(raebur 10695:3):                 return OK;
1.596.2.4  raeburn  10696:             } else {
                   10697:                 &Apache::loncommon::content_type($request,'text/html');
                   10698:                 $request->send_http_header;
                   10699:                 $request->print($start_page);
                   10700:             }
                   10701:         }
1.41      ng       10702:     } else {
1.596.2.4  raeburn  10703:         &init_perm();
                   10704:         if (!$env{'request.course.id'}) {
1.596.2.11  raeburn  10705:             unless ((&Apache::lonnet::allowed('usc',$env{'request.role.domain'})) &&
                   10706:                     ($command =~ /^scantronupload/)) {
                   10707:                 # Not in a course.
                   10708:                 $env{'user.error.msg'}="/adm/grades::vgr:0:0:Cannot display grades page outside course context";
                   10709:                 return HTTP_NOT_ACCEPTABLE;
                   10710:             }
1.596.2.4  raeburn  10711:         } elsif (!%perm) {
                   10712:             $request->internal_redirect('/adm/quickgrades');
                   10713:         }
                   10714:         &Apache::loncommon::content_type($request,'text/html');
                   10715:         $request->send_http_header;
1.596.2.12.2.  (raeburn 10716:):         unless ((($command eq 'submission' || $command eq 'versionsub')) && ($perm{'vgr'})) {
                   10717:):             $request->print($start_page); 
                   10718:):         }
1.104     albertel 10719: 	if ($command eq 'submission' && $perm{'vgr'}) {
1.596.2.12.2.  (raeburn 10720:):             my ($stuvcurrent,$stuvdisp,$versionform,$js);
                   10721:):             if (($env{'form.student'} ne '') && ($env{'form.userdom'} ne '')) {
                   10722:):                 ($stuvcurrent,$stuvdisp,$versionform,$js) =
                   10723:):                     &choose_task_version_form($symb,$env{'form.student'},
                   10724:):                                               $env{'form.userdom'});
                   10725:):             }
                   10726:):             &startpage($request,$symb,[{href=>"", text=>"Student Submissions"}],undef,undef,$stuvcurrent,$stuvdisp,undef,$js);
                   10727:):             if ($versionform) {
                   10728:):                 $request->print($versionform);
                   10729:):             }
                   10730:):             $request->print('<br clear="all" />');
1.257     albertel 10731: 	    ($env{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0));
1.596.2.12.2.  (raeburn 10732:):         } elsif ($command eq 'versionsub' && $perm{'vgr'}) {
                   10733:):             my ($stuvcurrent,$stuvdisp,$versionform,$js) =
                   10734:):                 &choose_task_version_form($symb,$env{'form.student'},
                   10735:):                                           $env{'form.userdom'},
                   10736:):                                           $env{'form.inhibitmenu'});
                   10737:):             &startpage($request,$symb,[{href=>"", text=>"Previous Student Version"}],undef,undef,$stuvcurrent,$stuvdisp,$env{'form.inhibitmenu'},$js);
                   10738:):             if ($versionform) {
                   10739:):                 $request->print($versionform);
                   10740:):             }
                   10741:):             $request->print('<br clear="all" />');
                   10742:):             $request->print(&show_previous_task_version($request,$symb));
1.103     albertel 10743: 	} elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) {
1.68      ng       10744: 	    &pickStudentPage($request);
1.103     albertel 10745: 	} elsif ($command eq 'displayPage' && $perm{'vgr'}) {
1.68      ng       10746: 	    &displayPage($request);
1.104     albertel 10747: 	} elsif ($command eq 'gradeByPage' && $perm{'mgr'}) {
1.71      ng       10748: 	    &updateGradeByPage($request);
1.104     albertel 10749: 	} elsif ($command eq 'processGroup' && $perm{'vgr'}) {
1.41      ng       10750: 	    &processGroup($request);
1.104     albertel 10751: 	} elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {
1.443     banghart 10752: 	    $request->print(&grading_menu($request));
                   10753: 	} elsif ($command eq 'submit_options' && $perm{'vgr'}) {
                   10754: 	    $request->print(&submit_options($request));
1.104     albertel 10755: 	} elsif ($command eq 'viewgrades' && $perm{'vgr'}) {
1.41      ng       10756: 	    $request->print(&viewgrades($request));
1.104     albertel 10757: 	} elsif ($command eq 'handgrade' && $perm{'mgr'}) {
1.41      ng       10758: 	    $request->print(&processHandGrade($request));
1.106     albertel 10759: 	} elsif ($command eq 'editgrades' && $perm{'mgr'}) {
1.41      ng       10760: 	    $request->print(&editgrades($request));
1.106     albertel 10761: 	} elsif ($command eq 'verify' && $perm{'vgr'}) {
1.41      ng       10762: 	    $request->print(&verifyreceipt($request));
1.400     www      10763:         } elsif ($command eq 'processclicker' && $perm{'mgr'}) {
                   10764:             $request->print(&process_clicker($request));
                   10765:         } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {
                   10766:             $request->print(&process_clicker_file($request));
1.414     www      10767:         } elsif ($command eq 'assignclickergrades' && $perm{'mgr'}) {
                   10768:             $request->print(&assign_clicker_grades($request));
1.106     albertel 10769: 	} elsif ($command eq 'csvform' && $perm{'mgr'}) {
1.72      ng       10770: 	    $request->print(&upcsvScores_form($request));
1.106     albertel 10771: 	} elsif ($command eq 'csvupload' && $perm{'mgr'}) {
1.41      ng       10772: 	    $request->print(&csvupload($request));
1.106     albertel 10773: 	} elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) {
1.41      ng       10774: 	    $request->print(&csvuploadmap($request));
1.246     albertel 10775: 	} elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {
1.257     albertel 10776: 	    if ($env{'form.associate'} ne 'Reverse Association') {
1.246     albertel 10777: 		$request->print(&csvuploadoptions($request));
1.41      ng       10778: 	    } else {
1.257     albertel 10779: 		if ( $env{'form.upfile_associate'} ne 'reverse' ) {
                   10780: 		    $env{'form.upfile_associate'} = 'reverse';
1.41      ng       10781: 		} else {
1.257     albertel 10782: 		    $env{'form.upfile_associate'} = 'forward';
1.41      ng       10783: 		}
                   10784: 		$request->print(&csvuploadmap($request));
                   10785: 	    }
1.246     albertel 10786: 	} elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
                   10787: 	    $request->print(&csvuploadassign($request));
1.106     albertel 10788: 	} elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
1.75      albertel 10789: 	    $request->print(&scantron_selectphase($request));
1.203     albertel 10790:  	} elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
                   10791:  	    $request->print(&scantron_do_warning($request));
1.142     albertel 10792: 	} elsif ($command eq 'scantron_validate' && $perm{'mgr'}) {
                   10793: 	    $request->print(&scantron_validate_file($request));
1.106     albertel 10794: 	} elsif ($command eq 'scantron_process' && $perm{'mgr'}) {
1.82      albertel 10795: 	    $request->print(&scantron_process_students($request));
1.157     albertel 10796:  	} elsif ($command eq 'scantronupload' && 
1.257     albertel 10797:  		 (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
                   10798: 		  &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
1.162     albertel 10799:  	    $request->print(&scantron_upload_scantron_data($request)); 
1.157     albertel 10800:  	} elsif ($command eq 'scantronupload_save' &&
1.257     albertel 10801:  		 (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
                   10802: 		  &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
1.157     albertel 10803:  	    $request->print(&scantron_upload_scantron_data_save($request));
1.202     albertel 10804:  	} elsif ($command eq 'scantron_download' &&
1.257     albertel 10805: 		 &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
1.162     albertel 10806:  	    $request->print(&scantron_download_scantron_data($request));
1.523     raeburn  10807:         } elsif ($command eq 'checksubmissions' && $perm{'vgr'}) {
                   10808:             $request->print(&checkscantron_results($request));     
1.106     albertel 10809: 	} elsif ($command) {
1.562     bisitz   10810: 	    $request->print('<p class="LC_error">'.&mt('Access Denied ([_1])',$command).'</p>');
1.26      albertel 10811: 	}
1.2       albertel 10812:     }
1.513     foxr     10813:     if ($ssi_error) {
                   10814: 	&ssi_print_error($request);
                   10815:     }
1.353     albertel 10816:     $request->print(&Apache::loncommon::end_page());
1.434     albertel 10817:     &reset_caches();
1.596.2.4  raeburn  10818:     return OK;
1.44      ng       10819: }
                   10820: 
1.1       albertel 10821: 1;
                   10822: 
1.13      albertel 10823: __END__;
1.531     jms      10824: 
                   10825: 
                   10826: =head1 NAME
                   10827: 
                   10828: Apache::grades
                   10829: 
                   10830: =head1 SYNOPSIS
                   10831: 
                   10832: Handles the viewing of grades.
                   10833: 
                   10834: This is part of the LearningOnline Network with CAPA project
                   10835: described at http://www.lon-capa.org.
                   10836: 
                   10837: =head1 OVERVIEW
                   10838: 
                   10839: Do an ssi with retries:
                   10840: While I'd love to factor out this with the vesrion in lonprintout,
                   10841: 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
                   10842: I'm not quite ready to invent (e.g. an ssi_with_retry object).
                   10843: 
                   10844: At least the logic that drives this has been pulled out into loncommon.
                   10845: 
                   10846: 
                   10847: 
                   10848: ssi_with_retries - Does the server side include of a resource.
                   10849:                      if the ssi call returns an error we'll retry it up to
                   10850:                      the number of times requested by the caller.
1.596.2.12.2.  8(raebur 10851:4):                      If we still have a problem, no text is appended to the
1.531     jms      10852:                      output and we set some global variables.
                   10853:                      to indicate to the caller an SSI error occurred.  
                   10854:                      All of this is supposed to deal with the issues described
1.596.2.12.2.  8(raebur 10855:4):                      in LON-CAPA BZ 5631 see:
1.531     jms      10856:                      http://bugs.lon-capa.org/show_bug.cgi?id=5631
                   10857:                      by informing the user that this happened.
                   10858: 
                   10859: Parameters:
                   10860:   resource   - The resource to include.  This is passed directly, without
                   10861:                interpretation to lonnet::ssi.
                   10862:   form       - The form hash parameters that guide the interpretation of the resource
                   10863:                
                   10864:   retries    - Number of retries allowed before giving up completely.
                   10865: Returns:
                   10866:   On success, returns the rendered resource identified by the resource parameter.
                   10867: Side Effects:
                   10868:   The following global variables can be set:
                   10869:    ssi_error                - If an unrecoverable error occurred this becomes true.
                   10870:                               It is up to the caller to initialize this to false
                   10871:                               if desired.
                   10872:    ssi_error_resource  - If an unrecoverable error occurred, this is the value
                   10873:                               of the resource that could not be rendered by the ssi
                   10874:                               call.
                   10875:    ssi_error_message   - The error string fetched from the ssi response
                   10876:                               in the event of an error.
                   10877: 
                   10878: 
                   10879: =head1 HANDLER SUBROUTINE
                   10880: 
                   10881: ssi_with_retries()
                   10882: 
                   10883: =head1 SUBROUTINES
                   10884: 
                   10885: =over
                   10886: 
                   10887: =item scantron_get_correction() : 
                   10888: 
                   10889:    Builds the interface screen to interact with the operator to fix a
                   10890:    specific error condition in a specific scanline
                   10891: 
                   10892:  Arguments:
                   10893:     $r           - Apache request object
                   10894:     $i           - number of the current scanline
                   10895:     $scan_record - hash ref as returned from &scantron_parse_scanline()
                   10896:     $scan_config - hash ref as returned from &get_scantron_config()
                   10897:     $line        - full contents of the current scanline
                   10898:     $error       - error condition, valid values are
                   10899:                    'incorrectCODE', 'duplicateCODE',
                   10900:                    'doublebubble', 'missingbubble',
                   10901:                    'duplicateID', 'incorrectID'
                   10902:     $arg         - extra information needed
                   10903:        For errors:
                   10904:          - duplicateID   - paper number that this studentID was seen before on
                   10905:          - duplicateCODE - array ref of the paper numbers this CODE was
                   10906:                            seen on before
                   10907:          - incorrectCODE - current incorrect CODE 
                   10908:          - doublebubble  - array ref of the bubble lines that have double
                   10909:                            bubble errors
                   10910:          - missingbubble - array ref of the bubble lines that have missing
                   10911:                            bubble errors
                   10912: 
1.596.2.12.2.  6(raebur 10913:3):    $randomorder - True if exam folder has randomorder set
                   10914:3):    $randompick  - True if exam folder has randompick set
                   10915:3):    $respnumlookup - Reference to HASH mapping question numbers in bubble lines
                   10916:3):                      for current line to question number used for same question
                   10917:3):                      in "Master Seqence" (as seen by Course Coordinator).
                   10918:3):    $startline   - Reference to hash where key is question number (0 is first)
                   10919:3):                   and value is number of first bubble line for current student
                   10920:3):                   or code-based randompick and/or randomorder.
                   10921:3): 
                   10922:3): 
1.531     jms      10923: =item  scantron_get_maxbubble() : 
                   10924: 
1.582     raeburn  10925:    Arguments:
                   10926:        $nav_error  - Reference to scalar which is a flag to indicate a
                   10927:                       failure to retrieve a navmap object.
                   10928:        if $nav_error is set to 1 by scantron_get_maxbubble(), the 
                   10929:        calling routine should trap the error condition and display the warning
                   10930:        found in &navmap_errormsg().
                   10931: 
1.596.2.12.2.  (raeburn 10932:):        $scantron_config - Reference to bubblesheet format configuration hash.
                   10933:): 
1.531     jms      10934:    Returns the maximum number of bubble lines that are expected to
                   10935:    occur. Does this by walking the selected sequence rendering the
                   10936:    resource and then checking &Apache::lonxml::get_problem_counter()
                   10937:    for what the current value of the problem counter is.
                   10938: 
                   10939:    Caches the results to $env{'form.scantron_maxbubble'},
                   10940:    $env{'form.scantron.bubble_lines.n'}, 
                   10941:    $env{'form.scantron.first_bubble_line.n'} and
                   10942:    $env{"form.scantron.sub_bubblelines.n"}
1.596.2.12.2.  6(raebur 10943:3):    which are the total number of bubble lines, the number of bubble
1.531     jms      10944:    lines for response n and number of the first bubble line for response n,
                   10945:    and a comma separated list of numbers of bubble lines for sub-questions
                   10946:    (for optionresponse, matchresponse, and rankresponse items), for response n.  
                   10947: 
                   10948: 
                   10949: =item  scantron_validate_missingbubbles() : 
                   10950: 
                   10951:    Validates all scanlines in the selected file to not have any
                   10952:     answers that don't have bubbles that have not been verified
                   10953:     to be bubble free.
                   10954: 
                   10955: =item  scantron_process_students() : 
                   10956: 
1.596.2.6  raeburn  10957:    Routine that does the actual grading of the bubblesheet information.
1.531     jms      10958: 
                   10959:    The parsed scanline hash is added to %env 
                   10960: 
                   10961:    Then foreach unskipped scanline it does an &Apache::lonnet::ssi()
                   10962:    foreach resource , with the form data of
                   10963: 
                   10964: 	'submitted'     =>'scantron' 
                   10965: 	'grade_target'  =>'grade',
                   10966: 	'grade_username'=> username of student
                   10967: 	'grade_domain'  => domain of student
                   10968: 	'grade_courseid'=> of course
                   10969: 	'grade_symb'    => symb of resource to grade
                   10970: 
                   10971:     This triggers a grading pass. The problem grading code takes care
                   10972:     of converting the bubbled letter information (now in %env) into a
                   10973:     valid submission.
                   10974: 
                   10975: =item  scantron_upload_scantron_data() :
                   10976: 
1.596.2.6  raeburn  10977:     Creates the screen for adding a new bubblesheet data file to a course.
1.531     jms      10978: 
                   10979: =item  scantron_upload_scantron_data_save() : 
                   10980: 
                   10981:    Adds a provided bubble information data file to the course if user
                   10982:    has the correct privileges to do so. 
                   10983: 
                   10984: =item  valid_file() :
                   10985: 
                   10986:    Validates that the requested bubble data file exists in the course.
                   10987: 
                   10988: =item  scantron_download_scantron_data() : 
                   10989: 
                   10990:    Shows a list of the three internal files (original, corrected,
1.596.2.6  raeburn  10991:    skipped) for a specific bubblesheet data file that exists in the
1.531     jms      10992:    course.
                   10993: 
                   10994: =item  scantron_validate_ID() : 
                   10995: 
                   10996:    Validates all scanlines in the selected file to not have any
1.556     weissno  10997:    invalid or underspecified student/employee IDs
1.531     jms      10998: 
1.582     raeburn  10999: =item navmap_errormsg() :
                   11000: 
                   11001:    Returns HTML mark-up inside a <div></div> with a link to re-initialize the course.
                   11002:    Should be called whenever the request to instantiate a navmap object fails.  
                   11003: 
1.531     jms      11004: =back
                   11005: 
                   11006: =cut

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>
500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.